[CakePHP3] withParsedBody()でブラウザからのPOSTをレスポンスへ再現

2020/03/15

author

masyus

下記のようなフローがあったとします。

  • 入力画面
  • 確認画面
  • 完了画面

1.から2.へはSessionで入力データを引き継がせるようにしたとして、

2.から「修正する」ボタンを押下してGETで1.へ戻る時、Sessionに引き継いだ入力情報をどうやって簡単に1.の画面へ反映させることができるか?

で実装を悩んだことがあります。

CakePHP3の仕様上、フォームの入力値を引き継がせる処理はValidationエラー時等でフォームのある画面に戻った際に、勝手に引き継がれる仕組みになっています。ですがあくまで入力画面のみの挙動のため、別の画面から入力画面へ戻ってきた時に入力値を引き継がせる方法が分からなかったのです。今回はこの問題の解決方法を紹介いたします。検証したCakePHPのバージョンは3.6.13、PHPは7.3です。

withParsedBody()でフォームへ入力値を引き継がせることが可能

最初から結論を申しますと、CakePHP3のSkeletonにsrc/Http/ServerRequest.phpというクラスがあるのですが、そのクラスに定義されているwithParsedBody()を使うことで実現可能です。

<?php
namespace App\Controller;

use App\Controller\AppController;
use App\Form\HogeForm;
use Cake\View\View;
use Cake\Network\Exception\BadRequestException;

class HogeController extends AppController
{
    public function initialize()
    {
        parent::initialize();
    }

    /**
     * 入力画面
     */
    public function input()
    {
        $hoge = new HogeForm(); // モデルのないフォーム

        if ($this->request->is('post')) {
            $requestData = $this->request->getData();
            if ($hoge->validate($requestData)) {
                $this->request->session()->write('requestData', $requestData);
                return $this->redirect(['action' => 'confirm']);
            }
        } else {
            if ($this->request->session()->check('requestData')) {
                // 確認画面から「修正する」ボタンを押して、GETで入力画面に戻ってきた時の処理
                $requestData = $this->request->session()->read('requestData');
                // 入力値を画面に引き継がせる(実際には「HTTPリクエストボディ内容を差し替える」が正しい)
                $this->request = $this->request->withParsedBody($requestData);
            }
        }

        $this->set(compact('hoge'));
    }

    /**
     * 確認画面
     */
    public function confirm()
    {
        if (!$this->request->session()->check('requestData')) {
            throw new BadRequestException('不正なリクエストです');
        }

        $requestData = $this->request->session()->read('requestData');
        $this->set(compact('requestData'));
    }
}

ServerRequestクラスは$this->request()もしくは$this->getRequest()にて呼び出すことが可能です。

withParsedBody()の役割

withParsedBody()は、画面からサーバーサイドへ送信されるHTTPリクエストボディに含まれるGETやPOSTにて渡されたデータを再現することができるメソッドのようです。内部的には下記のように、自身のServerRequestインスタンスを複製した上でデータをセットし、そのインスタンスを返します。

    /**
     * Update the parsed body and get a new instance.
     *
     * @param null|array|object $data The deserialized body data. This will
     *     typically be in an array or object.
     * @return static
     */
    public function withParsedBody($data)
    {
        $new = clone $this;
        $new->data = $data;

        return $new;
    }

参考から当該コードを辿れます。

もし1カラムずつデータをセットさせたい場合、withData()を使うと良いとのことです(※未検証です)。

    /**
     * Update the request with a new request data element.
     *
     * Returns an updated request object. This method returns
     * a *new* request object and does not mutate the request in-place.
     *
     * Use `withParsedBody()` if you need to replace the all request data.
     *
     * @param string $name The dot separated path to insert $value at.
     * @param mixed $value The value to insert into the request data.
     * @return static
     */
    public function withData($name, $value)
    {
        $copy = clone $this;
        $copy->data = Hash::insert($copy->data, $name, $value);

        return $copy;
    }

withParsedBody()が他に使えそうな箇所

(あまり書くかは分からないのですが、)Controllerのテストコードが挙げられます。なぜならwithParsedBody()は画面から入力されたリクエストデータを再現することができますので。

参考