Skip to content

扩展框架

管理器和工厂

Laravel 有几个 Manager 类用于管理基于驱动的组件的创建。这些包括缓存、会话、认证和队列组件。管理器类负责根据应用程序的配置创建特定的驱动实现。例如,CacheManager 类可以创建 APC、Memcached、文件和其他各种缓存驱动的实现。

每个管理器都包含一个 extend 方法,可以用来轻松地将新的驱动解析功能注入到管理器中。我们将在下面介绍每个管理器,并提供如何向它们注入自定义驱动支持的示例。

lightbulb

花点时间探索 Laravel 附带的各种 Manager 类,例如 CacheManagerSessionManager。阅读这些类将使您更深入地了解 Laravel 的底层工作原理。所有管理器类都扩展自 Illuminate\Support\Manager 基类,该基类为每个管理器提供了一些有用的通用功能。

缓存

要扩展 Laravel 的缓存功能,我们将使用 CacheManager 上的 extend 方法,该方法用于将自定义驱动解析器绑定到管理器中,并且在所有管理器类中通用。例如,要注册一个名为 "mongo" 的新缓存驱动,我们可以这样做:

php
Cache::extend('mongo', function($app)
{
	return Cache::repository(new MongoStore);
});

传递给 extend 方法的第一个参数是驱动的名称。这将对应于 config/cache.php 配置文件中的 driver 选项。第二个参数是一个闭包,应该返回一个 Illuminate\Cache\Repository 实例。闭包将被传递一个 $app 实例,该实例是 Illuminate\Foundation\Application 和服务容器的实例。

可以在默认的 App\Providers\AppServiceProviderboot 方法中调用 Cache::extend,该方法随新的 Laravel 应用程序一起提供,或者您可以创建自己的服务提供者来容纳扩展 - 只需不要忘记在 config/app.php 提供者数组中注册提供者。

要创建我们的自定义缓存驱动,我们首先需要实现 Illuminate\Contracts\Cache\Store 合约。因此,我们的 MongoDB 缓存实现看起来像这样:

php
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 连接实现这些方法。一旦我们的实现完成,我们就可以完成自定义驱动的注册:

php
Cache::extend('mongo', function($app)
{
	return Cache::repository(new MongoStore);
});

如果您想知道将自定义缓存驱动代码放在哪里,可以考虑将其发布在 Packagist 上!或者,您可以在 app 目录中创建一个 Extensions 命名空间。不过,请记住,Laravel 没有严格的应用程序结构,您可以根据自己的喜好组织应用程序。

会话

扩展 Laravel 以使用自定义会话驱动与扩展缓存系统一样简单。同样,我们将使用 extend 方法注册我们的自定义代码:

php
Session::extend('mongo', function($app)
{
	// 返回 SessionHandlerInterface 的实现
});

在哪里扩展会话

您应该在 AppServiceProviderboot 方法中放置会话扩展代码。

编写会话扩展

请注意,我们的自定义会话驱动应该实现 SessionHandlerInterface。此接口包含我们需要实现的几个简单方法。一个简单的 MongoDB 实现看起来像这样:

php
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,我们就可以将其注册到会话管理器中:

php
Session::extend('mongo', function($app)
{
	return new MongoHandler;
});

一旦注册了会话驱动,我们就可以在 config/session.php 配置文件中使用 mongo 驱动。

lightbulb

记住,如果您编写了自定义会话处理程序,请在 Packagist 上分享它!

认证

认证可以像缓存和会话功能一样扩展。同样,我们将使用我们熟悉的 extend 方法:

php
Auth::extend('riak', function($app)
{
	// 返回 Illuminate\Contracts\Auth\UserProvider 的实现
});

UserProvider 实现仅负责从持久存储系统(如 MySQL、Riak 等)中获取 Illuminate\Contracts\Auth\Authenticatable 实现。这两个接口允许 Laravel 认证机制继续工作,而不管用户数据如何存储或使用什么类型的类来表示它。

让我们看看 UserProvider 合约:

php
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。请记住,提供者应从 retrieveByIdretrieveByCredentials 方法返回此接口的实现:

php
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 注册我们的扩展:

php
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
<?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 如何构建的好方法。