初めに
PHPフレームワークFLowでは、GETリクエストの処理の中ではDB更新ができないという仕様になっています。そんな中、どうしてもGETの中でDB更新がしたいという場合もあるかと思います。今回はそんな場合の対処法について解説します。
GETの場合DB更新ができない
実際に試してみました。
以下で作成したAPIにGETリクエストを送ってみたいと思います。
<?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"
]);
}
}
実際に送ってみた結果がこちら。
一部エラー文を抜粋しました↓
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
に以下のようなコードがあります。
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更新
-
safe requestではない場合は
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
というクラスに直で書かれています。このリストから削除するという案です。
<?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更新したい時の対処を書きました。
正直使いどころがあるか微妙ですが、困ってる方は試してみてください。
ここまでご覧いただきありがとうございました!