扩展框架
管理器和工厂
Laravel 有几个 Manager
类用于管理基于驱动的组件的创建。这些包括缓存、会话、认证和队列组件。管理器类负责根据应用程序的配置创建特定的驱动实现。例如,CacheManager
类可以创建 APC、Memcached、文件和其他各种缓存驱动的实现。
每个管理器都包含一个 extend
方法,可以用来轻松地将新的驱动解析功能注入到管理器中。我们将在下面介绍每个管理器,并提供如何向它们注入自定义驱动支持的示例。
花点时间探索 Laravel 附带的各种 Manager
类,例如 CacheManager
和 SessionManager
。阅读这些类将使您更深入地了解 Laravel 的底层工作原理。所有管理器类都扩展自 Illuminate\Support\Manager
基类,该基类为每个管理器提供了一些有用的通用功能。
缓存
要扩展 Laravel 的缓存功能,我们将使用 CacheManager
上的 extend
方法,该方法用于将自定义驱动解析器绑定到管理器中,并且在所有管理器类中通用。例如,要注册一个名为 "mongo" 的新缓存驱动,我们可以这样做:
Cache::extend('mongo', function($app)
{
return Cache::repository(new MongoStore);
});
传递给 extend
方法的第一个参数是驱动的名称。这将对应于 config/cache.php
配置文件中的 driver
选项。第二个参数是一个闭包,应该返回一个 Illuminate\Cache\Repository
实例。闭包将被传递一个 $app
实例,该实例是 Illuminate\Foundation\Application
和服务容器的实例。
可以在默认的 App\Providers\AppServiceProvider
的 boot
方法中调用 Cache::extend
,该方法随新的 Laravel 应用程序一起提供,或者您可以创建自己的服务提供者来容纳扩展 - 只需不要忘记在 config/app.php
提供者数组中注册提供者。
要创建我们的自定义缓存驱动,我们首先需要实现 Illuminate\Contracts\Cache\Store
合约。因此,我们的 MongoDB 缓存实现看起来像这样:
class MongoStore implements Illuminate\Contracts\Cache\Store {
public function get($key) {}
public function put($key, $value, $minutes) {}
public function increment($key, $value = 1) {}
public function decrement($key, $value = 1) {}
public function forever($key, $value) {}
public function forget($key) {}
public function flush() {}
}
我们只需要使用 MongoDB 连接实现这些方法。一旦我们的实现完成,我们就可以完成自定义驱动的注册:
Cache::extend('mongo', function($app)
{
return Cache::repository(new MongoStore);
});
如果您想知道将自定义缓存驱动代码放在哪里,可以考虑将其发布在 Packagist 上!或者,您可以在 app
目录中创建一个 Extensions
命名空间。不过,请记住,Laravel 没有严格的应用程序结构,您可以根据自己的喜好组织应用程序。
会话
扩展 Laravel 以使用自定义会话驱动与扩展缓存系统一样简单。同样,我们将使用 extend
方法注册我们的自定义代码:
Session::extend('mongo', function($app)
{
// 返回 SessionHandlerInterface 的实现
});
在哪里扩展会话
您应该在 AppServiceProvider
的 boot
方法中放置会话扩展代码。
编写会话扩展
请注意,我们的自定义会话驱动应该实现 SessionHandlerInterface
。此接口包含我们需要实现的几个简单方法。一个简单的 MongoDB 实现看起来像这样:
class MongoHandler implements SessionHandlerInterface {
public function open($savePath, $sessionName) {}
public function close() {}
public function read($sessionId) {}
public function write($sessionId, $data) {}
public function destroy($sessionId) {}
public function gc($lifetime) {}
}
由于这些方法不像缓存 StoreInterface
那样容易理解,我们快速介绍一下每个方法的作用:
open
方法通常用于基于文件的会话存储系统。由于 Laravel 附带一个file
会话驱动,您几乎不需要在此方法中放置任何内容。您可以将其留为空存根。这只是 PHP 需要我们实现此方法的糟糕接口设计的一个事实(我们稍后会讨论)。close
方法与open
方法一样,通常也可以忽略。对于大多数驱动程序,它是不需要的。read
方法应返回与给定$sessionId
关联的会话数据的字符串版本。在您的驱动程序中检索或存储会话数据时,无需进行任何序列化或其他编码,因为 Laravel 会为您执行序列化。write
方法应将与$sessionId
关联的给定$data
字符串写入某个持久存储系统,例如 MongoDB、Dynamo 等。destroy
方法应从持久存储中删除与$sessionId
关联的数据。gc
方法应销毁所有比给定$lifetime
(UNIX 时间戳)更旧的会话数据。对于自我过期系统,如 Memcached 和 Redis,此方法可以留空。
一旦实现了 SessionHandlerInterface
,我们就可以将其注册到会话管理器中:
Session::extend('mongo', function($app)
{
return new MongoHandler;
});
一旦注册了会话驱动,我们就可以在 config/session.php
配置文件中使用 mongo
驱动。
记住,如果您编写了自定义会话处理程序,请在 Packagist 上分享它!
认证
认证可以像缓存和会话功能一样扩展。同样,我们将使用我们熟悉的 extend
方法:
Auth::extend('riak', function($app)
{
// 返回 Illuminate\Contracts\Auth\UserProvider 的实现
});
UserProvider
实现仅负责从持久存储系统(如 MySQL、Riak 等)中获取 Illuminate\Contracts\Auth\Authenticatable
实现。这两个接口允许 Laravel 认证机制继续工作,而不管用户数据如何存储或使用什么类型的类来表示它。
让我们看看 UserProvider
合约:
interface UserProvider {
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(Authenticatable $user, $token);
public function retrieveByCredentials(array $credentials);
public function validateCredentials(Authenticatable $user, array $credentials);
}
retrieveById
函数通常接收一个表示用户的数字键,例如来自 MySQL 数据库的自动递增 ID。与 ID 匹配的 Authenticatable
实现应由该方法检索并返回。
retrieveByToken
函数通过其唯一的 $identifier
和存储在字段 remember_token
中的 "记住我" $token
检索用户。与前一个方法一样,应返回 Authenticatable
实现。
updateRememberToken
方法使用新的 $token
更新 $user
字段 remember_token
。新令牌可以是成功 "记住我" 登录尝试时分配的新令牌,也可以是用户注销时的空值。
retrieveByCredentials
方法接收传递给 Auth::attempt
方法的凭据数组,以尝试登录应用程序。然后,该方法应 "查询" 底层持久存储以查找与这些凭据匹配的用户。通常,此方法将运行一个带有 $credentials['username']
的 "where" 条件的查询。然后,该方法应返回 UserInterface
的实现。此方法不应尝试进行任何密码验证或认证。
validateCredentials
方法应将给定的 $user
与 $credentials
进行比较以验证用户。例如,此方法可能会将 $user->getAuthPassword()
字符串与 $credentials['password']
的 Hash::make
进行比较。此方法应仅验证用户的凭据并返回布尔值。
现在我们已经探索了 UserProvider
上的每个方法,让我们看看 Authenticatable
。请记住,提供者应从 retrieveById
和 retrieveByCredentials
方法返回此接口的实现:
interface Authenticatable {
public function getAuthIdentifier();
public function getAuthPassword();
public function getRememberToken();
public function setRememberToken($value);
public function getRememberTokenName();
}
此接口很简单。getAuthIdentifier
方法应返回用户的 "主键"。在 MySQL 后端中,这将是自动递增的主键。getAuthPassword
应返回用户的哈希密码。此接口允许认证系统与任何用户类一起工作,而不管您使用什么 ORM 或存储抽象层。默认情况下,Laravel 在 app
目录中包含一个实现此接口的 User
类,因此您可以参考此类以获取实现示例。
最后,一旦我们实现了 UserProvider
,我们就可以使用 Auth
facade 注册我们的扩展:
Auth::extend('riak', function($app)
{
return new RiakUserProvider($app['riak.connection']);
});
在使用 extend
方法注册驱动后,您可以在 config/auth.php
配置文件中切换到新驱动。
基于服务容器的扩展
几乎每个 Laravel 框架附带的服务提供者都将对象绑定到服务容器中。您可以在 config/app.php
配置文件中找到应用程序的服务提供者列表。随着时间的推移,您应该浏览每个提供者的源代码。通过这样做,您将更好地了解每个提供者为框架添加了什么,以及用于将各种服务绑定到服务容器的键是什么。
例如,HashServiceProvider
将一个 hash
键绑定到服务容器中,该键解析为 Illuminate\Hashing\BcryptHasher
实例。您可以通过覆盖此绑定轻松扩展和覆盖此类。例如:
<?php namespace App\Providers;
class SnappyHashProvider extends \Illuminate\Hashing\HashServiceProvider {
public function boot()
{
parent::boot();
$this->app->bindShared('hash', function()
{
return new \Snappy\Hashing\ScryptHasher;
});
}
}
请注意,此类扩展了 HashServiceProvider
,而不是默认的 ServiceProvider
基类。一旦您扩展了服务提供者,请在 config/app.php
配置文件中用您扩展的提供者名称替换 HashServiceProvider
。
这是扩展任何在容器中绑定的核心类的一般方法。基本上每个核心类都是以这种方式绑定在容器中,并且可以被覆盖。再次阅读包含的框架服务提供者将使您熟悉各种类绑定到容器中的位置以及它们绑定的键。这是了解 Laravel 如何构建的好方法。