Laravel 的服务容器是框架中最重要的部分之一,但它却很少受到许多开发人员的关注。在面试了大量的候选人后,我意识到这种无知背后有两个主要原因。
- 他们发现依赖注入的想法很难理解。更不用说 IoC 和 IoC 容器的想法了。
- 他们不知道他们是否会使用容器。
依赖注入和 IoC
依赖注入的过度简化定义是将类依赖作为参数传递给其方法之一(通常是构造函数)的过程。
看看下面这段没有依赖注入的代码:
<?php
namespace App;
use App\Models\Post;
use App\Services\TwitterService;
class Publication {
public function __construct()
{
// dependency is instantiated inside the class
$this->twitterService = new TwitterService();
}
public function publish(Post $post)
{
$post->publish();
$this->socialize($post);
}
protected function socialize($post)
{
$this->twitterService->share($post);
}
}
这个类可以是一个虚构的博客平台的一部分,负责发布一篇文章并在社交媒体上分享。
该socialize()方法使用TwitterService该类的一个实例,该实例包含一个名为 的公共方法share()。
<?php
namespace App\Services;
use App\Models\Post;
class TwitterService {
public function share(Post $post)
{
dd('shared on Twitter!');
}
}
如您所见,在构造函数中,TwitterService已经创建了该类的一个新实例。您可以将实例作为外部参数注入构造函数,而不是在类内部执行实例化。
<?php
namespace App;
use App\Models\Post;
use App\Services\TwitterService;
class Publication {
public function __construct(TwitterService $twitterService)
{
$this->twitterService = $twitterService;
}
public function publish(Post $post)
{
$post->publish();
$this->socialize($post);
}
protected function socialize($post)
{
$this->twitterService->share($post);
}
}
对于这个简单的演示,您可以使用文件/中的路由回调routes/web.php。
<?php
// routes/web.php
use App\Publication;
use App\Models\Post;
use App\Services\TwitterService;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$post = new Post();
// dependency injection
$publication = new Publication(new TwitterService());
dd($publication->publish($post));
// shared on Twitter!
});
这是基本级别的依赖注入。将依赖注入应用到类会导致控制反转。以前,依赖类iePublication控制着依赖类ie 的实例化,TwitterService而后来,控制权已移交给框架。
IoC 容器
IoC 容器可以使依赖注入的过程更加高效。它是一个简单的类,能够在需要时保存和提供数据片段。一个简化的 IoC 容器可以写成如下
<?php
namespace App;
class Container {
// array for keeping the container bindings
protected $bindings = [];
// binds new data to the container
public function bind($key, $value)
{
// bind the given value with the given key
$this->bindings[$key] = $value;
}
// returns bound data from the container
public function make($key)
{
if (isset($this->bindings[$key])) {
// check if the bound data is a callback
if (is_callable($this->bindings[$key])) {
// if yes, call the callback and return the value
return call_user_func($this->bindings[$key]);
} else {
// if not, return the value as it is
return $this->bindings[$key];
}
}
}
}
您可以使用以下方法将任何数据绑定到此容器bind()
<?php
// routes/web.php
use App\Container;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$container = new Container();
$container->bind('name', 'Farhan Hasin Chowdhury');
dd($container->make('name'));
// Farhan Hasin Chowdhury
});
您可以通过传递一个回调函数将类绑定到此容器,该回调函数将类的实例作为第二个参数返回。
<?php
// routes/web.php
use App\Container;
use App\Service\TwitterService;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$container = new Container;
$container->bind(TwitterService::class, function(){
return new App\Services\TwitterService;
});
ddd($container->make(TwitterService::class));
// App\Services\TwitterService {#269}
});
假设您的TwitterService班级需要一个 API 密钥来进行身份验证。在这种情况下,您可以执行以下操作:
<?php
// routes/web.php
use App\Container;
use App\Service\TwitterService;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$container = new Container;
$container->bind('ApiKey', 'very-secret-api-key');
$container->bind(TwitterService::class, function() use ($container){
return new App\Services\TwitterService($container->make('ApiKey'));
});
ddd($container->make(TwitterService::class));
// App\Services\TwitterService {#269 ▼
// #apiKey: "very-secret-api-key"
// }
});
将一条数据绑定到容器后,您可以在必要时请求它。这样,您只需使用new关键字一次。
我并不是说使用new不好。但是每次使用 时new,都必须小心将正确的依赖项传递给类。然而,对于 IoC 容器,容器负责注入依赖项。
IoC 容器可以使您的代码更加灵活。考虑一种情况,您希望将TwitterService类与其他类(如LinkedInService类)交换。
该系统的当前实现不是很适合。要替换TwitterService该类,您必须创建一个新类,将其绑定到容器,并替换对前一个类的所有引用。
它不必是那样的。您可以通过使用接口来简化此过程。首先创建一个新的SocialMediaServiceInterface.
<?php
namespace App\Interfaces;
use App\Models\Post;
interface SocialMediaServiceInterface {
public function share(Post $post);
}
现在让你的TwitterService类实现这个接口。
<?php
namespace App\Services;
use App\Models\Post;
use App\Interfaces\SocialMediaServiceInterface;
class TwitterService implements SocialMediaServiceInterface {
protected $apiKey;
public function __construct($apiKey)
{
$this->apiKey = $apiKey;
}
public function share(Post $post)
{
dd('shared on Twitter!');
}
}
不是将具体类绑定到容器,而是绑定接口。在回调中,TwitterService像以前一样返回类的实例。
<?php
// routes/web.php
use App\Container;
use Illuminate\Support\Facades\Route;
use App\Interfaces\SocialMediaServiceInterface;
Route::get('/', function () {
$container = new Container;
$container->bind('ApiKey', 'very-secret-api-key');
$container->bind(SocialMediaServiceInterface::class, function() use ($container){
return new App\Services\TwitterService($container->make('ApiKey'));
});
ddd($container->make(SocialMediaServiceInterface::class));
// App\Services\TwitterService {#269 ▼
// #apiKey: "very-secret-api-key"
// }
});
到目前为止,代码和以前一样工作。当您想使用 LinkedIn 而不是 Twitter 时,乐趣就开始了。界面到位后,您可以通过两个简单的步骤完成此操作。
创建一个LinkedInService实现SocialMediaServiceInterface.
<?php
namespace App\Services;
use App\Models\Post;
use App\Interfaces\SocialMediaServiceInterface;
class LinkedInService implements SocialMediaServiceInterface {
public function share(Post $post)
{
dd('shared on LinkedIn!');
}
}
更新对bind()方法的调用以改为返回LinkedInService类的实例。
<?php
// routes/web.php
use App\Container;
use App\Interfaces\SocialMediaServiceInterface;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
$container = new Container;
$container->bind(SocialMediaServiceInterface::class, function() {
return new App\Services\LinkedInService();
});
ddd($container->make(SocialMediaServiceInterface::class));
// App\Services\LinkedInService {#269}
});
现在你得到了这个LinkedInService类的一个实例。这种方法的美妙之处在于其他地方的所有代码都保持不变。您只需更新bind()方法调用。只要一个类实现了它,SocialMediaServiceInterface它就可以作为一个有效的社交媒体服务绑定到容器。
服务容器和服务提供者
Laravel 自带了一个更强大的 IoC 容器,称为服务容器。您可以使用服务容器重写上一节中的示例,如下所示:
<?php
// routes/web.php
use App\Container;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
app()->bind('App\Interfaces\SocialMediaService', function() {
return new App\Services\LinkedInService();
});
ddd(app()->make('App\Interfaces\SocialMediaService'));
// App\Services\LinkedInService {#262}
});
在每个 Laravel 应用程序中,app实例都是容器。助手返回容器的app()一个实例。
就像你的自定义容器一样,Laravel 服务容器有一个bind()和一个make()用于绑定服务和检索服务的方法。
还有一种方法叫做singleton(). 当您将一个类绑定为单例时,该类只能有一个实例。
让我给你看一个例子。更新您的代码以创建给定类的两个实例。
<?php
// routes/web.php
use App\Container;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
app()->bind('App\Interfaces\SocialMediaService', function() {
return new App\Services\LinkedInService();
});
ddd(app()->make('App\Interfaces\SocialMediaService'), app()->make('App\Interfaces\SocialMediaService'));
// App\Services\LinkedInService {#262}
// App\Services\LinkedInService {#269}
});
由末尾的数字(#262 和#269)表示,这两个实例彼此不同。如果你将这个类绑定为一个单例,你会看到一些不同的东西。
<?php
// routes/web.php
use App\Container;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
app()->singleton('App\Interfaces\SocialMediaService', function() {
return new App\Services\LinkedInService();
});
ddd(app()->make('App\Interfaces\SocialMediaService'), app()->make('App\Interfaces\SocialMediaService'));
// App\Services\LinkedInService {#262}
// App\Services\LinkedInService {#262}
});
如您所见,现在这两个实例的编号相同,表示它们是同一个实例。
现在您已经了解了bind()、singleton()和make()方法,接下来您必须了解的是在哪里放置这些方法调用。您当然不能将它们放入您的控制器或模型中。
放置绑定的正确位置是服务提供商。服务提供者是驻留在app/Providers目录中的类。这些是框架的基石,负责引导大多数框架服务。
默认情况下,每个新的 Laravel 项目都带有五个服务提供者类。其中,AppServiceProvider类默认为空,有两种方法。它们是register()和boot()。
该register()方法用于向应用程序注册新服务。这是您放置bind()和singleton()方法调用的地方。
<?php
namespace App\Providers;
use App\Interfaces\SocialMediaService;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register services.
*
* @return void
*/
public function register()
{
$this->app->bind(SocialMediaService::class, function() {
return new \App\Services\LinkedInService;
});
}
/**
* Bootstrap services.
*
* @return void
*/
public function boot()
{
//
}
}
$this->app在提供程序内部,您只需编写而不是调用app()辅助函数即可访问容器。但你也可以这样做。
该boot()方法用于引导注册服务所需的逻辑。一个很好的例子是BroadcastingServiceProvider类。
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Broadcast;
use Illuminate\Support\ServiceProvider;
class BroadcastServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* @return void
*/
public function boot()
{
Broadcast::routes();
require base_path('routes/channels.php');
}
}
如您所见,它调用该Broadcast::routes()方法并需要该routes/channels.php文件,从而使广播路由在该过程中处于活动状态。
对于本例中的一两个简单绑定,您可以AppServiceProvider使用php artisan make:provider
发表评论 取消回复