gallu’s blog

エンジニアでゲーマーで講師で占い師なおいちゃんのブログです。

Slim docsの解析; The Response

https://www.slimframework.com/docs/v3/objects/response.html
ここも結構な大物だろう、と予想。


How to get the Response object
いやまぁHow to言われても「呼ばれる関数(メソッド)の第二引数でゲトれる」くらいだしなぁ。

<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

$app = new \Slim\App;
$app->get('/foo', function (ServerRequestInterface $request, ResponseInterface $response) {
    // Use the PSR 7 $response object

    return $response;
});
$app->run();

ここは一応突っ込みが無くもなくて。
「文字列をreturnすると、現状の$responseにその文字列をbodyとして書き込むような処理を追記してくれる」んだよね。
http://d.hatena.ne.jp/gallu/20180613/p1

        } elseif (is_string($newResponse)) {
            // if route callback returns a string, then append it to the response
            if ($response->getBody()->isWritable()) {
                $response->getBody()->write($newResponse);
            }
        }

あたりを参照。この辺が「そうそう簡単に変わる」ってのも考えにくいから「文字列returnしたらよしなにしてくれる」は、期待してよい動作、なんじゃないかなぁ? と思ってる。

The PSR 7 response object is injected into your Slim application middleware as the second argument of the middleware callable like this:

<?php
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Message\ResponseInterface;

$app = new \Slim\App;
$app->add(function (ServerRequestInterface $request, ResponseInterface $response, callable $next) {
    // Use the PSR 7 $response object

    return $next($request, $response);
});
// Define app routes...
$app->run();

middleware周りはまた今度、まとめて徹底的に。なので、今回は一旦、パス。


The Response Status。割と大事ぽ。

$newResponse = $response->withStatus(302);

ここだけ把握しておけばよいんじゃないかなぁ………あぁ、一点気になること。
Response(以外もいくつかそうだけど)は、いわゆる「不変インスタンスである」とされているから、withStatusの実装って


vendor/slim/slim/Slim/Http/Response.php

    public function withStatus($code, $reasonPhrase = '')
    {
        $code = $this->filterStatus($code);

        if (!is_string($reasonPhrase) && !method_exists($reasonPhrase, '__toString')) {
            throw new InvalidArgumentException('ReasonPhrase must be a string');
        }

        $clone = clone $this;
        $clone->status = $code;
        if ($reasonPhrase === '' && isset(static::$messages[$code])) {
            $reasonPhrase = static::$messages[$code];
        }

        if ($reasonPhrase === '') {
            throw new InvalidArgumentException('ReasonPhrase must be supplied for this code');
        }

        $clone->reasonPhrase = $reasonPhrase;

        return $clone;
    }

なんだよね。ポイントは「$clone = clone $this;」して「return $clone;」ってあたり。
なので、この辺でさっきの「文字列をreturn」と組み合わせると、なんか不一致が出そうな気が少しする、ので、気を付けたほうがよいかも。
………うん、returnは「ちゃんとResponseインスタンス返す」ようにしたほうが安全かも。
ちと、確認してみませう。


public/index.php

$app = new \Slim\App;
$app->get('/add_header', function(Request $request, Response $response, array $args) {
    $response = $response->withStatus(304);
    //return 'test';
    return $response->getBody()->write('test');
});

$ telnet localhost 8080
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /add_header HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
Host: example.com
Date: Mon, 18 Jun 2018 05:07:09 +0000
Connection: close
X-Powered-By: PHP/7.2.6
Content-Type: text/html; charset=UTF-8
Content-Length: 4

testConnection closed by foreign host.

………あれ?
…………あ。
vendor/slim/slim/Slim/Http/Stream.php

    public function write($string)
    {
        if (!$this->isWritable() || ($written = fwrite($this->stream, $string)) === false) {
            throw new RuntimeException('Could not write to stream');
        }

        // reset size so that it will be recalculated on next call to getSize()
        $this->size = null;

        return $written;
    }

戻り値、stringか。


public/index.php

$app->get('/add_header', function(Request $request, Response $response, array $args) {
    $response = $response->withStatus(304);
    $response->getBody()->write('test');
    //return 'test';
    return $response;
});

$ telnet localhost 8080
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /add_header HTTP/1.1
Host: example.com

HTTP/1.1 304 Not Modified
Host: example.com
Date: Mon, 18 Jun 2018 05:18:11 +0000
Connection: close
X-Powered-By: PHP/7.2.6

Connection closed by foreign host.

よし意図通り。
ってことは

$app->get('/add_header', function(Request $request, Response $response, array $args) {
    $response = $response->withStatus(304);
    //$response->getBody()->write('test');
    return 'test';
});

$ telnet localhost 8080
Trying ::1...
telnet: connect to address ::1: Connection refused
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
GET /add_header HTTP/1.1
Host: example.com

HTTP/1.1 200 OK
Host: example.com
Date: Mon, 18 Jun 2018 05:19:06 +0000
Connection: close
X-Powered-By: PHP/7.2.6
Content-Type: text/html; charset=UTF-8
Content-Length: 4

testConnection closed by foreign host.

だ〜よ〜ね〜〜。
これ、お作法的に
「最後は return $response; 」っての、徹底したほうが安全かも。ここだけは「Response、不変インスタンスじゃなければよかったのに」とか思う瞬間。まぁ、Slimがそう定義した訳でもない、から、ねぇ。
(実はお便利メソッドがresponseに生えてます。後述するwriteメソッド)。


The Response Headers。

$headers = $response->getHeaders();

とか、むしろ「デバッグの文脈」とか「解析の文脈」とか「テストの文脈」とか、で、便利そうな気がする。
便利ではあると思われるので、ほんのりと記憶しておきませう。


まぁあとは

$headerValueArray = $response->getHeader('Vary');
$headerValueString = $response->getHeaderLine('Vary');
if ($response->hasHeader('Vary')) {

とかあって

$newResponse = $oldResponse->withHeader('Content-type', 'application/json');
$newResponse = $oldResponse->withAddedHeader('Allow', 'PUT');

とかあるので、よき……なんだが、2点。
一つは「戻り値がインスタンスである」って点。「不変インスタンスだったよねぇ」ってのを忘れると色々と切ない気がするので、注意。
もう一つは「withHeaderとwithAddedHeaderの違い」。
どちらも vendor/slim/slim/Slim/Http/Message.php に実装があって、かつ

    public function withAddedHeader($name, $value)
    {
        $clone = clone $this;
        $clone->headers->add($name, $value);

        return $clone;
    }

とかって実装なので、本当の実装は
vendor/slim/slim/Slim/Http/Headers.php

    public function add($key, $value)
    {
        $oldValues = $this->get($key, []);
        $newValues = is_array($value) ? $value : [$value];
        $this->set($key, array_merge($oldValues, array_values($newValues)));
    }
    public function set($key, $value)
    {
        if (!is_array($value)) {
            $value = [$value];
        }
        parent::set($this->normalizeKey($key), [
            'value' => $value,
            'originalKey' => $key
        ]);
    }

この辺。
「複数行」って割と(業務で付与するheader的には)珍しいと思うので、「基本は withHeader() 」って覚えておいて、そんなに問題ないんじゃないかなぁ。


あぁもちろん
Remove Header

$newResponse = $oldResponse->withoutHeader('Allow');

もそろってるので、適宜よしなに。
これも不変インスタンス以下略。


The Response Body については。

$body = $response->getBody();

でとれる……のは、bodyインスタンス。デフォは vendor/slim/slim/Slim/Http/Body.php ここ。
なので

$response->getBody()->write();

この辺が、まぁよく使うところかなぁ、と。
上にも書いたけど「これの戻り値、string」なので、色々と注意、ではある。


Returning JSON
………あら、ナウい感じ。
vendor/slim/slim/Slim/Http/Response.php

    public function withJson($data, $status = null, $encodingOptions = 0)
    {
        $response = $this->withBody(new Body(fopen('php://temp', 'r+')));
        $response->body->write($json = json_encode($data, $encodingOptions));

        // Ensure that the json encoding passed successfully
        if ($json === false) {
            throw new \RuntimeException(json_last_error_msg(), json_last_error());
        }

        $responseWithJson = $response->withHeader('Content-Type', 'application/json;charset=utf-8');
        if (isset($status)) {
            return $responseWithJson->withStatus($status);
        }
        return $responseWithJson;
    }

便利〜。
………こっちは「return $response->withJson($data);」ってやってもいいのか………微妙に面倒w
まぁ「withついてるからcloneインスタンスが返ってくる」って覚えてもいいんだけどねぇ。
とはいえいずれにしても、json returnが考慮されているのは、よいこった。


……で、何気なくみていたら
vendor/slim/slim/Slim/Http/Response.php

    public function write($data)
    {
        $this->getBody()->write($data);

        return $this;
    }

やだぁ便利なのあるじゃないですか。
ってことは

return $response->write(HTML文字列);

でいけますなぁ。
こっちこっち。使うの、こっち。


Returning a Redirect。
うん、これも大事。

return $response->withRedirect('/new-url', 301);

シンプル。
………Slimって、ルーティングに「名前付き」ってできたんだっけか?
https://www.slimframework.com/docs/v3/objects/router.html#route-names
に出てくるっぽいので、次回に回そうw


いじょ。
割とシンプルだったなぁ………そういや、Cookie周りのことはなんも書いてないw
まぁ、一通り掘り込んだ上で「なかったら」改めて考察しませう。