本文來自pilishen.com—-原文連結; 歡迎作客我們的php&Laravel學習群:109256050
該篇翻譯整理自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 IlluminateDatabaseEloquentModel;
class Provider extends Model
{
//
}
這裡呢假設我們將所有處理外來API請求的class放在AppServices
資料夾下,我們得對應每一個主機供應商都有一個“service”class,假設DigitalOcean這家供應商的service class是這樣的:
<?php
namespace AppServices;
use AppContractsServerProvider;
class DigitalOcean implements ServerProvider
{
public function createServer($name, $size)
{
//
}
}
接下來呢,我們得能夠解析這個服務類,基於我們model裡的type這一欄的資訊,我們可以使用工廠(factory)模式來實現:
<?php
namespace AppServices;
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 AppHttpControllers;
use AppProvider;
use IlluminateHttpRequest;
use AppServicesServerProviderFactory;
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 AppHttpControllers;
use AppProvider;
use IlluminateHttpRequest;
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 AppServicesServerProviderFactory;
use IlluminateDatabaseEloquentModel;
class Provider extends Model
{
public function service()
{
return (new ServerProviderFactory)->make($this->type);
}
}
貌似可行。但是這樣呢,因為這個factory類是直接在service
方法內部例項化的,這是不好的,後期我們無法用它來mock測試。那麼如果用realtime facade的方式會怎麼樣呢?
<?php
namespace App;
use IlluminateDatabaseEloquentModel;
use FacadesAppServicesServerProviderFactory;
class Provider extends Model
{
public function service()
{
return ServerProviderFactory::make($this->type);
}
}
現在,不僅看起來更簡潔優雅,而且也可以測試了,因為facade可以進行mock,比如說這樣:
<?php
namespace TestsFeature;
use Mockery;
use AppProvider;
use TestsTestCase;
use AppContractsServerProvider;
use FacadesAppServicesServerProviderFactory;
use IlluminateFoundationTestingRefreshDatabase;
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,同時呢又不會影響到程式碼的可測試性。希望這能給你的實際開發帶來一定啟發。