Facades和Contract是Laravel中的两个架构概念。它们可以帮助你在应用程序中实例化一个对象,降低应用程序的耦合程度。大多情况下,每个Facades都有一个与之对应的Contract。但他们的表现形式却大相径庭。
在阅读此文章之前,你需要对Laravel的服务容器(Service container)和服务供应商(service provider)有一定的了解。
Facades
Facades提供了一个静态接口,接口用于获取服务容器中可用的类。可以将其理解为一个基础类类的静态代理。同传统的静态方法相比,它的语法更加简洁,更具有表现力,同时,保持了更高的可测试性和灵活性。
使用场景
Facades提供了简洁、易于表达的语法。我们无需进行依赖注入,手动输入较长的类名。因为使用了PHP的__callStatic动态方法,它们也非常易于测试。 所以,什么时候使用它,取决于你的品味。
创建一个Facades
我们以创建一个分页的Facades为例。首先,我们在app/Providers/AppServiceProvider 中将Page字符串绑定一个类\App\Foundation\Page。
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
//
$this->app->bind('Page', function () {
return new \App\Foundation\Page;
});
}
然后在创建一个类\App\Foundation\Page。
<?php
namespace App\Foundation;
class Page{
/**
* 获取分页数据
*
* @param Illuminate\Database\Query\Builder $query
* @param int limit 一页显示的记录条数
* @param int showPages 显示的页数
* @return array
*/
public function paginate($query, int $limit = 20, int $showPages = 10) {
}
}
最后,我们在项目中创建一个文件 app/Facades/Page.php,内容如下。
<?php
namespace App\Facades;
use Illuminate\Support\Facades\Facade;
/**
* 分页工具类
*
* @method static array paginate ($query, int $limit = 20, int $showPages = 10)
*/
class Page extends Facade{
protected static function getFacadeAccessor(){
return 'Page';
}
}
Page Facade继承了基础的Facade类,并定义了一个静态方法getFacadeAccessor()。该方法返回了之前在AppServiceProvider中绑定\App\Foundation\Page 类的名称 Page。
这样我们的Facads就已经创建完成了。你可以通过Page Facade的静态方法,访问\App\Foundation\Page类中的方法。
<?php
namespace App\Repository\Home;
use App\Facades\Page;
class Article implements RepositoryArticle
{
$articles = Page::paginate($articleQuery, $request->input('limit', 10));
}
Contract
Laravel的contract是定义框架核心服务的接口。每一个Contract都有一个实现这个接口的类。
使用场景
我们先看一看下面的代码。
<?php
namespace App\Article;
class Repository
{
/**
* The cache instance.
*/
protected $cache;
/**
* Create a new repository instance.
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(\SomePackage\Cache\Memcached $cache)
{
$this->cache = $cache;
}
/**
* Retrieve an Order by ID.
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
在这个类中,使用了Memcache缓存了相关的数据。设想一下,如果我们想用Redis代替Memcache。我们又要再次修改这个类。再从设计的角度考虑一下,Repository无需了解使用什么样引擎缓存的数据,它需要做的只是缓存数据,和从缓存中取出数据。
根据以上思路,我们改善一下代码
<?php
namespace App\Article;
use Illuminate\Contracts\Cache\Repository as Cache;
class Repository
{
/**
* The cache instance.
*/
protected $cache;
/**
* Create a new repository instance.
*
* @param \SomePackage\Cache\Memcached $cache
* @return void
*/
public function __construct(Cache $cache)
{
$this->cache = $cache;
}
/**
* Retrieve an Order by ID.
*
* @param int $id
* @return Order
*/
public function find($id)
{
if ($this->cache->has($id)) {
//
}
}
}
因为Illuminate\Contracts\Cache\Repository是一个接口,所以Repository类不再依赖某一个特定的类,降低了程序的耦合程度。我们只需要再服务容器中指定接口的具体实现类即可。
use Illuminate\Contracts\Cache\Repository as Cache
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind('Cache', function () {
return new \SomePackage\Cache\Memcached;
// return \SomePackage\Cache\Redis
});
}
创建一个contract
我们以Cache为例,首先定义一个接口App\Contract\Repository\Cache。
<?php
namespace App\Contract\Repository;
interface Cache{
public function find ($id);
public function all() : array;
}
然后实现接口App\Contract\Repository\Cache。
<?php
namespace App\Foundation;
use App\Contract\Repository\Cache as Cache;
class RedisCache implements Cache {
public function find ($id) {
// do something
}
public function all () : array {
// do something
}
}
最后在服务容器中绑定即可。
<?php
namespace App\Providers;
use App\Foundation\RedisCache as RedisCache;
use use App\Contract\Repository\Cache as Cache;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* @return void
*/
public function register()
{
$this->app->bind(Cache, function ($app) {
return $app->make(RedisCache::class);
});
}
}
总结
- 不像Facades无需在造函数中指定类的引用,Contract要求你必须这样做,然后服务容器在创建实例的时候,会进行依赖注入,实例化构造函数 中指定的依赖。
- 关于facades和contract的选择,和何时使用它们,完全取决于团队或是个人的喜好。
- 在编写第三方库的时候,推荐使用contract。因为composer包是在laravel项目之外创建的,你无法使用laravel facades的测试助手。