编程学习网 > PHP技术 > laravel > 一文带你弄清楚对Laravel框架以及关键概念的重新理解
2021
04-30

一文带你弄清楚对Laravel框架以及关键概念的重新理解


首先 laravel 核心就是一个 Ioc (依赖注入 )容器。框架本身需要的很多东西其实就是各个服务,每一个服务的实现我们可以称为服务容器。 
服务举例,有auth 认证服务、cache缓存服务、DB数据库服务、fileSystem文件系统服务 等等,当然你也可以自己添加第三方的 excel 服务 zip服务等。 
有了各种各样的服务来实现我们的需求,那么如何使用这些服务呢?即服务的绑定和解析。 
首先服务需要被绑定,类似变量赋值一样,我们使用这个变量就相当于用这个值,那么服务绑定到某个名称上,例如:

$this->app->singleton('cache', function ($app) {
return new CacheManager($app);
});


这样当我们通过某种方式调用 cache这个名称 的时候就相当于调用了 cache 服务。cache 服务的实现主要就是 上述代码 CacheManager 中实现的。 
服务绑定的方式: 
简单绑定:

$this->app->bind('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});


第一个参数是想要注册的类名或者接口名,第二个参数是返回类的实例的闭包。 
绑定一个单例: 
singleton 方法绑定一个只需要解析一次的类或接口到容器,然后接下来对容器的调用将会返回同一个实例:

$this->app->singleton('HelpSpot\API', function ($app) {
return new HelpSpot\API($app->make('HttpClient'));
});


绑定实例 : 
你还可以使用 instance 方法绑定一个已存在的对象实例到容器,随后调用容器将总是返回给定的实例:

$api = new HelpSpot\API(new HttpClient);
$this->app->instance('HelpSpot\Api', $api);


绑定原始值 : 
你可能有一个接收注入类的类,同时需要注入一个原生的数值比如整型,可以结合上下文轻松注入这个类需要的任何值:

$this->app->when('App\Http\Controllers\UserController')
->needs('$variableName')
->give($value);


类似的还有上下文绑定 
有时侯我们可能有两个类使用同一个接口,但我们希望在每个类中注入不同实现, 
例如,两个控制器依赖 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(PhotoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('local');
});
$this->app->when(VideoController::class)
->needs(Filesystem::class)
->give(function () {
return Storage::disk('s3');
});


绑定接口到实现 
服务容器的一个非常强大的功能是其绑定接口到实现。我们假设有一个 EventPusher 接口及其实现类RedisEventPusher , 
编写完该接口的 RedisEventPusher 实现后,就可以将其注册到服务容器:

$this->app->bind(
'App\Contracts\EventPusher',
'App\Services\RedisEventPusher'
);


这段代码告诉容器当一个类需要 EventPusher 的实现时将会注入 RedisEventPusher, 
现在我们可以在构造器或者任何其它通过服务容器注入依赖的地方进行 EventPusher 接口的依赖注入:

use App\Contracts\EventPusher;
/**
 * 创建一个新的类实例
 *
 * @param EventPusher  $pusher
 * @return void
 */
public function __construct(EventPusher $pusher){
$this->pusher = $pusher;
}


绑定有了 ,接下来就是考虑怎么使用的问题了。即 解析: 
make方法 : 
有很多方式可以从容器中解析对象,首先,你可以使用 make 方法,该方法接收你想要解析的类名或接口名作为参数:

$fooBar = $this->app->make('HelpSpot\API');


如果你所在的代码位置访问不了 $app 变量,可以使用辅助函数resolve:

$api = resolve('HelpSpot\API');

自动注入 : 
最后,也是最常用的,你可以简单的通过在类的构造函数中对依赖进行类型提示来从容器中解析对象, 
控制器、事件监听器、队列任务、中间件等都是通过这种方式。在实践中,这是大多数对象从容器中解析的方式。 
容器会自动为其解析类注入依赖,例如,你可以在控制器的构造函数中为应用定义的仓库进行类型提示,该仓库会自动解析并注入该类:

<?php
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;
}
}

至此服务的使用也就理清楚了。回过头来,再考虑我们说了服务绑定,但是细致的说 还要讲明白绑定声明在哪里。 
也就是概念:服务提供者 (serviceProvider) 所有的的 laravel 服务都是通过服务提供者启动的。 
即所有的服务都继承自 Illuminate\Support\ServiceProvider 类。大部分服务提供者都包含两个方法: register 和 boot 。 
在 register 方法中,你唯一要做的事情就是绑事物到服务容器,不要尝试在其中注册事件监听器,路由或者任何其它功能。 
通过 Artisan 命令 make:provider 即可生成一个新的提供者:

php artisan make:provider RiakServiceProvider

register方法 
正如前面所提到的,在 register 方法中只绑定事物到服务容器,而不要做其他事情,否则,一不小心就能用到一个尚未被加载的服务提供者提供的服务。 
现在让我们来看看一个基本的服务提供者长什么样:

<?php
namespace App\Providers;
use Riak\Connection;
use Illuminate\Support\ServiceProvider;
class RiakServiceProvider extends ServiceProvider{
/**
 * 在容器中注册绑定.
 *
 * @return void
 */
public function register()
{
$this->app->singleton(Connection::class, function ($app) {
return new Connection(config('riak'));
});
}
}

该服务提供者只定义了一个 register 方法,并使用该方法在服务容器中定义了一个 Riak\Contracts\Connection 的实现。 
boot方法 
如果我们想要在服务提供者中注册视图 composer 该怎么做?这就要用到 boot 方法了。 
该方法在所有服务提供者被注册以后才会被调用,这就是说我们可以在其中访问框架已注册的所有其它服务:

<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider{
/**
 * Perform post-registration booting of services.
 *
 * @return void
 */
public function boot()
{
view()->composer('view', function () {
//
});
}
}

我们也可以在boot方法中类型提示依赖

use Illuminate\Contracts\Routing\ResponseFactory;
public function boot(ResponseFactory $response){
$response->macro('caps', function ($value) {
//
});
}

这样服务提供者有了,服务就能顺利的被调用了。再接着往回想,那么服务提供者又是怎么注册声明的呢? 
即:注册服务提供者 
所有服务提供者都是通过配置文件 config/app.php 中进行注册,该文件包含了一个列出所有服务提供者名字的 providers 数组, 
默认情况下,其中列出了所有核心服务提供者,这些服务提供者启动 Laravel核心组件,比如邮件、队列、缓存等等。 
要注册你自己的服务提供者,只需要将其追加到该数组中即可:

'providers' => [
// 其它服务提供者
App\Providers\ComposerServiceProvider::class,
],

注: 在这里声明的服务,laravel在启动的时候会自动的运行所有服务提供者的内容。 
延迟加载服务提供者 
框架启动的时候只启动必要的服务,其它不必要的服务在用的时候启动这就是延迟加载服务提供者,这才是合理的或者说好的启动方案。 
想要延迟加载一个提供者,设置 defer 属性为 true 并定义一个 provides 方法,该方法返回该提供者注册的服务容器绑定, 
可以自行参考 RedisServiceProvider (声明 defer 以及 注册了多个服务绑定) 
终于一个完整的服务提供,注册,绑定,解析流程完成了。 
接下来为了使服务的调用更加简单,Laravel 使用了 Facades(门面) 
门面: 
门面为应用服务容器中的绑定类提供了一个“静态”接口。我们完全可以只使用服务,例如

$value = resolve('cache')->get('key'); // laravel 要解析 cache ,通过服务提供者列表找到 cache 的服务提供者 进而找到 服务绑定 的类实现,然后最终调用其 get方法 

但是有了门面,我们调用起来就更加简单明了

use cache;
$value = cache::get('key');

上面的例子,内部工作流程实际上是: 
laravel 在 config/app.php 里面声明的别名 aliases 找到 cache对应的门面类: Illuminate\Support\Facades\Cache::class 
这个里面主要声明了方法:getFacadeAccessor 返回 cache 这次这个cache对应的就是服务容器绑定的名称了, 
服务提供者会在启动的时候绑定 cache 对应的类实现,那么laravel自然就找到了对应的实现,进而调用其 get方法。 
而对开发来说表面看起来就像是直接调用 cache这个类的 get静态方法一样,语义明确。 
其实浏览门面的基类代码,Facade 就会发现魔术方法 __calStatic() 这里去调用指定的方法。 
契约: 
Laravel 中的契约是指框架提供的一系列定义核心服务的接口,Contracts(契约)使得实现更加的松耦合和简单 
举例看一个紧耦合代码:

<?php
namespace App\Orders;
class Repository
{
protected $cache;
/**
 * 创建一个新的Repository实例
 *
 * @param \SomePackage\Cache\Memcached  $cache
 * @return void
 */
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
 * 通过ID获取订单
 *
 * @param int  $id
 * @return Order
 */
public function find($id)
{
if ($this->cache->has($id))    {
//
}
}
}

在类初始化的时候自动注入 \SomePackage\Cache\Memcached 类对象,但是如果下次想换成 redis 等,就不得不来这里修改 注入的类。 
那么如果使用契约呢:

<?php
namespace App\Orders;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
 * 创建一个新的Repository实例
 *
 * @param Cache  $cache
 * @return void
 */
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
}

松耦合: 
无论 cache 怎么变,实现处不用修改代码,所以降低了耦合性。 
简单: 
当所有 Laravel 服务都统一在简单接口中定义,很容易判断给定服务提供的功能。契约可以充当框架特性的简明文档 
契约的使用: 
Laravel中很多类都是通过服务容器进行解析,包括控制器,以及监听器、中间件、队列任务,甚至路由闭包。所以,要实现一个契约,需要在解析类的构造函数中类型提示这个契约接口。 
契约和门面不同之处: 
从使用上的不同,我们在使用门面的时候都是主动 声明 哪个门面,然后调用其”静态”方法, 
而门面更多的是声明到类的构造函数中,系统会自动注入,接着就可以使用了。 
契约允许你在类中定义显示的依赖,这样更加明确。

看完本文是不是对Laravel框架以及关键概念有了全新的理解,觉得对你有帮助的话欢迎前往并持续关注编程学习网了解更多相关技术咨询。

扫码二维码 获取免费视频学习资料

Python编程学习

查 看2022高级编程视频教程免费获取