通过facade(尤其是realtime facade)来使代码更优雅

2018-02-05 10:46:12来源:segmentfault作者:pilishen人点击

分享

本文来自pilishen.com----原文链接


该篇翻译整理自laravel创始人Taylor的文章:Expressive Code & Real Time Facades,属于《Laravel底层核心技术实战揭秘》这一课程《laravel底层核心概念解析》这一章的扩展阅读。

laravel 5.4引入了realtime facade的功能,也即任何一个class都可以随时拿来当facade用,只要在其namespace前面加上Facades前缀即可。当然这个功能不可能随处都用到,但是偶尔呢,用它可以实现更简洁优雅、易于测试的代码方案。虽然下面的例子讲的是laravel 5.4的realtime facade,但是呢,其实也完全可以用在之前的版本上,因为所谓的realtime facade,无非就是系统自动给你注册成facade而已,鉴于这个功能又不可能到处用到,所以即使在老的版本里,如果你发现facade的这种代码实现方式更有吸引力,那么自己手动注册一个facade也完全可以的。


接下来的示例是关于Laravel Forge的,laravel Forge是laravel官方推出的laravel项目部署管理平台。当使用Forge的时候,你得在Forge后台将你服务器提供商的账号信息填上,然后呢交由Forge来具体管理。那么,这里假设呢我们有一个Model叫Provider,也就是对应着不同的主机提供商,比如国外的DigitalOcean、国内的阿里云等。


<?php
use App;
use Illuminate/Database/Eloquent/Model;
class Provider extends Model
{
//
}

这里呢假设我们将所有处理外来API请求的class放在App/Services文件夹下,我们得对应每一个主机供应商都有一个“service”class,假设DigitalOcean这家供应商的service class是这样的:


<?php
namespace App/Services;
use App/Contracts/ServerProvider;
class DigitalOcean implements ServerProvider
{
public function createServer($name, $size)
{
//
}
}

接下来呢,我们得能够解析这个服务类,基于我们model里的type这一栏的信息,我们可以使用工厂(factory)模式来实现:


<?php
namespace App/Services;
use InvalidArgumentException;class ServerProviderFactory
{
public function make($type)
{
switch ($type) {
case 'DigitalOcean':
return new DigitalOcean;
case 'Linode':
return new Linode;
default:
throw new InvalidArgumentException;
}
}
}

然后呢,我们就可以在需要的地方调用这个工厂,来相应地创建一个server 服务,比如假设在controller里调用:


<?php
namespace App/Http/Controllers;
use App/Provider;
use Illuminate/Http/Request;
use App/Services/ServerProviderFactory;class ServerController extends Controller
{
protected $factory;
public function __construct(ServerProviderFactory $factory)
{
$this->factory = $factory;
}
public function store(Request $request, Provider $provider)
{
$service = $this->factory->make($provider->type);
$response = $service->createServer($request->name, $request->size);
//
}
}

但是呢,我觉得这样还是有些繁琐,我想要是这样来用该多好呢?


<?php
namespace App/Http/Controllers;
use App/Provider;
use Illuminate/Http/Request;
class ServerController extends Controller
{
public function store(Request $request, Provider $provider)
{
$repsonse = $provider->service()->createServer(
$request->name, $request->size
);
//
}
}

我们只想简单地调用Provider这个实例上的service方法,然后就能获取到其背后对应的供应商,然后就能直接地createServer。这样来写呢,可能更像是我们日常中最直接的思考过程,虽然可能背后具体怎么实现你还没搞懂。那么怎么来实现呢?假设不借助facade,我们或许可以这样:


<?php
namespace App;
use App/Services/ServerProviderFactory;
use Illuminate/Database/Eloquent/Model;class Provider extends Model
{
public function service()
{
return (new ServerProviderFactory)->make($this->type);
}
}

貌似可行。但是这样呢,因为这个factory类是直接在service方法内部实例化的,这是不好的,后期我们无法用它来mock测试。那么如果用realtime facade的方式会怎么样呢?


<?php
namespace App;
use Illuminate/Database/Eloquent/Model;
use Facades/App/Services/ServerProviderFactory;class Provider extends Model
{
public function service()
{
return ServerProviderFactory::make($this->type);
}
}

现在,不仅看起来更简洁优雅,而且也可以测试了,因为facade可以进行mock,比如说这样:


<?php
namespace Tests/Feature;
use Mockery;
use App/Provider;
use Tests/TestCase;
use App/Contracts/ServerProvider;
use Facades/App/Services/ServerProviderFactory;
use Illuminate/Foundation/Testing/RefreshDatabase;class ExampleTest extends TestCase
{public function testBasicTest()
{
$provider = factory(Provider::class)->create([
'id' => 1,
'type' => 'DigitalOcean',
]);
$service = Mockery::mock(ServerProvider::class);
ServerProviderFactory::shouldReceive('make')
->with('DigitalOcean')
->andReturn($service);
$service->shouldReceive('createServer')
->once()
->with('web', '2GB')
->andReturn('server-id');
$response = $this->json('POST', '/api/providers/1/server', [
'name' => 'web',
'size' => '2GB',
]);
$response->assertStatus(201);
}
}

你会发现real-time facade最有用的地方就是构建简洁、优雅的object APIs,同时呢又不会影响到代码的可测试性。希望这能给你的实际开发带来一定启发。

最新文章

123

最新摄影

闪念基因

微信扫一扫

第七城市微信公众平台