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

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

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

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

Благодаря этой форме вы можете вместо классической ссылки <a> использовать кнопку <button>, которую можно визуально стилизовать так, чтобы она выглядела как обычная ссылка. Например, 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}">удалить</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 для действий, изменяющих состояние, вы достигнете более надежного и безопасного приложения.