路由

Slim Framework 的路由建立在 Fast Route 組件之上,速度快且穩定。即使我們使用此組件來進行所有路由,應用程式的核心已完全與其分離,並已放入介面來為使用其他路由函式庫鋪路。

如何建立路由

您可以在 Slim\App 執行個體上使用代理方法來定義應用程式路由。Slim Framework 提供最熱門的 HTTP 方法的使用方式。

GET 路由

您可以新增一條路由,此路由只處理 GET HTTP 請求,方法是使用 Slim 應用程式 get() 方法。它接受兩個引數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->get('/books/{id}', function ($request, $response, array $args) {
    // Show book identified by $args['id']
});

POST 路由

您可以新增一條路由,此路由只處理 POST HTTP 請求,方法是使用 Slim 應用程式 post() 方法。它接受兩個引數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->post('/books', function ($request, $response, array $args) {
    // Create new book
});

PUT 路由

您可以新增一條路由,此路由只處理 PUT HTTP 請求,方法是使用 Slim 應用程式 put() 方法。它接受兩個引數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->put('/books/{id}', function ($request, $response, array $args) {
    // Update book identified by $args['id']
});

DELETE 路由

您可以使用 Slim 應用程式裡的 delete() 方法,來新增只處理 DELETE HTTP 要求的路由。它接受兩個參數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->delete('/books/{id}', function ($request, $response, array $args) {
    // Delete book identified by $args['id']
});

OPTIONS 路由

您可以使用 Slim 應用程式裡的 options() 方法,來新增只處理 OPTIONS HTTP 要求的路由。它接受兩個參數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->options('/books/{id}', function ($request, $response, array $args) {
    // Return response headers
});

PATCH 路由

您可以使用 Slim 應用程式裡的 patch() 方法,來新增只處理 PATCH HTTP 要求的路由。它接受兩個參數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->patch('/books/{id}', function ($request, $response, array $args) {
    // Apply changes to book identified by $args['id']
});

任何路由

您可以使用 Slim 應用程式裡的 any() 方法,來新增處理所有 HTTP 要求方法的路由。它接受兩個參數

  1. 路由模式(可選擇指定名稱的佔位符)
  2. 路由回呼
$app->any('/books/[{id}]', function ($request, $response, array $args) {
    // Apply changes to books or book identified by $args['id'] if specified.
    // To check which method is used: $request->getMethod();
});

請注意第二個參數是回呼。您可以指定實作 __invoke() 方法的類別,而不是用 Closure。然後您可以在其他地方執行對應

$app->any('/user', 'MyRestfulController');

自訂路由

您可以使用 Slim 應用程式裡的 map() 方法來新增處理多個 HTTP 要求方法的路由。它接受三個參數

  1. HTTP 方法陣列
  2. 路由模式(可選擇指定名稱的佔位符)
  3. 路由回呼
$app->map(['GET', 'POST'], '/books', function ($request, $response, array $args) {
    // Create new book or list all books
});

路由回呼

上面描述的每個路由方法都接受回呼例程作為最後一個參數。此參數可以是任何 PHP 可呼叫的,且預設情況下它接受三個參數。

  • 要求 第一個參數是表示目前 HTTP 要求的 Psr\Http\Message\ServerRequestInterface 物件。
  • 回應 第二個參數是表示目前 HTTP 回應的 Psr\Http\Message\ResponseInterface 物件。
  • 引數 第三個參數是包含目前路徑命名佔位符值的關聯式陣列。

撰寫內容到回應

有兩種方式可以撰寫內容到 HTTP 回應

  1. 使用 Response 物件的 $response->getBody()->write('我的內容'); 方法。

  2. 只要從路由回呼 echo() 內容。如果您新增 輸出緩衝 Middleware,此內容將會附加或置於目前的 HTTP 回應物件之前。

請注意,自從 Slim 4 之後,您必須傳回 Psr\Http\Message\ResponseInterface 物件。

Closure 結束

如果你使用 相依性容器,並使用 Closure 實體作為路徑回呼函式,這個 closure 的狀態會繫結到 Container 實體。這表示你將可以透過 $this 關鍵字,在 Closure 內部 存取 DI 容器實體

$app->get('/hello/{name}', function ($request, $response, array $args) {
    // Use app HTTP cookie service
    $this->get('cookies')->set('name', [
        'value' => $args['name'],
        'expires' => '7 days'
    ]);
});
注意!

Slim 不支援 static closures。

重新導向助手

你可以新增一條路徑,使用 Slim 應用程式的 redirect() 方法,將 GET HTTP 要求重新導向到另一個 URL。它接受三個參數

  1. 路徑模式(搭配選用的命名佔位符)從重新導向
  2. 重新導向 位置,可以是 字串,也可以是 Psr\Http\Message\UriInterface
  3. 使用的 HTTP 狀態碼(選用;如果未設定,會使用 302
$app->redirect('/books', '/library', 301);

redirect() 路徑會回應要求的狀態碼,並將 Location 標頭設定為第二個參數。

路徑策略

路徑回呼函式的簽章是由路徑策略決定的。預設上,Slim 預期路徑回呼函式要接受要求、回應和一組路徑佔位符參數。這稱為 RequestResponse 策略。不過,你只要使用另一種策略,就能變更預期的路徑回呼函式簽章。舉例來說,Slim 提供了稱為 RequestResponseArgs 的另類策略,這個策略會接受要求和回應,並將各條路徑佔位符當成個別的參數。

以下是使用這個另類策略的一個範例

<?php
use Slim\Factory\AppFactory;
use Slim\Handlers\Strategies\RequestResponseArgs;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * Changing the default invocation strategy on the RouteCollector component
 * will change it for every route being defined after this change being applied
 */
$routeCollector = $app->getRouteCollector();
$routeCollector->setDefaultInvocationStrategy(new RequestResponseArgs());

$app->get('/hello/{name}', function ($request, $response, $name) {
    $response->getBody()->write($name);
    
    return $response;
});

或者,你也可以針對每條路徑設定不同的呼叫策略

<?php
use Slim\Factory\AppFactory;
use Slim\Handlers\Strategies\RequestResponseArgs;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();
$routeCollector = $app->getRouteCollector();

$route = $app->get('/hello/{name}', function ($request, $response, $name) {
    $response->getBody()->write($name);
    
    return $response;
});
$route->setInvocationStrategy(new RequestResponseArgs());

你可以透過實作 Slim\Interfaces\InvocationStrategyInterface 來提供自己的路徑策略。

路徑佔位符

上述各路徑方法都接受 URL 模式,這個模式會與目前的 HTTP 要求 URI 相符。路徑模式可以使用命名的佔位符,來動態比對 HTTP 要求 URI 片段。

格式

路徑模式佔位符從 { 開始,接著是佔位符名稱,最後以 } 結束。以下是一個名為 name 的範例佔位符

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
// ...

$app->get('/hello/{name}', function (ServerRequestInterface $request, ResponseInterface $response, array $args) {
    $name = $args['name'];
    $response->getBody()->write("Hello, $name");
    
    return $response;
});

選擇性區段

要讓部分內容變成選擇性的,只要以方括弧括起來就可以了

$app->get('/users[/{id}]', function ($request, $response, array $args) {
    // responds to both `/users` and `/users/123`
    // but not to `/users/`
    
    return $response;
});

透過巢狀撰寫,可以支援多個選擇性參數

$app->get('/news[/{year}[/{month}]]', function ($request, $response, array $args) {
    // responds to `/news`, `/news/2016` and `/news/2016/03`
    // ...
    
    return $response;
});

對於「無限」選擇性參數,你可以這樣做

$app->get('/news[/{params:.*}]', function ($request, $response, array $args) {
    // $params is an array of all the optional segments
    $params = explode('/', $args['params']);
    // ...
    
    return $response;
});

在此範例中,/news/2016/03/20 的 URI 會產生包含三個元素的 $params 陣列:['2016', '03', '20']

正規表示法的匹配

預設情況下,佔位符寫在 {} 內部,且可接受任何值。然而,佔位符還可以要求 HTTP 請求 URI 符合特定正規表示法。如果當前的 HTTP 請求 URI 不符合佔位符正規表示法,路徑便不會呼叫。這是名為 id 的佔位符範例,它需要一個或多個數字。

$app->get('/users/{id:[0-9]+}', function ($request, $response, array $args) {
    // Find user identified by $args['id']
    // ...
    
    return $response;
});

路徑名稱

應用程式路徑可被指定一個名稱。如果您想透過 RouteParser 的 urlFor() 方法,以程式化方式產生特定路徑的 URL,這會很有用。上面說明的每一個路由方法都會回傳一個 Slim\Route 物件,而該物件會公開一個 setName() 方法。

$app->get('/hello/{name}', function ($request, $response, array $args) {
    $response->getBody()->write("Hello, " . $args['name']);
    return $response;
})->setName('hello');

使用應用程式 RouteParser 的 urlFor() 方法產生此已命名路徑的 URL。

$routeParser = $app->getRouteCollector()->getRouteParser();
echo $routeParser->urlFor('hello', ['name' => 'Josh'], ['example' => 'name']);

// Outputs "/hello/Josh?example=name"

RouteParser 的urlFor() 方法接受三個引數:

  • $routeName 路徑名稱。可透過 $route->setName('name') 設定路徑名稱。路徑對應方法會回傳 Route 的實例,因此您可以在對應路徑後直接設定名稱。例如:$app->get('/', function () {...})->setName('name')
  • $data 路徑樣式佔位符和替換值的關聯式陣列。
  • $queryParams 要附加到所產生 URL 的查詢參數的關聯式陣列。

路徑群組

為了協助將路徑組織成邏輯群組,Slim\App 也提供了一個 group() 方法。每個群組的路徑樣式都會附加在群組中所包含的路徑或群組之前,而群組樣式中的任何佔位符引數最終都會提供給巢狀的路徑。

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/users/{id:[0-9]+}', function (RouteCollectorProxy $group) {
    $group->map(['GET', 'DELETE', 'PATCH', 'PUT'], '', function ($request, $response, array $args) {
        // Find, delete, patch or replace user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user');
    
    $group->get('/reset-password', function ($request, $response, array $args) {
        // Route for /users/{id:[0-9]+}/reset-password
        // Reset the password for user identified by $args['id']
        // ...
        
        return $response;
    })->setName('user-password-reset');
});

群組樣式可以為空,這允許將不共用一般模式的路徑進行邏輯性分組。

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('', function (RouteCollectorProxy $group) {
    $group->get('/billing', function ($request, $response, array $args) {
        // Route for /billing
        return $response;
    });
    
    $group->get('/invoice/{id:[0-9]+}', function ($request, $response, array $args) {
        // Route for /invoice/{id:[0-9]+}
        return $response;
    });
})->add(new GroupMiddleware());

請注意,在群組封閉中,Slim 會將封閉繫結到容器實例。

  • 在路徑封閉中,$this 會繫結到 Psr\Container\ContainerInterface 的實例。

路徑中間件

您也可以將中間件附加到任何路徑或路徑群組。

use Slim\Routing\RouteCollectorProxy;
// ...

$app->group('/foo', function (RouteCollectorProxy $group) {
    $group->get('/bar', function ($request, $response, array $args) {
        // ...
        return $response;
    })->add(new RouteMiddleware());
})->add(new GroupMiddleware());

路徑表示式快取

可以透過 RouteCollector::setCacheFile() 啟用路由快取。請參閱以下範例:

<?php
use Slim\Factory\AppFactory;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

/**
 * To generate the route cache data, you need to set the file to one that does not exist in a writable directory.
 * After the file is generated on first run, only read permissions for the file are required.
 *
 * You may need to generate this file in a development environment and committing it to your project before deploying
 * if you don't have write permissions for the directory where the cache file resides on the server it is being deployed to
 */
$routeCollector = $app->getRouteCollector();
$routeCollector->setCacheFile('/path/to/cache.file');

容器解析

你不一定要為你的路由定義函數。在 Slim 中有多種不同的方式來定義路由動作函數。

除了函數以外,你還可以:使用

  • container_key:method
  • Class:method
  • 實作 __invoke() 方法的類別
  • container_key

此功能是由 Slim 的 Callable 解析器類別啟用的。它會把字串項目轉換成函數呼叫。範例

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

或者,你可以利用 PHP 的 ::class 算子,此算子與 IDE lookup 系統搭配使用效果良好,而且會產生相同的結果

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

你也可以傳遞陣列,其中第一個元素會包含類別名稱,第二個元素會包含要呼叫的方法名稱

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

在上面的這段程式碼中,我們定義了 / 路由,並告訴 Slim 在 HomeController 類別上執行 home() 方法。

Slim 會先在容器中尋找 HomeController 的項目,如果找到了,就會使用該執行個體,否則它就會呼叫建構函式,並把容器當成第一個引數。一旦建立了類別的執行個體之後,就會呼叫指定的方法,使用你已定義的任何策略。

使用容器註冊控制器

建立一個具有 home 動作方法的控制器。建構函式應該接受需要的依存項。舉例來說:

<?php

use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Views\Twig;

class HomeController
{
    private $view;

    public function __construct(Twig $view)
    {
        $this->view = $view;
    }
    
    public function home(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
    {
      // your code here
      // use $this->view to render the HTML
      // ...
      
      return $response;
    }
}

在容器中建立工廠,讓它使用依存項來建置控制器

use Psr\Container\ContainerInterface;
// ...

$container = $app->getContainer();

$container->set(\HomeController::class, function (ContainerInterface $container) {
    // retrieve the 'view' from the container
    $view = $container->get('view');
    
    return new HomeController($view);
});

這能讓你利用容器進行依存項注入,而且還能讓你將特定的依存項注入到控制器中。

讓 Slim 建立控制器執行個體

或者,如果類別在容器中沒有項目,那麼 Slim 會將容器的執行個體傳遞到建構函式。你可以使用具有多個動作的控制器,而不是只有處理一個動作的可呼叫類別來建立控制器。

<?php

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class HomeController
{
   private $container;

   // constructor receives container instance
   public function __construct(ContainerInterface $container)
   {
       $this->container = $container;
   }

   public function home(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }

   public function contact(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }
}

你可以這樣使用你的控制器方法。

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

使用可呼叫類別

你不需要在你的路由可呼叫中指定任何方法,而且可以只把它設成可呼叫類別,例如:

<?php

use Psr\Container\ContainerInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;

class HomeAction
{
   private $container;

   public function __construct(ContainerInterface $container)
   {
       $this->container = $container;
   }

   public function __invoke(ServerRequestInterface $request, ResponseInterface $response, array $args): ResponseInterface
   {
        // your code to access items in the container... $this->container->get('');
        
        return $response;
   }
}

你可以這樣使用這個類別。

$app->get('/', \HomeAction::class);

就像控制器一樣,如果你使用容器註冊類別名稱,那麼你就可以建立工廠,並在動作類別中注入你需要的特定依存項。

路由物件

有時候在中間件中,你需要你路由的參數。

在此範例中,我們首先檢查使用者是否已登入,其次檢查使用者是否有權限觀看他們嘗試觀看的那部影片。

$app->get('/course/{id}', Video::class . ':watch')
    ->add(PermissionMiddleware::class);
<?php

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Server\RequestHandlerInterface as RequestHandler;
use Slim\Routing\RouteContext;

class PermissionMiddleware
{
    public function __invoke(Request $request, RequestHandler $handler)
    {
        $routeContext = RouteContext::fromRequest($request);
        $route = $routeContext->getRoute();
        
        $courseId = $route->getArgument('id');
        
        // do permission logic...
        
        return $handler->handle($request);
    }
}

從路由中取得基礎路徑

若要從路線中取得基礎路徑,只要執行下列動作即可

<?php

use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\Factory\AppFactory;
use Slim\Routing\RouteContext;

require __DIR__ . '/../vendor/autoload.php';

$app = AppFactory::create();

$app->get('/', function(Request $request, Response $response) {
    $routeContext = RouteContext::fromRequest($request);
    $basePath = $routeContext->getBasePath();
    // ...
    
    return $response;
});