Laravel 5 系统架构:服务提供者、服务容器、Contracts、Facades
官方文档在一方面真的写的很糟糕,完全没描述相互之间的关系。
事实上,服务提供者(Service Provider)、服务容器(Service Container)、Contracts、Facades 是一件东西的多个方面。其实就是 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.php
的 providers
中定义 '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.php
的 aliases
中定义 'Hash' => 'IlluminateSupportFacadesHash'
行。
那么,就不用上面长长的 $this->app->make('hash')
,直接:
Hash::make('password'); |
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
多谢作者的讲解,正好解决了我这方面的疑惑。
不过现在还在思考这样实现对于实际项目中,会不会意义大于效果呢?
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)
Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2272.118 Safari/537.36
一次机会看到你的blog。深圳南山科技园软件企业,可以相互认识下,QQ1301868201,加Q时注明 “南靖男的时代”