初识Laravel Facades和Contract

没头脑

作者 没头脑

创建时间 2021-03-03

更新时间 2021-05-18

阅读 263

评论 0

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的测试助手。
提 交
暂无评论