====== Slim4 クックブック ======
Version 4.5.0
--- //[[http://www.y2sunlight.com|y2sunlight]] 2020-09-23//
[[slim:top|Slim に戻る]]
関連記事
* [[slim:4:install|Slim4 インストール]]
* [[slim:4:concepts|Slim4 コンセプト]]
* [[slim:4:request|Slim4 リクエスト]]
* [[slim:4:response|Slim4 レスポンス]]
* [[slim:4:routing|Slim4 ルーティング]]
* [[slim:4:middleware|Slim4 ミドルウェア]]
* Slim4 クックブック
本章は以下のサイトの **Cook book** のセクションを翻訳し若干の補足を加えたのもです。
* https://www.slimframework.com/docs/v4/
-----
===== ルートパターンの末尾のスラッシュ(/) =====
Slimは、末尾にスラッシュがあるURLパターンを、無いものとは異なるものとして扱います。つまり、''/user'' と ''/user/'' は異なるので、異なるコールバックをアタッチできます。
For GET requests a permanent redirect is fine, but for other request methods like POST or PUT the browser will send the second request with the GET method. To avoid this you simply need to remove the trailing slash and pass the manipulated url to the next middleware.
GETリクエストの場合、パーマネント(permanent)リダイレクトは問題ありませんが、POSTやPUTなどの他のリクエストメソッドの場合、ブラウザはGETメソッドを使用して2番目のリクエストを送信します。これを回避するには、単に、末尾のスラッシュを削除し、操作されたURLを次のミドルウェアに渡す必要があります。
''/'' で終わるすべてのURLを、末尾以外の ''/'' に相当するものにリダイレクトまたはリライトする場合、次のミドルウェアを追加できます。
add(function (Request $request, RequestHandler $handler) {
$uri = $request->getUri();
$path = $uri->getPath();
if ($path != '/' && substr($path, -1) == '/') {
// recursively remove slashes when its more than 1 slash
$path = rtrim($path, '/');
// permanently redirect paths with a trailing slash
// to their non-trailing counterpart
$uri = $uri->withPath($path);
if ($request->getMethod() == 'GET') {
$response = new Response();
return $response
->withHeader('Location', (string) $uri)
->withStatus(301);
} else {
$request = $request->withUri($uri);
}
}
return $handler->handle($request);
});
あるいは、[[https://github.com/middlewares/trailing-slash|middlewares/trailing-slash]] ミドルウェアを検討してください。これにより、すべてのURLに末尾のスラッシュを強制的に追加することもできます。
use Middlewares\TrailingSlash;
$app->add(new TrailingSlash(true)); // true adds the trailing slash (false removes it)
\\
===== 現在のルートの取得 =====
アプリケーション内の現在のルートにアクセスする必要がある場合は、着信時の ''ServerRequestInterface'' を使用して ''RouteContext'' オブジェクトをインスタンス化する必要があります。
そこから、''$routeContext->getRoute()'' を介してルートを取得し、''getName()'' を使用してルートの名前にアクセスするか、''getMethods()'' などを介してこのルートでサポートされているメソッドを取得できます。
注:ルート(route)ハンドラーに到達する前にミドルウェアサイクル中に ''RouteContext'' オブジェクトにアクセスする必要がある場合は、エラー処理ミドルウェアの前に、''RoutingMiddleware'' を最も外側のミドルウェアとして追加する必要があります(以下の例を参照)。
例:
add(function (Request $request, RequestHandler $handler) {
$routeContext = RouteContext::fromRequest($request);
$route = $routeContext->getRoute();
// return NotFound for non existent route
if (empty($route)) {
throw new HttpNotFoundException($request);
}
$name = $route->getName();
$groups = $route->getGroups();
$methods = $route->getMethods();
$arguments = $route->getArguments();
// ... do something with the data ...
return $handler->handle($request);
});
// The RoutingMiddleware should be added after our CORS middleware so routing is performed first
$app->addRoutingMiddleware();
// The ErrorMiddleware should always be the outermost middleware
$app->addErrorMiddleware(true, true, true);
// ...
$app->run();
\\
===== CORSの設定 =====
CORS - クロス オリジンリソース シェアリング(オリジン間リソース共有)
* CORSサポートを実装するための適切なフローチャートリファレンス: \\ [[https://www.html5rocks.com/static/images/cors_server_flowchart.png|CORSサーバーのフローチャート]]
* CORSサポートはここでテストできます:[[http://www.test-cors.org/]]
* ここで仕様を読むことができます:[[https://www.w3.org/TR/cors/]]
> 参考:https://developer.mozilla.org/ja/docs/Glossary/CORS
==== シンプル ソリューション ====
単純なCORSリクエストの場合、サーバーはレスポンスに次のヘッダーを追加するだけで済みます。
Access-Control-Allow-Origin: , ...
The following code should enable lazy CORS.
次のコードは、Lazy CORS を有効にする必要があります。
$app->options('/{routes:.+}', function ($request, $response, $args) {
return $response;
});
$app->add(function ($request, $handler) {
$response = $handler->handle($request);
return $response
->withHeader('Access-Control-Allow-Origin', 'http://mysite')
->withHeader('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type, Accept, Origin, Authorization')
->withHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, PATCH, OPTIONS');
});
最後のルートとして次のルートを追加します:
map(['GET', 'POST', 'PUT', 'DELETE', 'PATCH'], '/{routes:.+}', function ($request, $response) {
throw new HttpNotFoundException($request);
});
\\
==== Access-Control-Allow-Methods ====
次のミドルウェアを使用して、Slimのルーターにクエリを実行し、特定のパターンが実装するメソッドのリストを取得できます。
これが完全なサンプルアプリケーションです:
addBodyParsingMiddleware();
// This middleware will append the response header Access-Control-Allow-Methods with all allowed methods
$app->add(function (Request $request, RequestHandlerInterface $handler): Response {
$routeContext = RouteContext::fromRequest($request);
$routingResults = $routeContext->getRoutingResults();
$methods = $routingResults->getAllowedMethods();
$requestHeaders = $request->getHeaderLine('Access-Control-Request-Headers');
$response = $handler->handle($request);
$response = $response->withHeader('Access-Control-Allow-Origin', '*');
$response = $response->withHeader('Access-Control-Allow-Methods', implode(',', $methods));
$response = $response->withHeader('Access-Control-Allow-Headers', $requestHeaders);
// Optional: Allow Ajax CORS requests with Authorization header
// $response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
return $response;
});
// The RoutingMiddleware should be added after our CORS middleware so routing is performed first
$app->addRoutingMiddleware();
// The routes
$app->get('/api/v0/users', function (Request $request, Response $response): Response {
$response->getBody()->write('List all users');
return $response;
});
$app->get('/api/v0/users/{id}', function (Request $request, Response $response, array $arguments): Response {
$userId = (int)$arguments['id'];
$response->getBody()->write(sprintf('Get user: %s', $userId));
return $response;
});
$app->post('/api/v0/users', function (Request $request, Response $response): Response {
// Retrieve the JSON data
$parameters = (array)$request->getParsedBody();
$response->getBody()->write('Create user');
return $response;
});
$app->delete('/api/v0/users/{id}', function (Request $request, Response $response, array $arguments): Response {
$userId = (int)$arguments['id'];
$response->getBody()->write(sprintf('Delete user: %s', $userId));
return $response;
});
// Allow preflight requests
// Due to the behaviour of browsers when sending a request,
// you must add the OPTIONS method. Read about preflight.
$app->options('/api/v0/users', function (Request $request, Response $response): Response {
// Do nothing here. Just return the response.
return $response;
});
// Allow additional preflight requests
$app->options('/api/v0/users/{id}', function (Request $request, Response $response): Response {
return $response;
});
// Using groups
$app->group('/api/v0/users/{id:[0-9]+}', function (RouteCollectorProxy $group) {
$group->put('', function (Request $request, Response $response, array $arguments): Response {
// Your code here...
$userId = (int)$arguments['id'];
$response->getBody()->write(sprintf('Put user: %s', $userId));
return $response;
});
$group->patch('', function (Request $request, Response $response, array $arguments): Response {
$userId = (int)$arguments['id'];
$response->getBody()->write(sprintf('Patch user: %s', $userId));
return $response;
});
// Allow preflight requests
$group->options('', function (Request $request, Response $response): Response {
return $response;
});
});
$app->run();
\\
==== Access-Control-Allow-Credentials ====
リクエストに資格情報(Cookie、承認ヘッダー、またはTLSクライアント証明書)が含まれている場合は、レスポンスオブジェクトに ''Access-Control-Allow-Credentials'' ヘッダーを追加する必要がある場合があります。
$response = $response->withHeader('Access-Control-Allow-Credentials', 'true');
\\
===== POSTフォームを使ったファイルのアップロード =====
POSTリクエストのフォームを使用してアップロードされたファイルは、Requestメソッド ''getUploadedFiles()'' を使用して取得できます。
POSTリクエストを使用してファイルをアップロードする場合は、ファイルアップロードフォームに属性 ''enctype="multipart/form-data"'' があることを確認してください。そうでない場合、''getUploadedFiles()'' は空の配列を返します。
同じ input name に対して複数のファイルがアップロードされる場合は、HTMLのinput nameの後に鍵括弧( ''[]'' )を追加します。そうでない場合、''getUploadedFiles()'' によってinput nameに対してアップロードされたファイルが1つだけ返されます。
以下は、単一ファイルと複数ファイルの両方のアップロードを含むHTMLフォームの例です。
アップロードされたファイルは、''moveTo'' メソッドを使用してディレクトリに移動できます。以下は、上記のHTMLフォームのアップロードされたファイルを処理するサンプルアプリケーションです。
build();
$container->set('upload_directory', __DIR__ . '/uploads');
AppFactory::setContainer($container);
$app = AppFactory::create();
$app->post('/', function (ServerRequestInterface $request, ResponseInterface $response) {
$directory = $this->get('upload_directory');
$uploadedFiles = $request->getUploadedFiles();
// handle single input with single file upload
$uploadedFile = $uploadedFiles['example1'];
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
$filename = moveUploadedFile($directory, $uploadedFile);
$response->getBody()->write('Uploaded: ' . $filename . '
');
}
// handle multiple inputs with the same key
foreach ($uploadedFiles['example2'] as $uploadedFile) {
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
$filename = moveUploadedFile($directory, $uploadedFile);
$response->getBody()->write('Uploaded: ' . $filename . '
');
}
}
// handle single input with multiple file uploads
foreach ($uploadedFiles['example3'] as $uploadedFile) {
if ($uploadedFile->getError() === UPLOAD_ERR_OK) {
$filename = moveUploadedFile($directory, $uploadedFile);
$response->getBody()->write('Uploaded: ' . $filename . '
');
}
}
return $response;
});
/**
* Moves the uploaded file to the upload directory and assigns it a unique name
* to avoid overwriting an existing uploaded file.
*
* @param string $directory The directory to which the file is moved
* @param UploadedFileInterface $uploadedFile The file uploaded file to move
*
* @return string The filename of moved file
*/
function moveUploadedFile(string $directory, UploadedFileInterface $uploadedFile)
{
$extension = pathinfo($uploadedFile->getClientFilename(), PATHINFO_EXTENSION);
// see http://php.net/manual/en/function.random-bytes.php
$basename = bin2hex(random_bytes(8));
$filename = sprintf('%s.%0.8s', $basename, $extension);
$uploadedFile->moveTo($directory . DIRECTORY_SEPARATOR . $filename);
return $filename;
}
$app->run();
\\