扩展框架
介绍
Laravel提供了许多扩展点,供您自定义框架核心组件的行为,甚至完全替换它们。例如,哈希功能由HasherInterface
契约定义,您可以根据应用程序的需求实现它。您还可以扩展Request
对象,允许您添加自己的便利“助手”方法。您甚至可以添加全新的认证、缓存和会话驱动程序!
Laravel组件通常通过两种方式进行扩展:在IoC容器中绑定新的实现,或使用Manager
类注册扩展,这些类是“工厂”设计模式的实现。在本章中,我们将探讨扩展框架的各种方法,并检查必要的代码。
请记住,Laravel组件通常通过两种方式进行扩展:IoC绑定和Manager
类。管理器类作为“工厂”设计模式的实现,负责根据应用程序的配置实例化基于驱动程序的设施,例如缓存和会话。
管理器与工厂
Laravel有几个Manager
类来管理驱动程序组件的创建。这些包括缓存、会话、认证和队列组件。管理器类负责根据应用程序的配置创建特定的驱动程序实现。例如,CacheManager
类可以创建APC、Memcached、文件和各种其他实现的缓存驱动程序。
这些管理器中的每一个都包含一个extend
方法,可以用来轻松地将新的驱动程序解析功能注入到管理器中。我们将在下面介绍每个管理器,并提供如何向每个管理器注入自定义驱动程序支持的示例。
花点时间探索随Laravel一起提供的各种Manager
类,例如CacheManager
和SessionManager
。阅读这些类将使您更深入地了解Laravel在底层是如何工作的。所有管理器类都扩展了Illuminate\Support\Manager
基类,该基类为每个管理器提供了一些有用的公共功能。
扩展位置
本文件涵盖了如何扩展多种Laravel组件,但您可能想知道将扩展代码放在哪里。与大多数其他引导代码一样,您可以自由地将一些扩展放在您的start
文件中。缓存和认证扩展是这种方法的良好候选者。其他扩展,如Session
,必须放在服务提供者的register
方法中,因为它们在请求生命周期的早期就需要。
缓存
要扩展Laravel缓存功能,我们将使用CacheManager
上的extend
方法,该方法用于将自定义驱动程序解析器绑定到管理器,并且在所有管理器类中都是常见的。例如,要注册一个名为“mongo”的新缓存驱动程序,我们可以这样做:
Cache::extend('mongo', function($app)
{
// 返回Illuminate\Cache\Repository实例...
});
传递给extend
方法的第一个参数是驱动程序的名称。这将对应于app/config/cache.php
配置文件中的driver
选项。第二个参数是一个Closure,应该返回一个Illuminate\Cache\Repository
实例。Closure将传递一个$app
实例,该实例是Illuminate\Foundation\Application
和IoC容器的实例。
要创建我们的自定义缓存驱动程序,我们首先需要实现Illuminate\Cache\StoreInterface
契约。因此,我们的MongoDB缓存实现看起来像这样:
class MongoStore implements Illuminate\Cache\StoreInterface {
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连接实现每一个这些方法。一旦我们的实现完成,我们可以完成自定义驱动程序的注册:
use Illuminate\Cache\Repository;
Cache::extend('mongo', function($app)
{
return new Repository(new MongoStore);
});
如上例所示,您可以在创建自定义缓存驱动程序时使用基本的Illuminate\Cache\Repository
。通常不需要创建自己的存储库类。
如果您想知道将自定义缓存驱动程序代码放在哪里,可以考虑将其放在Packagist上!或者,您可以在应用程序的主文件夹中创建一个Extensions
命名空间。例如,如果应用程序名为Snappy
,您可以将缓存扩展放在app/Snappy/Extensions/MongoStore.php
中。但是,请记住,Laravel没有严格的应用程序结构,您可以根据自己的喜好组织应用程序。
如果您在考虑将一段代码放在哪里,请始终考虑服务提供者。正如我们所讨论的,使用服务提供者来组织框架扩展是组织代码的好方法。
会话
使用自定义会话驱动程序扩展Laravel与扩展缓存系统一样简单。同样,我们将使用extend
方法来注册我们的自定义代码:
Session::extend('mongo', function($app)
{
// 返回SessionHandlerInterface的实现
});
扩展会话的位置
会话扩展需要与缓存和认证等其他扩展以不同的方式注册。由于会话在请求生命周期的早期就开始,因此在start
文件中注册扩展将发生得太晚。相反,需要一个服务提供者。您应该将会话扩展代码放在服务提供者的register
方法中,并且该提供者应该放在providers
配置数组中低于默认的Illuminate\Session\SessionServiceProvider
。
编写会话扩展
请注意,我们的自定义会话驱动程序应该实现SessionHandlerInterface
。该接口包含在PHP 5.4+核心中。如果您使用的是PHP 5.3,Laravel将为您定义该接口,以便您具有向前兼容性。该接口包含我们需要实现的几个简单方法。一个简单的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;
});
一旦会话驱动程序注册完成,我们可以在app/config/session.php
配置文件中使用mongo
驱动程序。
请记住,如果您编写了自定义会话处理程序,请在Packagist上分享它!
认证
认证可以像缓存和会话功能一样扩展。同样,我们将使用我们已经熟悉的extend
方法:
Auth::extend('riak', function($app)
{
// 返回Illuminate\Auth\UserProviderInterface的实现
});
UserProviderInterface
的实现仅负责从持久存储系统(如MySQL、Riak等)中获取UserInterface
实现。这两个接口允许Laravel认证机制继续正常工作,无论用户数据如何存储或使用何种类型的类来表示它。
让我们看看UserProviderInterface
:
interface UserProviderInterface {
public function retrieveById($identifier);
public function retrieveByToken($identifier, $token);
public function updateRememberToken(UserInterface $user, $token);
public function retrieveByCredentials(array $credentials);
public function validateCredentials(UserInterface $user, array $credentials);
}
retrieveById
函数通常接收一个数字键,表示用户,例如来自MySQL数据库的自增ID。应检索并返回与该ID匹配的UserInterface
实现。
retrieveByToken
函数通过其唯一的$identifier
和“记住我”的$token
检索用户,存储在remember_token
字段中。与之前的方法一样,应返回UserInterface
实现。
updateRememberToken
方法使用新的$token
更新$user
字段remember_token
。新令牌可以是成功“记住我”登录尝试时分配的令牌,或者在用户注销时为null。
retrieveByCredentials
方法接收传递给Auth::attempt
方法的凭据数组,当尝试登录应用程序时。该方法应“查询”底层持久存储以获取与这些凭据匹配的用户。通常,此方法将运行一个带有$credentials['username']
的“where”条件的查询。此方法不应尝试进行任何密码验证或身份验证。
validateCredentials
方法应将给定的$user
与$credentials
进行比较,以验证用户。例如,此方法可能会将$user->getAuthPassword()
字符串与$credentials['password']
的Hash::make
进行比较。
现在我们已经探讨了UserProviderInterface
上的每个方法,让我们看看UserInterface
。请记住,提供者应从retrieveById
和retrieveByCredentials
方法返回此接口的实现:
interface UserInterface {
public function getAuthIdentifier();
public function getAuthPassword();
}
此接口很简单。getAuthIdentifier
方法应返回用户的“主键”。在MySQL后端中,这将是自增主键。getAuthPassword
应返回用户的哈希密码。此接口允许认证系统与任何用户类一起工作,无论您使用何种ORM或存储抽象层。默认情况下,Laravel在app/models
目录中包含一个实现此接口的User
类,因此您可以参考此类以获取实现示例。
最后,一旦我们实现了UserProviderInterface
,我们就准备好将我们的扩展与Auth
门面注册:
Auth::extend('riak', function($app)
{
return new RiakUserProvider($app['riak.connection']);
});
在使用extend
方法注册驱动程序后,您可以在app/config/auth.php
配置文件中切换到新驱动程序。
基于IoC的扩展
几乎所有随Laravel框架提供的服务提供者都会将对象绑定到IoC容器中。您可以在app/config/app.php
配置文件中找到应用程序的服务提供者列表。随着时间的推移,您应该浏览每个提供者的源代码。通过这样做,您将更好地了解每个提供者为框架添加了什么,以及用于将各种服务绑定到IoC容器的键。
例如,HashServiceProvider
将hash
键绑定到IoC容器,该键解析为Illuminate\Hashing\BcryptHasher
实例。您可以通过覆盖此IoC绑定轻松扩展和覆盖此类。例如:
class SnappyHashProvider extends Illuminate\Hashing\HashServiceProvider {
public function boot()
{
App::bindShared('hash', function()
{
return new Snappy\Hashing\ScryptHasher;
});
parent::boot();
}
}
请注意,此类扩展了HashServiceProvider
,而不是默认的ServiceProvider
基类。扩展服务提供者后,将HashServiceProvider
替换为app/config/app.php
配置文件中的扩展提供者名称。
这是扩展绑定在容器中的任何核心类的一般方法。实际上,几乎每个核心类都是以这种方式绑定在容器中的,并且可以被覆盖。同样,阅读包含的框架服务提供者将使您熟悉各种类是如何绑定到容器中的,以及它们是通过什么键绑定的。这是更深入了解Laravel如何构建的好方法。
请求扩展
由于它是框架的基础部分,并且在请求周期的早期就实例化,因此扩展Request
类的方式与之前的示例略有不同。
首先,像正常一样扩展类:
<?php namespace QuickBill\Extensions;
class Request extends \Illuminate\Http\Request {
// 自定义的便利方法...
}
扩展类后,打开bootstrap/start.php
文件。此文件是每次请求到达应用程序时包含的最早文件之一。请注意,执行的第一个操作是创建Laravel的$app
实例:
$app = new \Illuminate\Foundation\Application;
当创建新的应用程序实例时,它将创建一个新的Illuminate\Http\Request
实例,并使用request
键将其绑定到IoC容器。因此,我们需要一种方法来指定在创建“默认”请求类型时应使用的自定义类,对吧?幸运的是,应用程序实例上的requestClass
方法正是这样做的!因此,我们可以在bootstrap/start.php
文件的最顶部添加这一行:
use Illuminate\Foundation\Application;
Application::requestClass('QuickBill\Extensions\Request');
一旦您指定了自定义请求类,Laravel将在每次创建Request
实例时使用此类,方便您始终可以获得自定义请求类的实例,即使在单元测试中也是如此!