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類的實例。並使你很方便的能隨時訪問到它,甚至單元測試也不例外!

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