Laravel 5 系统架构:服务提供者、服务容器、Contracts、Facades

官方文档在一方面真的写的很糟糕,完全没描述相互之间的关系。

事实上,服务提供者(Service Provider)服务容器(Service Container)ContractsFacades 是一件东西的多个方面。其实就是 Yii 的组件(Component)。

和 Yii 一样,Laravel 的所有功能都是通过与 Yii 组件类似的服务架构来实现的。

一个最简单直接的例子是 Lavarel Hashing 服务。其自身只是一个 Bcrypt hash 的封装。

首先,Laravel 5 定义了一个 Contracts:Illuminate/Contracts/Hashing/Hasher.php

<?php namespace IlluminateContractsHashing;
interface Hasher {
    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array   $options
     * @return string
     */
    public function make($value, array $options = array());
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = array());
    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = array());
}

Contracts 只是描述一个服务的具体方法,而不是实现该服务。Container 才是服务的具体实现,对于一个服务来说,可能会有多种不同的实现。Hash 目前只有一个,就是:Illuminate/Hashing/BcryptHasher.php

<?php namespace IlluminateHashing;
use RuntimeException;
use IlluminateContractsHashingHasher as HasherContract;
class BcryptHasher implements HasherContract {
    /**
     * Default crypt cost factor.
     *
     * @var int
     */
    protected $rounds = 10;
    /**
     * Hash the given value.
     *
     * @param  string  $value
     * @param  array   $options
     * @return string
     *
     * @throws RuntimeException
     */
    public function make($value, array $options = array())
    {
        $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
        $hash = password_hash($value, PASSWORD_BCRYPT, array('cost' => $cost));
        if ($hash === false)
        {
            throw new RuntimeException("Bcrypt hashing not supported.");
        }
        return $hash;
    }
    /**
     * Check the given plain value against a hash.
     *
     * @param  string  $value
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function check($value, $hashedValue, array $options = array())
    {
        return password_verify($value, $hashedValue);
    }
    /**
     * Check if the given hash has been hashed using the given options.
     *
     * @param  string  $hashedValue
     * @param  array   $options
     * @return bool
     */
    public function needsRehash($hashedValue, array $options = array())
    {
        $cost = isset($options['rounds']) ? $options['rounds'] : $this->rounds;
        return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, array('cost' => $cost));
    }
    /**
     * Set the default password work factor.
     *
     * @param  int  $rounds
     * @return $this
     */
    public function setRounds($rounds)
    {
        $this->rounds = (int) $rounds;
        return $this;
    }
}

除了实现 Contracts 中定义的三个方法外,还实现了一个额外 setRounds() 方法。

下一步就是注册为服务提供者了:Illuminate/Hashing/HashServiceProvider.php

<?php namespace IlluminateHashing;
use IlluminateSupportServiceProvider;
class HashServiceProvider extends ServiceProvider {
    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;
    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('hash', function() { return new BcryptHasher; });
    }
    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return array('hash');
    }
}

很明显的 register 了服务,具体做的就是 new 了一个 Container。如果容器有多种实现,这时建议使用 Config 来配置服务了。

$this->app->singleton('hash', function($app) { return new Hasher($app['config']['hash']); });

这时候 Hasher 实际上一个工厂类(Factory),使用配置来确定具体的,如 new Hasher('bcrypt') 来实例化。

如果服务要延迟载入,也就是按需载入。需要有一个激活标志,也就是 provides() 方法。

当然,HashServiceProvider.php 必须在 config/app.phpproviders 中定义 'IlluminateHashingHashServiceProvider' 行。

这时,就可以使用:

$this->app->make('hash')->make('password');
$this->app['hash']->make('password');

最后,声明一个 Facade:Illuminate/Support/Facades/Hash.php

<?php namespace IlluminateSupportFacades;
/**
 * @see IlluminateHashingBcryptHasher
 */
class Hash extends Facade {
    /**
     * Get the registered name of the component.
     *
     * @return string
     */
    protected static function getFacadeAccessor()
    {
        return 'hash';
    }
}

config/app.phpaliases 中定义 'Hash' => 'IlluminateSupportFacadesHash' 行。

那么,就不用上面长长的 $this->app->make('hash'),直接:

Hash::make('password');

3 Responses to “Laravel 5 系统架构:服务提供者、服务容器、Contracts、Facades”

  1. zen说道:
    Google Chrome 42.0.2311.90 Mac OS X  10.9.5
    Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/42.0.2311.90 Safari/537.36

    多谢作者的讲解,正好解决了我这方面的疑惑。
    不过现在还在思考这样实现对于实际项目中,会不会意义大于效果呢?

    • 风起说道:
      Google Chrome 49.0.2623.75 Mac OS X  10.11.3
      Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.75 Safari/537.36

      这个的意义在开源项目和自用项目的意义是不同的.
      这里一个是为了规范协作的开发者(Contracts),
      还有就是为了让有需要扩展的人更方便替或者扩展换系统组件(ServiceProvider),
      最后就是为了统一调用方式(Facade)了, 你会发现所有的组件不管是自开发还是第三方, 调用方式都是那么的统一: Component::function(param)

  2. rocky说道:
    Google Chrome 41.0.2272.118 Windows 7
    Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36

    一次机会看到你的blog。深圳南山科技园软件企业,可以相互认识下,QQ1301868201,加Q时注明 “南靖男的时代”

发表评论

%d 博主赞过: