[TOC]
# 服務容器
在說 Ioc 容器之前,我們需要了解什么是 Ioc 容器。
Laravel 服務容器是一個用于管理類依賴和執行依賴注入的強大工具。
在理解這句話之前,我們需要先了解一下服務容器的來龍去脈: [laravel神奇的服務容器](https://www.insp.top/learn-laravel-container)。這篇博客告訴我們,服務容器就是工廠模式的升級版,對于傳統的工廠模式來說,雖然解耦了對象和外部資源之間的關系,但是工廠和外部資源之間卻存在了耦和。而服務容器在為對象創建了外部資源的同時,又與外部資源沒有任何關系,這個就是 Ioc 容器。
?
所謂的依賴注入和控制反轉: [依賴注入和控制反轉](http://blog.csdn.net/doris_crazy/article/details/18353197),就是
只要不是由內部生產(比如初始化、構造函數 __construct 中通過工廠方法、自行手動 new 的),而是由外部以參數或其他形式注入的,都屬于依賴注入(DI)
也就是說:
依賴注入是從應用程序的角度在描述,可以把依賴注入描述完整點:應用程序依賴容器創建并注入它所需要的外部資源;
控制反轉是從容器的角度在描述,描述完整點:容器控制應用程序,由容器反向的向應用程序注入應用程序所需要的外部資源。
# Laravel中的服務容器
Laravel服務容器主要承擔兩個作用:綁定與解析,服務容器的結構如下:

# 綁定
所謂的綁定就是將接口與實現建立對應關系。幾乎所有的服務容器綁定都是在服務提供者中完成,也就是在服務提供者中綁定。
如果一個類沒有基于任何接口那么就沒有必要將其綁定到容器。容器并不需要被告知如何構建對象,因為它會使用 PHP 的反射服務自動解析出具體的對象。
也就是說,如果需要依賴注入的外部資源如果沒有接口,那么就不需要綁定,直接利用服務容器進行解析就可以了,服務容器會根據類名利用反射對其進行自動構造。
## bind綁定
綁定有多種方法,首先最常用的是bind函數的綁定:
綁定自身
~~~
$this->app->bind('App\Services\RedisEventPusher', null);
~~~
綁定閉包
~~~
$this->app->bind('name', function () {
return 'Taylor';
});//閉包返回變量
$this->app->bind('HelpSpot\API', function () {
return HelpSpot\API::class;
});//閉包直接提供類實現方式
public function testSharedClosureResolution()
{
$container = new Container;
$class = new stdClass;
$container->bind('class', function () use ($class) {
return $class;
});
$this->assertSame($class, $container->make('class'));
}//閉包返回類變量
$this->app->bind('HelpSpot\API', function () {
return new HelpSpot\API();
});//閉包直接提供類實現方式
$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});//閉包返回需要依賴注入的類
~~~
綁定接口
~~~
public function testCanBuildWithoutParameterStackWithConstructors()
{
$container = new Container;
$container->bind('Illuminate\Tests\Container\IContainerContractStub',
'Illuminate\Tests\Container\ContainerImplementationStub');
$this->assertInstanceOf(ContainerDependentStub::class,
$container->build(ContainerDependentStub::class));
}
interface IContainerContractStub
{
}
class ContainerImplementationStub implements IContainerContractStub
{
}
class ContainerDependentStub
{
public $impl;
public function __construct(IContainerContractStub $impl)
{
$this->impl = $impl;
}
}
~~~
這三種綁定方式中,第一種綁定自身一般用于綁定單例。
## bindif綁定
~~~
public function testBindIfDoesntRegisterIfServiceAlreadyRegistered()
{
$container = new Container;
$container->bind('name', function ()
return 'Taylor';
});
$container->bindIf('name', function () {
return 'Dayle';
});
$this->assertEquals('Taylor', $container->make('name'));
}
~~~
## singleton綁定
singleton 方法綁定一個只需要解析一次的類或接口到容器,然后接下來對容器的調用將會返回同一個實例:
~~~
$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});
~~~
值得注意的是,singleton綁定在解析的時候若存在參數重載,那么就自動取消單例模式。
~~~
public function testSingletonBindingsNotRespectedWithMakeParameters()
{
$container = new Container;
$container->singleton('foo', function ($app, $config) {
return $config;
});
$this->assertEquals(['name' => 'taylor'], $container->makeWith('foo', ['name' => 'taylor']));
$this->assertEquals(['name' => 'abigail'], $container->makeWith('foo', ['name' => 'abigail']));
}
~~~
## instance綁定
我們還可以使用 instance 方法綁定一個已存在的對象實例到容器,隨后調用容器將總是返回給定的實例:
~~~
$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);
~~~
## Context綁定
有時侯我們可能有兩個類使用同一個接口,但我們希望在每個類中注入不同實現,例如,兩個控制器依賴 Illuminate\Contracts\Filesystem\Filesystem 契約的不同實現。Laravel 為此定義了簡單、平滑的接口:
~~~
use Illuminate\Support\Facades\Storage;
use App\Http\Controllers\VideoController;
use App\Http\Controllers\PhotoControllers;
use Illuminate\Contracts\Filesystem\Filesystem;
$this->app->when(StorageController::class)
->needs(Filesystem::class)
->give(function () {
Storage::class
});//提供類名
$this->app->when(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return new Storage();
});//提供實現方式
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return new Storage($app->make(Disk::class));
});//需要依賴注入
~~~
## 原始值綁定
我們可能有一個接收注入類的類,同時需要注入一個原生的數值比如整型,可以結合上下文輕松注入這個類需要的任何值:
~~~
$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);
~~~
## 數組綁定
數組綁定一般用于綁定閉包和變量,但是不能綁定接口,否則只能返回接口的實現類名字符串,并不能返回實現類的對象。
~~~
public function testArrayAccess()
{
$container = new Container;
$container[IContainerContractStub::class] = ContainerImplementationStub::class;
$this->assertTrue(isset($container[IContainerContractStub::class]));
$this->assertEquals(ContainerImplementationStub::class,
$container[IContainerContractStub::class]);
unset($container['something']);
$this->assertFalse(isset($container['something']));
}
~~~
## 標簽綁定
少數情況下,我們需要解析特定分類下的所有綁定,例如,你正在構建一個接收多個不同 Report 接口實現的報告聚合器,在注冊完 Report 實現之后,可以通過 tag 方法給它們分配一個標簽:
~~~
$this->app->bind('SpeedReport', function () {
//
});
$this->app->bind('MemoryReport', function () {
//
});
$this->app->tag(['SpeedReport', 'MemoryReport'], 'reports');
~~~
這些服務被打上標簽后,可以通過 tagged 方法來輕松解析它們:
~~~
$this->app->bind('ReportAggregator', function ($app) {
return new ReportAggregator($app->tagged('reports'));
});
~~~
~~~
public function testContainerTags()
{
$container = new Container;
$container->tag('Illuminate\Tests\Container\ContainerImplementationStub', 'foo', 'bar');
$container->tag('Illuminate\Tests\Container\ContainerImplementationStubTwo', ['foo']);
$this->assertCount(1, $container->tagged('bar'));
$this->assertCount(2, $container->tagged('foo'));
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStub', $container->tagged('foo')[0]);
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStub', $container->tagged('bar')[0]);
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->tagged('foo')[1]);
$container = new Container;
$container->tag(['Illuminate\Tests\Container\ContainerImplementationStub', 'Illuminate\Tests\Container\ContainerImplementationStubTwo'], ['foo']);
$this->assertCount(2, $container->tagged('foo'));
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStub', $container->tagged('foo')[0]);
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStubTwo', $container->tagged('foo')[1]);
$this->assertEmpty($container->tagged('this_tag_does_not_exist'));
}
~~~
## extend擴展
extend是在當原來的類被注冊或者實例化出來后,可以對其進行擴展,而且可以支持多重擴展:
~~~
public function testExtendInstancesArePreserved()
{
$container = new Container;
$container->bind('foo', function () {
$obj = new StdClass;
$obj->foo = 'bar';
return $obj;
});
$obj = new StdClass;
$obj->foo = 'foo';
$container->instance('foo', $obj);
$container->extend('foo', function ($obj, $container) {
$obj->bar = 'baz';
return $obj;
});
$container->extend('foo', function ($obj, $container) {
$obj->baz = 'foo';
return $obj;
});
$this->assertEquals('foo', $container->make('foo')->foo);
$this->assertEquals('baz', $container->make('foo')->bar);
$this->assertEquals('foo', $container->make('foo')->baz);
}
~~~
## Rebounds與Rebinding
綁定是針對接口的,是為接口提供實現方式的方法。我們可以對接口在不同的時間段里提供不同的實現方法,一般來說,對同一個接口提供新的實現方法后,不會對已經實例化的對象產生任何影響。但是在一些場景下,在提供新的接口實現后,我們希望對已經實例化的對象重新做一些改變,這個就是 rebinding 函數的用途。
下面就是一個例子:
~~~
abstract class Car
{
public function __construct(Fuel $fuel)
{
$this->fuel = $fuel;
}
public function refuel($litres)
{
return $litres * $this->fuel->getPrice();
}
public function setFuel(Fuel $fuel)
{
$this->fuel = $fuel;
}
}
class JeepWrangler extends Car
{
//
}
interface Fuel
{
public function getPrice();
}
class Petrol implements Fuel
{
public function getPrice()
{
return 130.7;
}
}
~~~
我們在服務容器中是這樣對car接口和fuel接口綁定的:
~~~
$this->app->bind('fuel', function ($app) {
return new Petrol;
});
$this->app->bind('car', function ($app) {
return new JeepWrangler($app['fuel']);
});
$this->app->make('car');
~~~
如果car被服務容器解析實例化成對象之后,有人修改了 fuel 接口的實現,從 Petrol 改為 PremiumPetrol:
~~~
$this->app->bind('fuel', function ($app) {
return new PremiumPetrol;
});
~~~
由于 car 已經被實例化,那么這個接口實現的改變并不會影響到 car 的實現,假若我們想要 car 的成員變量 fuel 隨著 fuel 接口的變化而變化,我們就需要一個回調函數,每當對 fuel 接口實現進行改變的時候,都要對 car 的 fuel 變量進行更新,這就是 rebinding 的用途:
~~~
$this->app->bindShared('car', function ($app) {
return new JeepWrangler($app->rebinding('fuel', function ($app, $fuel) {
$app['car']->setFuel($fuel);
}));
});
~~~
# 服務別名
## 什么是服務別名
在說服務容器的解析之前,需要先說說服務的別名。什么是服務別名呢?不同于上一個博客中提到的 Facade 門面的別名(在 config/app 中定義),這里的別名服務綁定名稱的別名。通過服務綁定的別名,在解析服務的時候,跟不使用別名的效果一致。別名的作用也是為了同時支持全類型的服務綁定名稱以及簡短的服務綁定名稱考慮的。
?
通俗的講,假如我們想要創建 auth 服務,我們既可以這樣寫:
~~~
$this->app->make('auth')
~~~
又可以寫成:
~~~
$this->app->make('\Illuminate\Auth\AuthManager::class')
~~~
還可以寫成
~~~
$this->app->make('\Illuminate\Contracts\Auth\Factory::class')
~~~
后面兩個服務的名字都是 auth 的別名,使用別名和使用 auth 的效果是相同的。
## 服務別名的遞歸
需要注意的是別名是可以遞歸的:
~~~
app()->alias('service', 'alias_a');
app()->alias('alias_a', 'alias_b');
app()-alias('alias_b', 'alias_c');
~~~
會得到:
~~~
'alias_a' => 'service'
'alias_b' => 'alias_a'
'alias_c' => 'alias_b'
~~~
## 服務別名的實現
那么這些別名是如何加載到服務容器里面的呢?實際上,服務容器里面有個 aliases 數組:
~~~
$aliases = [
'app' => [\Illuminate\Foundation\Application::class, \Illuminate\Contracts\Container\Container::class, \Illuminate\Contracts\Foundation\Application::class],
'auth' => [\Illuminate\Auth\AuthManager::class, \Illuminate\Contracts\Auth\Factory::class],
'auth.driver' => [\Illuminate\Contracts\Auth\Guard::class],
'blade.compiler' => [\Illuminate\View\Compilers\BladeCompiler::class],
'cache' => [\Illuminate\Cache\CacheManager::class, \Illuminate\Contracts\Cache\Factory::class],
...
]
~~~
而服務容器的初始化的過程中,會運行一個函數:
~~~
public function registerCoreContainerAliases()
{
foreach ($aliases as $key => $aliases) {
foreach ($aliases as $alias) {
$this->alias($key, $alias);
}
}
}
public function alias($abstract, $alias)
{
$this->aliases[$alias] = $abstract;
$this->abstractAliases[$abstract][] = $alias;
}
~~~
加載后,服務容器的aliases和abstractAliases數組:
~~~
$aliases = [
'Illuminate\Foundation\Application' = "app"
'Illuminate\Contracts\Container\Container' = "app"
'Illuminate\Contracts\Foundation\Application' = "app"
'Illuminate\Auth\AuthManager' = "auth"
'Illuminate\Contracts\Auth\Factory' = "auth"
'Illuminate\Contracts\Auth\Guard' = "auth.driver"
'Illuminate\View\Compilers\BladeCompiler' = "blade.compiler"
'Illuminate\Cache\CacheManager' = "cache"
'Illuminate\Contracts\Cache\Factory' = "cache"
...
]
$abstractAliases = [
app = {array} [3]
0 = "Illuminate\Foundation\Application"
1 = "Illuminate\Contracts\Container\Container"
2 = "Illuminate\Contracts\Foundation\Application"
auth = {array} [2]
0 = "Illuminate\Auth\AuthManager"
1 = "Illuminate\Contracts\Auth\Factory"
auth.driver = {array} [1]
0 = "Illuminate\Contracts\Auth\Guard"
blade.compiler = {array} [1]
0 = "Illuminate\View\Compilers\BladeCompiler"
cache = {array} [2]
0 = "Illuminate\Cache\CacheManager"
1 = "Illuminate\Contracts\Cache\Factory"
...
]
~~~
# 服務解析
## make 解析
有很多方式可以從容器中解析對象,首先,你可以使用 make 方法,該方法接收你想要解析的類名或接口名作為參數:
~~~
public function testAutoConcreteResolution()
{
$container = new Container;
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerConcreteStub',
$container->make('Illuminate\Tests\Container\ContainerConcreteStub'));
}
//帶有依賴注入和默認值的解析
public function testResolutionOfDefaultParameters()
{
$container = new Container;
$instance = $container->make('Illuminate\Tests\Container\ContainerDefaultValueStub');
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerConcreteStub',
$instance->stub);
$this->assertEquals('taylor', $instance->default);
}
//
public function testResolvingWithArrayOfParameters()
{
$container = new Container;
$instance = $container->makeWith(ContainerDefaultValueStub::class, ['default' => 'adam']);
$this->assertEquals('adam', $instance->default);
$instance = $container->make(ContainerDefaultValueStub::class);
$this->assertEquals('taylor', $instance->default);
$container->bind('foo', function ($app, $config) {
return $config;
});
$this->assertEquals([1, 2, 3], $container->makeWith('foo', [1, 2, 3]));
}
public function testNestedDependencyResolution()
{
$container = new Container;
$container->bind('Illuminate\Tests\Container\IContainerContractStub', 'Illuminate\Tests\Container\ContainerImplementationStub');
$class = $container->make('Illuminate\Tests\Container\ContainerNestedDependentStub');
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerDependentStub', $class->inner);
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerImplementationStub', $class->inner->impl);
}
class ContainerDefaultValueStub
{
public $stub;
public $default;
public function __construct(ContainerConcreteStub $stub, $default = 'taylor')
{
$this->stub = $stub;
$this->default = $default;
}
}
class ContainerConcreteStub
{
}
class ContainerImplementationStub implements IContainerContractStub
{
}
class ContainerDependentStub
{
public $impl;
public function __construct(IContainerContractStub $impl)
{
$this->impl = $impl;
}
}
class ContainerNestedDependentStub
{
public $inner;
public function __construct(ContainerDependentStub $inner)
{
$this->inner = $inner;
}
}
~~~
如果你所在的代碼位置訪問不了 $app 變量,可以使用輔助函數resolve:
~~~
$api = resolve('HelpSpot\API');
~~~
## 自動注入
~~~
namespace App\Http\Controllers;
use App\Users\Repository as UserRepository;
class UserController extends Controller{
/**
* 用戶倉庫實例
*/
protected $users;
/**
* 創建一個控制器實例
*
* @param UserRepository $users 自動注入
* @return void
*/
public function __construct(UserRepository $users)
{
$this->users = $users;
}
}
~~~
## call 方法注入
make 解析是服務容器進行解析構建類對象時所用的方法,在實際應用中,還有另外一個需求,那就是當前已經獲取了一個類對象,我們想要調用它的一個方法函數,這時發現這個方法中參數眾多,如果一個個的 make 會比較繁瑣,這個時候就要用到 call 解析了。我們可以看這個例子:
~~~
class TaskRepository{
public function testContainerCall(User $user,Task $task){
$this->assertInstanceOf(User::class, $user);
$this->assertInstanceOf(Task::class, $task);
}
public static function testContainerCallStatic(User $user,Task $task){
$this->assertInstanceOf(User::class, $user);
$this->assertInstanceOf(Task::class, $task);
}
public function testCallback(){
echo 'call callback successfully!';
}
public function testDefaultMethod(){
echo 'default Method successfully!';
}
}
~~~
### 閉包函數注入
~~~
public function testCallWithDependencies()
{
$container = new Container;
$result = $container->call(function (StdClass $foo, $bar = []) {
return func_get_args();
});
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals([], $result[1]);
$result = $container->call(function (StdClass $foo, $bar = []) {
return func_get_args();
}, ['bar' => 'taylor']);
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals('taylor', $result[1]);
}
~~~
### 普通函數注入
~~~
public function testCallWithGlobalMethodName()
{
$container = new Container;
$result = $container->call('Illuminate\Tests\Container\containerTestInject');
$this->assertInstanceOf('Illuminate\Tests\Container\ContainerConcreteStub', $result[0]);
$this->assertEquals('taylor', $result[1]);
}
~~~
### 靜態方法注入
服務容器的 call 解析主要依靠 call_user_func_array() 函數,關于這個函數可以查看[ Laravel學習筆記之Callback Type - 來生做個漫畫家](https://segmentfault.com/a/1190000006981167),這個函數對類中的靜態函數和非靜態函數有一些區別,對于靜態函數來說:
~~~
class ContainerCallTest
{
public function testContainerCallStatic(){
App::call(TaskRepository::class.'@testContainerCallStatic');
App::call(TaskRepository::class.'::testContainerCallStatic');
App::call([TaskRepository::class,'testContainerCallStatic']);
}
}
~~~
服務容器調用類的靜態方法有三種,注意第三種使用數組的形式,數組中可以直接傳類名 TaskRepository::class;
### 非靜態方法注入
對于類的非靜態方法:
~~~
class ContainerCallTest
{
public function testContainerCall(){
$taskRepo = new TaskRepository();
App::call(TaskRepository::class.'@testContainerCall');
App::call([$taskRepo,'testContainerCall']);
}
}
~~~
我們可以看到非靜態方法只有兩種調用方式,而且第二種數組傳遞的參數是類對象,原因就是 call_user_func_array函數的限制,對于非靜態方法只能傳遞對象。
### bindmethod 方法綁定
服務容器還有一個 bindmethod 的方法,可以綁定類的一個方法到自定義的函數:
~~~
public function testContainCallMethodBind(){
App::bindMethod(TaskRepository::class.'@testContainerCallStatic',function () {
$taskRepo = new TaskRepository();
$taskRepo->testCallback();
});
App::call(TaskRepository::class.'@testContainerCallStatic');
App::call(TaskRepository::class.'::testContainerCallStatic');
App::call([TaskRepository::class,'testContainerCallStatic']);
App::bindMethod(TaskRepository::class.'@testContainerCall',function (TaskRepository $taskRepo) { $taskRepo->testCallback(); });
$taskRepo = new TaskRepository();
App::call(TaskRepository::class.'@testContainerCall');
App::call([$taskRepo,'testContainerCall']);
}
~~~
從結果上看,bindmethod 不會對靜態的第二種解析方法( :: 解析方式)起作用,對于其他方式都會調用綁定的函數。
~~~
public function testCallWithBoundMethod()
{
$container = new Container;
$container->bindMethod('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable', function ($stub) {
return $stub->unresolvable('foo', 'bar');
});
$result = $container->call('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable');
$this->assertEquals(['foo', 'bar'], $result);
$container = new Container;
$container->bindMethod('Illuminate\Tests\Container\ContainerTestCallStub@unresolvable', function ($stub) {
return $stub->unresolvable('foo', 'bar');
});
$result = $container->call([new ContainerTestCallStub, 'unresolvable']);
$this->assertEquals(['foo', 'bar'], $result);
}
class ContainerTestCallStub
{
public function unresolvable($foo, $bar)
{
return func_get_args();
}
}
~~~
### 默認函數注入
~~~
public function testContainCallDefultMethod(){
App::call(TaskRepository::class,[],'testContainerCall');
App::call(TaskRepository::class,[],'testContainerCallStatic');
App::bindMethod(TaskRepository::class.'@testContainerCallStatic',function () {
$taskRepo = new TaskRepository();
$taskRepo->testCallback();
});
App::bindMethod(TaskRepository::class.'@testContainerCall',function (TaskRepository $taskRepo) { $taskRepo->testCallback(); });
App::call(TaskRepository::class,[],'testContainerCall');
App::call(TaskRepository::class,[],'testContainerCallStatic');
}
~~~
值得注意的是,這種默認函數注入的方法使得非靜態的方法也可以利用類名去調用,并不需要對象。默認函數注入也回受到 bindmethod 函數的影響。
## 數組解析
~~~
app()['service'];
~~~
## app($service)的形式
~~~
app('service');
~~~
# 服務容器事件
每當服務容器解析一個對象時就會觸發一個事件。你可以使用 resolving 方法監聽這個事件:
~~~
$this->app->resolving(function ($object, $app) {
// 解析任何類型的對象時都會調用該方法...
});
$this->app->resolving(HelpSpot\API::class, function ($api, $app) {
// 解析「HelpSpot\API」類型的對象時調用...
});
$this->app->afterResolving(function ($object, $app) {
// 解析任何類型的對象后都會調用該方法...
});
$this->app->afterResolving(HelpSpot\API::class, function ($api, $app) {
// 解析「HelpSpot\API」類型的對象后調用...
});
~~~
服務容器每次解析對象的時候,都會調用這些通過 resolving 和 afterResolving 函數傳入的閉包函數,也就是觸發這些事件。
注意:如果是單例,則只在解析時會觸發一次
~~~
public function testResolvingCallbacksAreCalled()
{
$container = new Container;
$container->resolving(function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo');
$this->assertEquals('taylor', $instance->name);
}
public function testResolvingCallbacksAreCalledForType()
{
$container = new Container;
$container->resolving('StdClass', function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo');
$this->assertEquals('taylor', $instance->name);
}
public function testResolvingCallbacksShouldBeFiredWhenCalledWithAliases()
{
$container = new Container;
$container->alias('StdClass', 'std');
$container->resolving('std', function ($object) {
return $object->name = 'taylor';
});
$container->bind('foo', function () {
return new StdClass;
});
$instance = $container->make('foo');
$this->assertEquals('taylor', $instance->name);
}
~~~
# 裝飾函數
容器的裝飾函數有兩種,wrap用于裝飾call,factory用于裝飾make:
~~~
public function testContainerWrap()
{
$result = $container->wrap(function (StdClass $foo, $bar = []) {
return func_get_args();
}, ['bar' => 'taylor']);
$this->assertInstanceOf('Closure', $result);
$result = $result();
$this->assertInstanceOf('stdClass', $result[0]);
$this->assertEquals('taylor', $result[1]);
}
public function testContainerGetFactory()
{
$container = new Container;
$container->bind('name', function () {
return 'Taylor’;
});
$factory = $container->factory('name');
$this->assertEquals($container->make('name'), $factory());
}
~~~
# 容器重置flush
容器的重置函數flush會清空容器內部的aliases、abstractAliases、resolved、bindings、instances
~~~
public function testContainerFlushFlushesAllBindingsAliasesAndResolvedInstances()
{
$container = new Container;
$container->bind('ConcreteStub', function () {
return new ContainerConcreteStub;
}, true);
$container->alias('ConcreteStub', 'ContainerConcreteStub');
$concreteStubInstance = $container->make('ConcreteStub');
$this->assertTrue($container->resolved('ConcreteStub'));
$this->assertTrue($container->isAlias('ContainerConcreteStub'));
$this->assertArrayHasKey('ConcreteStub', $container->getBindings());
$this->assertTrue($container->isShared('ConcreteStub'));
$container->flush();
$this->assertFalse($container->resolved('ConcreteStub'));
$this->assertFalse($container->isAlias('ContainerConcreteStub'));
$this->assertEmpty($container->getBindings());
$this->assertFalse($container->isShared('ConcreteStub'));
}
~~~
- 配置
- composer安裝
- composer用法
- composer版本約束表達
- phpstorm
- sftp文件同步
- php類型約束
- laradock
- 配置文件緩存詳解
- git
- 自定義函數
- 核心概念
- IOC
- 服務提供者
- Facade
- 契約
- 生命周期
- 路由
- 請求
- 命名路由
- 路由分組
- 資源路由
- 控制器路由
- 響應宏
- 響應
- Command
- 創建命令
- 定時任務
- console路由
- 執行用戶自定義的定時任務
- artisan命令
- 中間件
- 創建中間件
- 使用中間件
- 前置和后置
- 詳細介紹
- 訪問次數限制
- 為 VerifyCsrfToken 添加過濾條件
- 單點登錄
- 事件
- 創建
- ORM
- 簡介
- DB類
- 配置
- CURD
- queryScope和setAttribute
- 查看sql執行過程
- 關聯關系
- 一對一
- 一對多
- 多對多
- 遠程關聯
- 多態一對多
- 多態多對多
- 關聯數據庫的調用
- withDefault
- 跨模型更新時間戳
- withCount,withSum ,withAvg, withMax,withMin
- SQL常見操作
- 模型事件
- 模型事件詳解
- 模型事件與 Observer
- deleted 事件未被觸發
- model validation
- ORM/代碼片段
- Repository模式
- 多重where語句
- 中間表類型轉換
- Collection集合
- 新增的一些方法
- 常見用法
- 求和例子
- 機場登機例子
- 計算github活躍度
- 轉化評論格式
- 計算營業額
- 創建lookup數組
- 重新組織出表和字段關系并且字段排序
- 重構循環
- 其他例子
- 其他問題一
- 去重
- 第二個數組按第一個數組的鍵值排序
- 搜索ES
- 安裝
- 表單
- Request
- sessiom
- Response
- Input
- 表單驗證
- 簡介
- Validator
- Request類
- 接口中的表單驗證
- Lumen 中自定義表單驗證返回消息
- redis
- 廣播事件
- 發布訂閱
- 隊列
- 守護進程
- redis隊列的坑
- beanstalkd
- rabbitmq
- redis隊列
- 日志模塊
- 錯誤
- 日志詳解
- 數據填充與遷移
- 生成數據
- 數據填充seed
- migrate
- 常見錯誤
- Blade模板
- 流程控制
- 子視圖
- URL
- 代碼片段
- Carbon時間類
- 一些用法
- 郵件
- 分頁
- 加密解密
- 緩存
- 文件上傳
- 優化
- 隨記
- 嵌套評論
- 判斷字符串是否是合法的 json 字符串
- 單元測試
- 計算出兩個日期的diff
- 自定義一個類文件讓composer加載
- 時間加減
- 對象數組互轉方法
- 用戶停留過久自動退出登錄
- optional 輔助方法
- 文件下載
- Api
- Dingo api
- auth.basic
- api_token
- Jwt-Auth
- passport
- Auth
- Authentication 和 Authorization
- Auth Facade
- 授權策略
- Gates
- composer包
- debug包
- idehelp包
- image處理
- 驗證碼
- jq插件
- 第三方登錄
- 第三方支付
- log顯示包
- 微信包
- xss過濾
- Excel包
- MongoDB
- php操作
- 聚合查詢
- 發送帶附件郵件
- 中文轉拼音包
- clockwork網頁調試
- emoji表情
- symfony組件
- swooletw/laravel-swoole
- 常見問題
- 跨域問題
- Laravel隊列優先級的一個坑
- cache:clear清除緩存問題
- .env無法讀取
- 源碼相關基礎知識
- __set和__get
- 依賴注入、控制反轉和依賴倒置原則
- 控制反轉容器(Ioc Container)
- 深入服務容器
- call_user_func
- compact
- 中間件簡易實現
- array_reduce
- 中間件實現代碼
- Pipeline管道操作
- composer自動加載
- redis延時隊列
- 了解laravel redis隊列
- cli
- 源碼解讀
- Facade分析
- Facade源碼分析
- IOC服務容器
- 中間件原理
- 依賴注入淺析
- 微信
- 微信公眾號
- 常用接收消息
- 6大接收接口
- 常用被動回復消息
- 接口調用憑證
- 自定義菜單
- 新增素材
- 客服消息
- 二維碼
- 微信語音
- LBS定位
- 網頁授權
- JSSDK
- easywechat
- 小程序
- 小程序配置app.json