0
0
この記事誰得? 私しか得しないニッチな技術で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

【PHPフレームワークFLow】GETリクエストだけどDB更新したい時の対処

Last updated at Posted at 2024-07-05

初めに

PHPフレームワークFLowでは、GETリクエストの処理の中ではDB更新ができないという仕様になっています。そんな中、どうしてもGETの中でDB更新がしたいという場合もあるかと思います。今回はそんな場合の対処法について解説します。

GETの場合DB更新ができない

実際に試してみました。
以下で作成したAPIにGETリクエストを送ってみたいと思います。

Getだけど登録を試みたAPI
<?php
namespace Neos\Welcome\Controller;

use Neos\Flow\Annotations as Flow;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Controller\ActionController;

class PersistanceTestController extends ActionController
{

    /**
     * @Flow\Inject
     * @var \Neos\Welcome\Domain\Repository\ItemRepository
     */
    protected $itemRepository;

    /**
     * @Flow\Inject
     * @var \Neos\Flow\Mvc\View\JsonView
     */
    protected $view;

    /**
     * @return string
     */
    public function getButWantToRegisterAction()
    {   
        // GETだけどデータ登録
        $this->itemRepository->addItem(
            'id', 'name', 100, 'storeId', 'storeName', 'address'
        );
        $this->view->assign('value', [
            "Success Registeration"
        ]);
    }
}

実際に送ってみた結果がこちら。

image.png

一部エラー文を抜粋しました↓

According to the HTTP 1.1 specification, so called "safe request" (usually GET or HEAD requests)
should not change your data on the server side and should be considered read-only. If you need to add,
modify or remove data, you should use the respective request methods (POST, PUT, DELETE and PATCH).

GETとHEADはsafe requestとして扱われており、登録は許してないとのこと。困った困った。

なぜできないのか

ということでどこのコードで制御されているのか探してきました。
Package.phpに以下のようなコードがあります。

Package.phpでデータ更新の有無を確認している
class Package extends BasePackage
{
    public function boot(Core\Bootstrap $bootstrap)
    {
        // 中略
        
        $dispatcher->connect(Mvc\Dispatcher::class, 'afterControllerInvocation', function ($request) use ($bootstrap) {
            // No auto-persistence if there is no PersistenceManager registered
            if (
                $bootstrap->getObjectManager()->has(Persistence\PersistenceManagerInterface::class)
            ) {
                if (!$request instanceof Mvc\ActionRequest || SecurityHelper::hasSafeMethod($request->getHttpRequest()) !== true) {
                    $bootstrap->getObjectManager()->get(Persistence\PersistenceManagerInterface::class)->persistAll();
                } elseif (SecurityHelper::hasSafeMethod($request->getHttpRequest())) {
                    $bootstrap->getObjectManager()->get(Persistence\PersistenceManagerInterface::class)->persistAllowedObjects();
                }
            }
        });
        
        // 中略
    }
}

このコードを見るに、以下のような処理になってそうです。

  • DispatcherクラスのafterControllerInvocationというメソッドが呼び出される際に、クロージャで定義している処理が走る
  • クロージャーの処理は、
    • safe requestではない場合はpersistAll()でDB更新
    • そうでない場合はpersistAllowedObjects()許可されているオブジェクトのみDB更新

DispacherはFlowにおけるControllerを呼び出しているクラスです。afterControllerInvocationは名前的にControllerの後に呼び出される処理のようですね。

また、persistAll()はどんなオブジェクトでもDB更新を許可するのに対し、persistAllowedObjects()はあらかじめ許可をしておいたオブジェクトのみ更新するという違いがありそうです。

対処法

以上から、以下のような対処法が浮かびました。

  • 案1:Dispacherに処理が返る前に、ActionController内でpersistAllする
  • 案2:更新対象のオブジェクトをAllowedObjectsに追加する
  • 案3:safe requestの一覧からGETをなくす

案1:Dispacherに処理が返る前に、ActionController内でpersistAllする

一番現実的な案です。
APIごとに処理を変えることができるので便利ですしね。

    public function getButWantToRegisterAction()
    {   
        $this->itemRepository->addItem('id', 'name', 100, 'storeId', 'storeName', 'address');
        // ActionControllerの処理を終える前にpersistAll()を実行
        $this->persistenceManager->persistAll();
        $this->view->assign('value', [
            "Success Registeration"
        ]);
    }

案2:更新対象のオブジェクトをAllowedObjectsに追加する

オブジェクトごとにDB更新していいかどうかを設定したいならこの案かなと思います。

これについては実際まだ試せていないので、できるのかどうかも含めて今度検証してみます。

(今までFlowを触ってきた感じでは、settings.yamlなどに記述するのかなと思っていますが、真相は分からず)

案3:safe requestの一覧からGETをなくす

個人的に一番微妙な案です。
safe requestの一覧はFW内のSecurityHelperというクラスに直で書かれています。このリストから削除するという案です。

safe requestの一覧はSecurityHelperクラスに書かれている
<?php
namespace Neos\Flow\Http\Helper;

use Psr\Http\Message\RequestInterface;

/**
 * Helper functions about request safety and security.
 */
abstract class SecurityHelper
{
    public static function isSecureRequest(RequestInterface $request): bool
    {
        // TODO: Isn't there a better way to figure this out?
        return $request->getUri()->getScheme() === 'https';
    }

    public static function hasSafeMethod(RequestInterface $request): bool
    {
        return (in_array($request->getMethod(), ['GET', 'HEAD']));
    }
}

FWのソースを直接修正するのは微妙なので、やるならこのクラスを継承した独自クラスを作成してやる感じになるかと思います。

終わりに

ということで、GETリクエストだけどDB更新したい時の対処を書きました。
正直使いどころがあるか微妙ですが、困ってる方は試してみてください。

ここまでご覧いただきありがとうございました!

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0