Как правильно использовать POST-ссылки

В веб-приложениях, особенно в административных интерфейсах, должно быть основным правилом, что действия, изменяющие состояние сервера, не должны выполняться с помощью метода HTTP GET. Как следует из названия метода, GET должен использоваться только для получения данных, а не для их изменения. Для таких действий, как удаление записей, целесообразнее использовать метод POST. Хотя идеальным вариантом было бы использование метода DELETE, его невозможно вызвать без JavaScript, поэтому исторически используется POST.

Как сделать это на практике? Используйте этот простой прием. В начале вашего шаблона создайте вспомогательную форму с идентификатором postForm, которую вы затем будете использовать для кнопок удаления:

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

В этой форме вы можете использовать <button> вместо классической <a> ссылку, которая может быть визуально изменена, чтобы выглядеть как обычная ссылка. Например, CSS-фреймворк Bootstrap предлагает классы 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}">delete</button>
			<!-- instead of <a n:href="delete $post->id">delete</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); // hypothetical code for deleting a record
		$this->redirect('default');
	}
}

Атрибут доступен с версии Nette Application 3.2, а подробнее о его возможностях вы можете узнать на странице Как использовать атрибут #Requires.

Если вы использовали сигнал handleDelete() вместо действия actionDelete(), то нет необходимости указывать sameOrigin: true, так как у сигналов эта защита установлена неявно:

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

Такой подход не только повышает безопасность вашего приложения, но и способствует соблюдению надлежащих веб-стандартов и практик. Используя методы POST для действий, изменяющих состояние, вы получаете более надежное и безопасное приложение.