Extending The Framework 扩展框架

Extending The Framework 扩展框架

Introduction 介绍

Laravel offers many extension points for you to customize the behavior of the framework’s core components, or even replace them entirely. For example, the hashing facilites are defined by a HasherInterface contract, which you may implement based on your application’s requirements. You may also extend the Request object, allowing you to add your own convenient “helper” methods. You may even add entirely new authentication, cache, and session drivers!

为了方便你自定义框架核心组件,Laravel 提供了大量可以扩展的地方。你甚至可以完全替换掉旧组件。例如:哈希器遵守了HasherInterface接口,你可以按照你自己应用的需求来重新实现。你也可以扩展Request对象,添加你自己用的顺手的“helper”方法。你甚至可以添加全新的身份认证、缓存和会话机制!

Laravel components are generally extended in two ways: binding new implementations in the IoC container, or registering an extension with a Manager class, which are implementations of the “Factory” design pattern. In this chapter we’ll explore the various methods of extending the framework and examine the necessary code.

Laravel组件通常有两种扩展方式:在IoC容器里面绑定新实现,或者用Manager类注册一个扩展,该扩展采用了工厂模式实现。 在本章中我们将探索不同的扩展方式并检查我们都需要些什么代码。

Methods Of Extension 扩展方式

Remember, Laravel components are typically extended in one of two ways: IoC bindings and the Manager classes. The manager classes serve as an implementation of the “factory” design pattern, and are responsible for instantiating driver based facilities such as cache and session.

要记住Laravel通常有以下两种扩展方式:通过IoC绑定和通过Manager类(下文译作“管理类”)。其中管理类实现了工厂设计模式,负责组件的实例化。比如缓存和会话机制。

Manager & Factories 管理者和工厂

Laravel has several Manager classes that manage the creation of driver-based components. These include the cache, session, authentication, and queue components. The manager class is responsible for creating a particular driver implementation based on the application’s configuration. For example, the CacheManager class can create APC, Memcached, Native, and various other implementations of cache drivers.

Laravel有好多Manager类用来管理基于驱动的组件的生成过程。基于驱动的组件包括:缓存、会话、身份认证、队列组件等。管理类负责根据应用程序的配置,来生成特定的驱动实例。比如:CacheManager可以创建APC、Memcached、Native、还有其他不同的缓存驱动的实现。

Each of these managers includes an extend method which may be used to easily inject new driver resolution functionality into the manager. We’ll cover each of these managers below, with examples of how to inject custom driver support into each of them.

每个管理类都包含名为extend的方法,该方法可用于将新功能注入到管理类中。下面我们将逐个介绍管理类,为你展示如何注入自定义的驱动。

Learn About Your Managers 如何了解你的管理类

Take a moment to explore the various Manager classes that ship with Laravel, such as the CacheManager and SessionManager. Reading through these classes will give you a more thorough understanding of how Laravel works under the hood. All manager classes extend the Illuminate\Support\Manager base class, which provides some helpful, common functionality for each manager.

请花点时间看看Laravel中各个Manager类的代码,比如CacheManager和SessionManager。通过阅读这些代码能让你对Laravel的管理类机制更加清楚透彻。所有的管理类都继承自Illuminate\Support\Manager基类,该基类为每一个管理类提供了一些有效且通用的功能。

Cache 缓存

To extend the Laravel cache facility, we will use the extend method on the CacheManager, which is used to bind a custom driver resolver to the manager, and is common across all manager classes. For example, to register a new cache driver named “mongo”, we would do the following:

要扩展Laravel的缓存机制,我们将使用CacheManager里的extend方法来绑定我们自定义的缓存驱动。扩展其他的管理类也是类似的。比如,我们想注册一个新的缓存驱动,名叫“mongo”,代码可以这样写:

<!-- lang: php -->
Cache::extend('mongo', function($app)
{
    // Return Illuminate\Cache\Repository instance...
});

The first argument passed to the extend method is the name of the driver. This will correspond to your driveroption in the app/config/cache.php configuration file. The second argument is a Closure that should return an Illuminate\Cache\Repository instance. The Closure will be passed an $app instance, which is an instance of Illuminate\Foundation\Application and an IoC container.

extend方法的第一个参数是你要定义的驱动的名字。该名字对应着app/config/cache.php配置文件中的driver项。第二个参数是一个匿名函数(闭包),该匿名函数有一个$app参数是Illuminate\Foundation\Application的实例也是一个IoC容器,该匿名函数要返回一个Illuminate\Cache\Repository的实例。

To create our custom cache driver, we first need to implement the Illuminate\Cache\StoreInterface contract. So, our MongoDB cache implementation would look something like this:

要创建我们自己的缓存驱动,首先要实现Illuminate\Cache\StoreInterface接口。所以我们用MongoDB来实现的缓存驱动就可能看上去是这样:

<!-- lang:php -->
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() {}
}

We just need to implement each of these methods using a MongoDB connection. Once our implementation is complete, we can finish our custom driver registeration:

我们只需使用MongoDB链接来实现上面的每一个方法即可。一旦实现完毕,就可以照下面这样完成该驱动的注册:

<!-- lang:php -->
use Illuminate\Cache\Repository;
Cache::extend('mongo', function($app)
{
    return new Repository(new MongoStore);
}

As you can see in the example above, you may use the base Illuminate\Cache\Repository when creating custom cache drivers. These is typically no need to create your own repository class.

你可以像上面的例子那样来创建Illuminate\Cache\Repository的实例。也就是说通常你不需要创建你自己的仓库类(Repository)。

If you’re wondering where to put your custom cache driver code, consider making it available on Packagist! Or, you could create an Extensions namespace within your application’s primary folder. For example, if the application is named Snappy, you could place the cache extension in app/Snappy/Extensions/MongoStore.php. However, keep in mind that Laravel doesn not have a rigid application structure and you are free to organize your application according to your preferences.

如果你不知道要把自定义的缓存驱动代码放到哪儿,可以考虑放到Packagist里!或者你也可以在你应用的主目录下创建一个Extensions目录。比如,你的应用叫做Snappy,你可以将缓存扩展代码放到app/Snappy/Extensions/MongoStore.php。不过请记住Laravel没有对应用程序的结构做硬性规定,所以你可以按任意你喜欢的方式组织你的代码。

Where To Extend 在哪儿调用Extend方法?

If you’re ever wondering where to put a piece of code, always consider a service provider. As we’ve discussed, using a service provider to organize framework extensions is a great way to organize your code.

如果你还发愁在哪儿放注册代码,先考虑放到服务提供者里吧。我们之前就讲过,使用服务提供者是一种非常棒的管理你应用代码的途径。

Session 会话

Extending Laravel with a custom session driver is just as easy as extending the cache system. Again, we will use the extend method to register our custom code:

扩展Laravel的会话机制和上文的缓存机制一样简单。和刚才一样,我们使用extend方法来注册自定义的代码:

<!-- lang:php -->
Session::extend('mongo', function($app)
{
    // Return implementation of SessionHandlerInterface
});

Note that our custom cache driver should implement the SessionHandlerInterface. This interface is included in the PHP 5.4+ core. If you are using PHP 5.3, the interface will be defined for you by Laravel so you have forward-compatibility. This interface contains just a few simple methods we need to implement. A stubbed MongoDB implementation would look something like this:

注意我们自定义的会话驱动(译者注:原味是cache driver,应该是笔误。正确应为session driver)实现的是SessionHandlerInterface接口。这个接口在PHP 5.4以上版本才有。但如果你用的是PHP 5.3也别担心,Laravel会自动帮你定义这个接口的。该接口要实现的方法不多也不难。我们用MongoDB来实现就像下面这样:

<!-- lang: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) {}
}

Since these methods are not as readily understandable as the cache StoreInterface, let’s quickly cover what each of the methods do:

这些方法不像刚才的StoreInterface接口定义的那么容易理解。我们来挨个简单讲讲这些方法都是干啥的:

  • The open method would typically be used in file based session store system. Since Laravel ships with a native session driver that uses PHP’s native file storage for sessions, you will almost never need to put anything in this method. You can leave it as an empty stub. It is simply a fact of poor interface design (which we’ll discuss later) that PHP requires us to implement this method.
  • open方法一般在基于文件的会话系统中才会用到。Laravel已经自带了一个native的会话驱动,使用的就是PHP自带的基于文件的会话系统,你可能永远也不需要在这个方法里写东西。所以留空就好。另外这也是一个接口设计的反面教材(稍后我们会继续讨论这一点)。
  • The close method, like the open method, can also usually be disregarded. For most drivers, it is not needed.
  • close方法和open方法通常都不是必需的。对大部分驱动来说都不必要实现。
  • The read method should return the string version of the session data associated with the given $sessionId. There is no need to do any serialization or other encoding when retrieving or storing session data in your driver, as Laravel will perform the serialization for you.
  • read方法应该根据$sessionId参数来返回对应的会话数据的字符串形式。在你的会话驱动里,不论读写都不需要做任何数据序列化工作。因为Laravel会负责数据序列化的。
  • The write method should write the given datastringassociatedwiththe sessionId to some persistent storage system, such as MongoDB, Dynamo, etc.
  • write方法应该将sessionId data字符串放置在一个持久化存储系统中。比如MongoDB,Dynamo等等。
  • The destroy method should remove the data associated with the $sessionId from persistent storage.
  • destroy方法应该将$sessionId对应的数据从持久化存储系统中删除。
  • The gc method should destroy all session data that is older than the given $lifetime, which is a UNIX timestamp. For self-expiring systems like Memcached and Redis, this method may be left empty.
  • gc方法应该将所有时间超过参数$lifetime的数据全都删除,该参数是一个UNIX时间戳。如果你使用的是类似Memcached或Redis这种有自主到期功能的存储系统,那该方法可以留空。

  • Once the SessionHandlerInterface has been implemented, we are ready to register it with the Session manager:

一旦SessionHandlerInterface实现完毕,我们就可以将其注册进会话管理器:

<!-- lang:php -->
Session::extend('mongo', function($app)
{
    return new MongoHandler;
});

Once the session driver has been registered, we may use the mongo driver in our app/config/session.php configuration file.

注册完毕后,我们就可以在app/config/session.php配置文件里使用mongo驱动了。

Share Your Knowledge 分享你的知识

Remember, if you write a custom session handler, share it on Packagist!

你要是写了个自定义的会话处理器,别忘了在Packagist上分享啊!

Authentication 身份认证

Authentication may be extended the same way as the cache and session facilities. Again, we will use the extendmethod we have become familiar with:

身份认证模块的扩展方式和缓存与会话的扩展方式一样:使用我们熟悉的extend方法就可以进行扩展:

<!-- lang: php -->
Auth::extend('riak', function($app)
{
    // Return implementation of Illuminate\Auth\UserProviderInterface
});

The UserProviderInterface implementations are only responsible for fetching a UserInterface implementation out of persistent storage system, such as MySQL, Riak, etc. These two interfaces allow the Laravel authentication mechanisms to continue functioning regardless of how the user data is stored or what type of class is used to represent it.

接口UserProviderInterface负责从各种持久化存储系统——如MySQL,Riak等——中获取数据,然后得到接口UserInterface的实现对象。有了这两个接口,Laravel的身份认证机制就可以不用管用户数据是如何储存的、究竟哪个类来代表用户对象这种事儿,从而继续专注于身份认证本身的实现。

Let’s take a look at the UserProviderInterface:

咱们来看一看UserProviderInterface接口的代码:

<!-- lang:php -->
interface UserProviderInterface {
    public function retrieveById($identifier);
    public function retrieveByCredentials(array $credentials);
    public function validateCredentials(UserInterface $user, array $credentials);
}

The retrieveById function typically receives a numeric key representing the user, such as an auto-incrementing ID from a MySQL database. The UserInterface implementation matching the ID should be retrieved and returned by the method.

方法retrieveById通常接受一个数字参数用来表示一个用户,比如MySQL数据库的自增ID。该方法要找到匹配该ID的UserInterface的实现对象,并且将该对象返回。

The retrieveByCredentials method receives the array of credentials passed to the Auth::attempt method when attempting to sign into an application. The method should then “query” the underlying persistent storage for the user matching those credentials. Typically, this method will run a query with a “where” condition on $credentials[‘username’]. This method should not attempt to do any password validation or authentication.

retrieveByCredentials方法接受一个参数作为登录帐号。该参数是在尝试登录系统时从Auth::attempt方法传来的。那么该方法应该“查询”底层的持久化存储系统,来找到那些匹配到该帐号的用户。通常该方法会执行一个带有“where”条件的查询来匹配参数里的$credentials[‘username’]。该方法不应该做任何密码验证。

The validateCredentials method should compare the given userwiththe credentials to authenticate the user. For example, this method might compare the user>getAuthPassword();stringtoaHash::makeof credentials[‘password’].

validateCredentials方法会通过比较user credentials参数来检测用户是否通过认证。比如,该方法会调用user>getAuthPassword(); credentials[‘password’]经过Hash::make处理后的结果进行比对。

Now that we have explored each of the methods on the UserProviderInterface, let’s take a look at the UserInterface. Remember, the provider should return implementations of this interface from the retrieveById and retrieveByCredentials methods:

现在我们探索了UserProviderInterface接口的每一个方法,接下来咱们看一看UserInterface接口。别忘了UserInterface的实例应当是retrieveById和retrieveByCredentials方法的返回值:

<!-- lang:php -->
interface UserInterface {
    public function getAuthIdentifier();
    public function getAuthPassword();
}

This interface is simple. The getAuthIdentifier method should return the “primary key” of the user. In a MySQL back-end, again, this would be the auto-incrementing primary key. The getAuthPassword should return the user’s hashed password. This interface allows the authentication system to work with any User class, regardless of what ORM or storage abstraction layer you are using. By default, Laravel includes a User class in the app/modelsdirectory which implements this interface, so you may consult this class for an implementation example.

这个接口很简单。
getAuthIdentifier方法应当返回用户的“主键”。就像刚才提到的,在MySQL中可能就是自增主键了。getAuthPassword方法应当返回经过散列处理的用户密码。有了这个接口,身份认证系统就可以不用关心用户类到底使用了什么ORM或者什么存储方式。Laravel已经在app/models目录下,包含了一个默认的User类且实现了该接口。所以你可以参考这个类当例子。

Finally, once we have implemented the UserProviderInterface, we can ready to register our extension with the Authfacade:

当我们最后实现了UserProviderInterface接口后,我们可以将该扩展注册进Auth里面:

<!-- lang:php -->
Auth::extend('riak', function($app)
{
    return new RiakUserProvider($app['riak.connection']);
});

After you have registered the driver with the extend method, you switch to the new driver in your app/config/auth.php configuration file.

使用extend方法注册好驱动以后,你就可以在app/config/auth.php配置文件里面切换到新的驱动了。

IoC Based Extension 使用容器进行扩展

Almost every service provider included with the Laravel framework binds objects into the IoC container. You can find a list of your application’s service providers in the app/config/app.php configuration file. As you have time, you should skim through each of these provider’s source code. By doing so, you will gain a much better understanding of what each providers adds to the framework, as well as that keys are used to bind various services into the IoC container.

Laravel框架内几乎所有的服务提供者都会绑定一些对象到IoC容器里。你可以在app/config/app.php文件里找到服务提供者列表。如果你有时间的话,你应该大致过一遍每个服务提供者的源码。这么做你便可以对每个服务提供者有更深的理解,明白他们都往框架里加了什么东西,对应的什么键。那些键就用来联系着各种各样的服务。

For example, the PaginationServiceProvider binds a paginator key into the IoC container, which resolves into Illuminate\Pagination\Environment instance. You can easily extend and override this class within your own application by overriding this IoC binding. For example, you could create a class that extend the base Environment:

举个例子,PaginationServiceProvider向容器内绑定了一个paginator键,对应着一个Illuminate\Pagination\Environment的实例。你可以很容易的通过覆盖容器绑定来扩展重写该类。比如,你可以创建一个扩展自Environment类的子类:

<!-- lang:php -->
namespace Snappy\Extensions\Pagination;
class Environment extends \Illuminate\Pagination\Environment {
    //
}

Once you have created your class extension, you may create a new SnappyPaginationProvider service provider class which overrides the paginator in its boot method:

子类写好以后,你可以再创建个新的SnappyPaginationProvider服务提供者来扩展其boot方法,在里面覆盖paginator:

<!-- lang:php -->
class SnappyPaginationProvider extends PaginationServiceProvider {
    public function boot()
    {
        App::bind('paginator', function()
        {
            return new Snappy\Extensions\Pagination\Environment;
        }

        parent::boot();
    }
}

Note that this class extends the PaginationServiceProvider, not the default ServiceProvider base class. Once you have extended the service provider, swap out the PaginationServiceProvider in your app/config/app.php configuration file with the name of your extended provider.

注意这里我们继承了PaginationServiceProvider,而非默认的基类ServiceProvider。扩展的服务提供者编写完毕后,就可以在app/config/app.php文件里将PaginationServiceProvider替换为你刚扩展的那个类了。

This is the general method of extending any core class that is bound in the container. Essentially every core class is bound in the container in this fashion, and can be overridden. Again, reading through the included framework service providers will familiarize you with where various classes are bound into the container, and what keys they are bound by. This is a great way to learn more about how Laravel is put together.

这就是扩展绑定进容器的核心类的一般方法。基本上每一个核心类都以这种方式绑定进了容器,都可以被重写。还是那一句话,读一遍框架内的服务提供者源码吧。这有助于你熟悉各种类是怎么绑定进容器的,都绑定的是哪些键。这是学习Laravel框架到底如何运转的好方法。

Request Extension 请求的扩展

Because it is such a foundational piece of the framework and is instantiated very early in the request cycle, extending the Request class works a little differently than the previous examples.

由于这玩意儿是框架里面非常基础的部分,并且在请求流程中很早就被实例化,所以要扩展Request类的方法与之前相比是有些许不同的。

First, extend the class like normal:

首先还是要写个子类:

<!-- lang:php -->
namespace QuickBill\Extensions;
class Request extends \Illuminate\Http\Request {
    // Custom, helpful methods here...
}

Once you have extended the class, open the bootstrap/start.php file. This file is one of the very first files to be included on each request to your application. Note that the first action performed is the creation of the Laravel $app instance:

子类写好后,打开bootstrap/start.php文件。该文件是应用的请求流程中最早被载入的几个文件之一。要注意被执行的第一个动作是创建Laravel的$app实例:

<!-- lang:php -->
$app = new \Illuminate\Foundation\Application;

When a new application instance is created, it will create a new Illuminate\Http\Request instance and bind it to the IoC container using the request key. So, we need a way to specify a custom class that should be used as the “default” request type, right? And, thankfully, the requestClass method on the application instance does just this! So, we can add this line at the very top of our bootstrap/start.php file:

当新的应用实例创建后,它将会创建一个Illuminate\Http\Request的实例并且将其绑定到IoC容器里,键名为request。所以我们需要找个方法来将一个自定义的类指定为“默认的”请求类,对不对?而且幸运的是,应用实例有一个名为requestClass的方法就是用来干这事儿的!所以我们只需要在bootstrap/start.php文件最上面加一行:

<!-- lang:php -->
use Illuminate\Foundation\Application;
Application::requestClass('QuickBill\Extensions\Request');

Once you have specified the custom request class, Laravel will use this class anytime it creates a Requestinstance, conveniently allowing you to always have an instance of your custom request class available, even in unit test!

一旦你指定了自定义的请求类,Laravel将在任何时候都可以使用这个Request类的实例。并使你很方便的能随时访问到它,甚至单元测试也不例外!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章