为了方便你自定义框架的核心组件功能,乃至是完备更换它们,Laravel 供应了大量可以对运用进行扩展的地方。例如,哈希做事实现了 Illuminate\Contracts\Hashing\Hasher 左券,你可以按照自己运用的需求来重新实现它。你还可以继续 Request 工具类,添加自己用的顺手的方法。你乃至可以添加全新的用户认证、缓存和会话驱动!
Laravel 组件功能常日有两种扩展办法:在做事容器里面绑定新实现,或者通过采取工厂模式实现的 Manager 类注册一个自定义的扩展。在本章中,我们将探索扩展框架核心功能的不同办法,并检讨都须要些什么代码。
管理类和工厂
Laravel 有多个 Manager 类用来管理基于驱动的组件的创建。这些组件包括缓存、会话、用户认证、行列步队组件等。管理类卖力根据运用程序的配置来创建特定的驱动实例。例如,CacheManager 可以创建 APC、Memcached、Redis 以及其他不同的缓存驱动的实现。
同时,每个管理类都包含 extend 方法,该方法可用于将新的驱动办理方案注入到管理类中。下面我们将逐个先容这些管理类,并向你展示如何将自定义的驱动注入它们。
理解你的管理类:请花点韶光看看 Laravel 中每个 Manager 类的代码,比如 CacheManager 和 SessionManager。通过阅读这些代码能让你对Laravel 底层事情事理有更加全面的理解。所有管理类都继续自Illuminate\Support\Manager 基类,该基类每个管理类供应了一些有用的通用功能(适用于 Laravel 4,Laravel 5并非如此)。
缓存
要扩展 Laravel 的缓存做事,须要利用 CacheManager 里的 extend 方法,该方法用来绑定自定义的缓存驱动到管理类。在所有管理类中都是这个逻辑,以是扩展其他的管理类也是按照这个思路来。例如,我们想注册一个新的缓存驱动,名叫「mongo」,代码可以这样写:
Cache::extend('mongo', function($app) { // Return Illuminate\Cache\Repository instance...});
extend 方法的第一个参数是自定义缓存驱动的名字。该名字对应 config/cache.php 配置文件中的 driver 配置项。第二个参数是一个会返回 Illuminate\Cache\Repository 实例的匿名函数,传入该匿名函数的 $app 参数是 Illuminate\Foundation\Application 的实例,即全局做事容器。
要创建自定义的缓存驱动,首先要实现 Illuminate\Contracts\Cache\Store 接口。以是,基于 MongoDB 实现的缓存驱动代码构造如下:
use Illuminate\Contracts\Cache\Store;class MongoStore implements Store{ public function get($key) { // TODO: Implement get() method. } public function many(array $keys) { // TODO: Implement many() method. } public function put($key, $value, $minutes) { // TODO: Implement put() method. } public function putMany(array $values, $minutes) { // TODO: Implement putMany() method. } public function increment($key, $value = 1) { // TODO: Implement increment() method. } public function decrement($key, $value = 1) { // TODO: Implement decrement() method. } public function forever($key, $value) { // TODO: Implement forever() method. } public function forget($key) { // TODO: Implement forget() method. } public function flush() { // TODO: Implement flush() method. } public function getPrefix() { // TODO: Implement getPrefix() method. }}
我们只需利用 MongoDB 连接来实现上面的每一个方法即可。一旦实现完毕,就可以像下面这样完成自定义驱动的注册
use Illuminate\Cache\Repository;use Illuminate\Support\Facades\Cache;Cache::extend('mongo', function($app){ return new Repository(new MongoStore);}
正如上面的例子所示,在创建自定义驱动的时候你可以直策应用 Illuminate\Cache\Repository 基类。常日你不须要创建自己的 Repository 类。
如果你不知道要把自定义的缓存驱动代码放到哪里,可以考虑将其放到扩展包然后发布到 Packagist 。或者,你也可以在运用的 app 目录下创建一个 Extensions 目录,然后将 MongoStore.php 放到该目录下。不过,Laravel 并没有对运用程序的目录构造做硬性规定,以是你完备可以按照自己喜好的办法组织运用程序的代码。
如果你还为在哪里存放注册代码发愁,做事供应者是个不错的地方。我们之前就讲过,利用做事供应者来管理框架扩展代码是一个非常不错的办法,将相应代码放到做事供应者的 boot 方法即可。
会话(Session)
通过自定义会话驱动扩展 Laravel 功能和扩展缓存系统一样大略。和刚才一样,我们还是利用 extend 方法来注册自定义的代码:
Session::extend('mongo', function($app){ // Return implementation of SessionHandlerInterface});
把稳,我们自定义的会话驱动须要实现 SessionHandlerInterface 接口。这个接口在 PHP 5.4 以上版本才有。但如果你用的是 PHP 5.3,也别担心,Laravel 会自动帮你定义这个接口的。该接口包含了一些须要我们实现的方法。基于 MongoDB 来实现的会话驱动的代码构培养像下面这样:
class MongoHandler implements \SessionHandlerInterface{ public function close() { // TODO: Implement close() method. } public function destroy($session_id) { // TODO: Implement destroy() method. } public function gc($maxlifetime) { // TODO: Implement gc() method. } public function open($save_path, $name) { // TODO: Implement open() method. } public function read($session_id) { // TODO: Implement read() method. } public function write($session_id, $session_data) { // TODO: Implement write() method. }}
这些方法不像前面的 Illuminate\Contracts\Cache\Store 接口定义的那么随意马虎理解。下面我们大略讲讲这些方法都是干什么的:
close 方法和 open 方法常日都不是必需的。对大部分驱动来说都不必实现。destroy 方法会将与 $sessionId 干系联的数据从持久化存储系统中删除。gc 方法会将所有存在韶光超过参数 $lifetime 设定值的数据全都删除,该参数是一个 UNIX 韶光戳。如果你利用的是诸如 Memcached 或Redis 这种自主管理过期数据的系统,那么该方法可以留空。open 方法一样平常在基于文件的会话存储系统中才会用到。Laravel 自带的 file 会话驱动利用的便是 PHP 原生的基于文件的会话系统,你可能永久也不须要在这个方法里写东西,以是留空就好。其余这也是一个接口设计的反面教材(稍后我们会谈论这一点),由于 PHP 总是哀求我们实现它,纵然大部分时候不须要实现它。read 方法会返回与给定 $sessionId 关联的会话数据的字符串版本。在你的会话驱动中,无论读取还是存储会话数据,都不须要做任何序列化和编码操作,由于 Laravel 已经替你做了。write 方法会将给定的 $data 字符串关联到对应的 $sessionId,然后将其写入到一个持久化存储系统中,例如 MongoDB、数据库、Redis 等。一旦 SessionHandlerInterface 被实现,我们就可以将其注册到会话管理器:
Session::extend('mongo', function($app) { return new MongoHandler;});
注册完毕后,我们就可以在 config/session.php 配置文件里利用mongo 驱动了。
你假如写了个自定义的会话处理器,别忘了在 Packagist 上分享它!
用户认证
用户认证的扩展办法和缓存、会话的扩展办法一样,利用认证管理类上的 extend 方法就可以了:
Auth::extend('riak', function($app) { // Return implementation of Illuminate\Contracts\Auth\UserProvider});
Illuminate\Contracts\Auth\UserProvider 接口的实现类卖力从某个持久化存储系统(如 MySQL、Riak 等)中获取 Illuminate\Contracts\Auth\Authenticatable 接口的实现类实例。这两个接口使得 Laravel 的用户认证机制得以在不用关心用户数据如何存储以及利用何种类型表示用户的情形下连续事情。
下面我们来看看 Illuminate\Contracts\Auth\UserProvider 接口的代码:
<?phpnamespace Illuminate\Contracts\Auth;interface UserProvider{ / Retrieve a user by their unique identifier. @param mixed $identifier @return \Illuminate\Contracts\Auth\Authenticatable|null / public function retrieveById($identifier); / Retrieve a user by their unique identifier and \"大众remember me\"大众 token. @param mixed $identifier @param string $token @return \Illuminate\Contracts\Auth\Authenticatable|null / public function retrieveByToken($identifier, $token); / Update the \"大众remember me\"大众 token for the given user in storage. @param \Illuminate\Contracts\Auth\Authenticatable $user @param string $token @return void / public function updateRememberToken(Authenticatable $user, $token); / Retrieve a user by the given credentials. @param array $credentials @return \Illuminate\Contracts\Auth\Authenticatable|null / public function retrieveByCredentials(array $credentials); / Validate a user against the given credentials. @param \Illuminate\Contracts\Auth\Authenticatable $user @param array $credentials @return bool / public function validateCredentials(Authenticatable $user, array $credentials);}
retrieveById 方法常日接管一个表示用户ID的数字键,比如 MySQL 数据库的自增 ID。该方法会返回与给定 ID 匹配的 Illuminate\Contracts\Auth\Authenticatable 的实现类实例,如 User 模型类实例。
当用户考试测验登录到运用时,retrieveByCredentials 方法会接管通报给 Auth::attempt 方法的认证凭据数组。然后该方法会「查询」底层的持久化存储系统,来找到与给定凭据信息匹配的用户。常日,该方法会实行一个带有「where」条件的查询来匹配参数里的 $credentials['username']。该方法不应该考试测验做任何密码验证。
validateCredentials 方法会通过比较给定 $user 和$credentials 来认证用户。例如,该方法会比较 $user->getAuthPassword() 方法返回的字符串和 $credentials['password'] 经由 Hash::make 处理后的结果,如果相等,则认为认证通过,否则认证失落败。
retrieveByToken 方法和 updateRememberToken 则用于在登录认证时实现「记住我」的功能,让用户在 Token 有效期内不用输入登录凭据即可自动登录。
现在,我们已经探索了 Illuminate\Contracts\Auth\UserProvider 接口的每一个方法,接下来,我们来看看 Illuminate\Contracts\Auth\Authenticatable 接口。别忘了,Authenticatable 接口实现的实例是通过是 UserProvider 实现实例的 retrieveById 和 retrieveByCredentials 方法返回的:
<?phpnamespace Illuminate\Contracts\Auth;interface Authenticatable{ / Get the name of the unique identifier for the user. @return string / public function getAuthIdentifierName(); / Get the unique identifier for the user. @return mixed / public function getAuthIdentifier(); / Get the password for the user. @return string / public function getAuthPassword(); / Get the token value for the \"大众remember me\"大众 session. @return string / public function getRememberToken(); / Set the token value for the \公众remember me\"大众 session. @param string $value @return void / public function setRememberToken($value); / Get the column name for the \"大众remember me\公众 token. @return string / public function getRememberTokenName();}
这个接口很大略。getAuthIdentifier 方法返回用户的「主键」。如果在 MySQL 数据库中,便是自增主键了。getAuthPassword 方法返回经由散列处理的用户密码。getAuthIdentifierName 方法会返回用户的唯一标识,比如用户名或邮箱信息。其他几个方法都是和「记住我」功能干系的。
有了这个接口,用户认证系统就可以处理任何用户类,而不用关心该用户类利用了什么 ORM 框架或者存储抽象层。默认情形下,Laravel 已经在 app 目录下供应了实现 Authenticatable 接口的 User 类。以是你可以将这个类作为实现示例。
末了,当我们实现了 Illuminate\Contracts\Auth\UserProvider 接口后,就可以将对应扩展注册进 Auth 里面:
Auth::extend('riak', function($app) { return new RiakUserProvider($app['riak.connection']);});
利用 extend 方法注册好驱动往后,你就可以在 config/auth.php 配置文件中切换到新的驱动了(对应配置项是 providers.users.driver)。
容器默认绑定
险些所有 Laravel 框架自带的做事供应者都会绑定一些工具到做事容器里。你可以在 config/app.php 配置文件里找到做事供应者列表。如果你有韶光的话,你该当大致过一遍每个做事供应者的源码。这么做的好处是你可以对每个做事供应者有更深的理解,明白它们都往框架里加了什么东西,以及对应的绑定到做事容器的键是什么,通过这些键我们就可以从容器中解析相应的做事。
学院君注:由于 Laravel 5.5 中新增了包自动创造功能,以是 config/app.php 配置文件的 providers 数组供应的做事做事者列表并不全,最全的列表在 bootstrap/cache/services.php 的 providers 数组中。
举个例子,AuthServiceProvider 向做事容器内绑定了一个 auth 键,通过这个键解析出来的做事是一个 Illuminate\Auth\AuthManager 的实例。你可以在自己的运用中通过覆盖这个做事容器绑定来轻松实现扩展并重写该类。例如,你可以创建一个继续自 AuthManager 类的子类:
namespace App\Extensions;class MyAuthManager extends Illuminate\Auth\AuthManager { //}
子类写好往后,你可以在做事供应者 AuthServiceProvider 的 boot 方法中覆盖默认的 auth:
public function boot(){ ... $this->app->singleton('auth', function ($app) { return new MyAuthManager; });}
这便是扩展绑定进容器的任意核心类的通用方法。基本上每一个核心类都以这种办法绑定进了容器,都可以被重写。还是那一句话,读一遍框架自带的做事供应者源码可以帮助你熟习各种类是怎么绑定进容器的,都绑定到哪些键上。这是学习 Laravel 框架底层究竟如何运转的最佳实践。
要求
由于这是框架里面非常根本的部分,并且在要求生命周期中很早就被实例化,以是扩展 Request 类的方法与之前扩展其他做事的示例比较是有些不同的。
首先,还是要写个继续自 Illuminate\Http\Request 的子类:
namespace App\Extensions;class CustomRequest extends Illuminate\Http\Request { // Custom, helpful methods here...}
子类写好后,打开 bootstrap/app.php 文件。该文件是每次对运用发起要求时最早被载入的几个文件之一。该文件中实行的第一个动作是创建 Laravel 的 $app 实例:
$app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../'));
当新的运用实例创建后,它将会创建一个 Illuminate\Http\Request 的实例并且将其绑定到做事容器里,键名为 request。以是,我们须要找个方法来将一个自定义的类指定为「默认的」要求类,对不对?在 Laravel 4 中,运用实例上有一个 requestClass 方法,可以用来指定自定义的要求类,但是在 Laravel 5 中,没有这个类,以是我们须要自己来实现,打开 public/index.php,在
app = require_once __DIR__.'/../bootstrap/app.php';
这行代码之后添加如下代码覆盖默认的 request 指向:
$app->alias('request', \App\Extensions\CustomRequest::class);
然后还要修正
$request = Illuminate\Http\Request::capture()
这段代码为
$request = App\Extensions\CustomRequest::capture()
指定好自定义的要求类后,Laravel 会在任何创建 Request 实例的时候都利用这个自定义的类,以便你始终拥有自定义的要求类,纵然在单元测试中也不例外!
举两个例子,怎么样写好代码
最经典的算法,献给正在口试道路上的你