Як правильно використовувати 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 для дій, що змінюють стан, ви досягнете більш надійного та безпечного додатку.