POST リンクの正しい使い方

ウェブアプリケーション、とくに管理画面では、サーバーの状態を変える操作を HTTP の GET メソッドで行わない、という基本の原則を守るべきです。名前が示すとおり、GET はデータの取得にだけ使うもので、変更に使うものではありません。レコードの削除のような操作には POST メソッドのほうが適しています。理想をいえば DELETE メソッドですが、JavaScript なしでは呼び出せません。だからこそ、こうした操作には歴史的に POST が使われてきました。

実際にはどう実装すればよいでしょうか。次の簡単な工夫を使います。レイアウトテンプレートの先頭に、ID が postForm の補助的なフォームを作ります。削除ボタンなどの操作では、このフォームを使います。

<form method="post" id="postForm"></form>

このフォームのおかげで、通常の <a> リンクの代わりに <button> を使えます。このボタンは普通のリンクのように見えるようスタイルを当てられます。たとえば Bootstrap の CSS フレームワークには btn btn-link クラスがあり、ボタンをほかのリンクと見分けがつかないようにできます。form="postForm" 属性を使って、ボタンを用意した補助フォームに結びつけます。

<table>
	<tr n:foreach="$posts as $post">
		<td>{$post->title}</td>
		<td>
			<button class="btn btn-link" form="postForm" formaction="{link delete $post->id}">削除</button>
			<!-- <a n:href="delete $post->id">削除</a> の代わり -->
		</td>
	</tr>
</table>

このボタンをクリックすると delete アクションが呼ばれます。リクエストが POST メソッドでのみ、しかも同じドメインから来たものだけ受け付けられるようにするには(CSRF 攻撃への実効的な防御になります)、#[Requires] アトリビュートを使います。

use Nette\Application\Attributes\Requires;

class AdminPresenter extends Nette\Application\UI\Presenter
{
	#[Requires(methods: 'POST', sameOrigin: true)]
	public function actionDelete(int $id): void
	{
		$this->facade->deletePost($id); // レコードを削除する架空のコード
		$this->redirect('default');
	}
}

このアトリビュートは Nette Application 3.2 から使えます。その機能について詳しくは #Requires アトリビュートの使い方のページをご覧ください。

actionDelete() アクションの代わりに handleDelete() シグナルを使っていた場合、sameOrigin: true を指定する必要はありません。シグナルではこの保護が既定で有効だからです。

#[Requires(methods: 'POST')]
public function handleDelete(int $id): void
{
	$this->facade->deletePost($id);
	$this->redirect('this');
}

この方法はアプリケーションの安全性を高めるだけでなく、ウェブの適切な標準と作法に沿うことにもつながります。状態を変える操作に POST メソッドを使えば、より堅牢で安全なアプリケーションになります。