Miles' Blog

天涯何處無幹話,何必要講實務話

Using Invocation Strategy

參考 v3.12.1

如同它的名稱,它實作了 Strategy Pattern。首先會有一個 InvocationStrategyInterface,它只定義一個方法。原始碼如下:

/**
* Invoke a route callable.
*
* @param callable $callable The callable to invoke using the strategy.
* @param ServerRequestInterface $request The request object.
* @param ResponseInterface $response The response object.
* @param array $routeArguments The route's placholder arguments
*
* @return ResponseInterface|string The response from the callable.
*/
public function __invoke(
callable $callable,
ServerRequestInterface $request,
ResponseInterface $response,
array $routeArguments
);

這裡的 $callable,是從存裡 Route 的 callable 解析出來的。因為有可能會這樣打:

$app->get('/', '\HomeController:home');

必須要從字串解析成 callable,當然這就會是另一個角色--CallableResolverInterface 的任務。而另外三個參數則是官方文件 Route callbacks 所提到的三個參數。

Slim 提供兩個預設實作,分別是 RequestResponseRequestResponseArgs。只是前者才是預設的行為,後者是可選的行為。這就是 Strategy Pattern 的優點,演算法是可以自由切換的,切換的方法後面會再描述。

它是如何被使用?

上面知道了介面與實作的關係,還有參數的用途,那它是什麼時候被使用的呢?使用 IDE 反查功能可以找到,是在 Route::invoke() 裡呼叫的:

$handler = isset($this->container) ? $this->container->get('foundHandler') : new RequestResponse();

這裡可以知道,Strategy 是可以透過 container 注入來替換的。

回頭看看實作

預設行為實作如下:

public function __invoke(
callable $callable,
ServerRequestInterface $request,
ResponseInterface $response,
array $routeArguments
) {
foreach ($routeArguments as $k => $v) {
$request = $request->withAttribute($k, $v);
}

return call_user_func($callable, $request, $response, $routeArguments);
}

中間的 foreach 先不看,先來比對官方的 GET 範例:

$app = new \Slim\App();
$app->get('/books/{id}', function ($request, $response, $args) {
// Show book identified by $args['id']
});

從這兩份片段原始碼可以了解,Route 的 callable 是如何被執行的。

這也是為什麼需要 $args 變數,即使 $request$response 變數都用不到,我們還是得三個都打上去才能正常運作。

客製化 Invocation Strategy

從上面的分析可以了解,如果我們想像 Laravel 一樣,傳入什麼 class 都自動注入,其實這在 Slim 的架構上可以很容易做到。

比方說我們可以改成這樣寫:

public function __invoke(
callable $callable,
ServerRequestInterface $request,
ResponseInterface $response,
array $routeArguments
) {
foreach ($routeArguments as $k => $v) {
$request = $request->withAttribute($k, $v);
}

return $this->container->call($callable, [$routeArguments]);
}

Route 定義就能改成如下:

$app = new \Slim\App();
$app->get('/books/{id}', function (Request $request, $args) {
// no response variable
});

References

0%