1. 前言
在 Laravel 框架中,Facade 使用的是抽象类,而 Contract 使用的就是接口。
接着上一篇 PHP 抽象类和接口的区别,我们看下 Laravel 中抽象类和接口的具体应用,并重新梳理一下服务容器(Service Container)、服务提供者(Service Provider)、Facade、Contract 之间的关系。
一个最直接的例子是为 Laravel 提供哈希处理的 Laravel Hashing 服务,实际上它只是对 Bcrypt 和 Argon2 的封装。
2. 定义 Contract
首先,Laravel 定义了一个接口,接口中声明了哈希处理用到三个方法:
Illuminate/Contracts/Hashing/Hasher.php
namespace Illuminate\Contracts\Hashing;
interface Hasher
{
/**
* Hash the given value.
*
* @param string $value
* @param array $options
* @return string
*/
public function make($value, array $options = []);
/**
* 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 = []);
/**
* 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 = []);
}
Contract 的主要目的是声明服务要实现的方法,规范接口。这样即使要修改或者替换方法内容,也不会对其它使用此功能的代码产生影响。
3. 实现 Contract
对于一个服务来说,可能会有多种不同的实现。哈希目前只有一个:
Illuminate/Hashing/BcryptHasher.php
namespace Illuminate\Hashing;
use RuntimeException;
use Illuminate\Contracts\Hashing\Hasher 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 = [])
{
$hash = password_hash($value, PASSWORD_BCRYPT, [
'cost' => $this->cost($options),
]);
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 = [])
{
if (strlen($hashedValue) === 0) {
return false;
}
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 = [])
{
return password_needs_rehash($hashedValue, PASSWORD_BCRYPT, [
'cost' => $this->cost($options),
]);
}
/**
* Set the default password work factor.
*
* @param int $rounds
* @return $this
*/
public function setRounds($rounds)
{
$this->rounds = (int) $rounds;
return $this;
}
/**
* Extract the cost value from the options array.
*
* @param array $options
* @return int
*/
protected function cost(array $options = [])
{
return $options['rounds'] ?? $this->rounds;
}
}
除了实现 Contracts 中定义的三个方法外,还是实现了额外的 setRounds
方法。
4. 添加到服务提供者
下一步就是将 Contract 的实现添加到服务提供者:
Illuminate/Hashing/HashServiceProvider.php
namespace Illuminate\Hashing;
use Illuminate\Support\ServiceProvider;
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 ['hash'];
}
}
上述代码中,应用会在服务容器中将 hash
绑定到 BcryptHasher
的单例上。
如果要延迟加载服务提供者,可以将 defer
属性设置为 true
,并定义 provides
方法。provides
方法返回注册到服务容器时提供的绑定。
5. 注册服务提供者
最后在应用启动时注册服务提供者。只需要将其添加到 config/app.php
文件的 providers
数组中即可。
现在,就可以从服务容器中解析并使用服务了:
$this->app->make('hash')->make('password');
$this->app['hash']->make('password');
如果无法访问 $app
变量,可以使用全局辅助函数 resolve
:
resolve('hash')->make('password');
或者在支持依赖注入的地方使用类型提示。
6. Facade
除了通过服务容器访问服务外,Laravel 还提供了另一种访问方式,就是使用 Facade。
哈希对应的 Facade 如下:
Illuminate/Support/Facades/Hash.php
namespace Illuminate\Support\Facades;
/**
* @see \Illuminate\Hashing\BcryptHasher
*/
class Hash extends Facade
{
/**
* Get the registered name of the component.
*
* @return string
*/
protected static function getFacadeAccessor()
{
return 'hash';
}
}
里面只有一个简单的方法 getFacadeAccessor
,用于返回注册到服务容器时提供的绑定,对应我们这里,就是服务提供者中注册时使用的 hash
。
所有 Facade 都继承自 Illuminate\Support\Facades\Facade
基类,如之前所述,此类是一个抽象类。当我们使用 Facade 时,它会从服务容器中解析出对应的 Contract 的实现,然后调用相应的方法。就像这样:
\Illuminate\Support\Facades\Hash::make('password');
当然,使用时明显感觉到类名长了点,不太方便。我们可以利用 PHP 的 class_alias
函数为类取个短点的别名。当然,在 Laravel 中,只需要将其添加到 config/app.php
的 aliases
数组中就可以了:
return [
.
.
.
'aliases' => [
.
.
.
'Hash' => Illuminate\Support\Facades\Hash::class,
.
.
.
],
.
.
.
];
最后,我们可以更方便地调用:
\Hash::make('password');
7. 总结
服务容器是 Laravel 的核心,Contract 定义了服务所需方法的列表,服务提供者将服务与具体实现绑定到服务容器中,而 Facade 提供了访问服务的另一种简单方法。