Como usar corretamente links POST

Em aplicações web, especialmente em interfaces administrativas, deve ser uma regra básica que ações que alteram o estado do servidor não devem ser realizadas através do método HTTP GET. Como o nome do método sugere, GET deve ser usado apenas para obter dados, não para alterá-los. Para ações como excluir registros, é mais apropriado usar o método POST. Embora o ideal fosse o método DELETE, ele não pode ser invocado sem JavaScript, por isso historicamente se usa POST.

Como fazer isso na prática? Use este truque simples. No início do template, crie um formulário auxiliar com o identificador postForm, que você usará posteriormente para os botões de exclusão:

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

Graças a este formulário, você pode usar um botão <button> em vez de um link <a> clássico, que pode ser estilizado visualmente para parecer um link comum. Por exemplo, o framework CSS Bootstrap oferece as classes btn btn-link com as quais você pode garantir que o botão não seja visualmente diferente de outros links. Usando o atributo form="postForm", nós o vinculamos ao formulário pré-preparado:

<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>
			<!-- em vez de <a n:href="delete $post->id">delete</a> -->
		</td>
	</tr>
</table>

Ao clicar no link, a ação delete agora é invocada. Para garantir que as requisições sejam aceitas apenas através do método POST e do mesmo domínio (o que é uma defesa eficaz contra ataques CSRF), use o atributo #[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); // código hipotético que exclui o registro
		$this->redirect('default');
	}
}

O atributo existe desde o Nette Application 3.2 e você pode aprender mais sobre suas possibilidades na página Como usar o atributo #Requires.

Se você estivesse usando o sinal handleDelete() em vez da ação actionDelete(), não seria necessário especificar sameOrigin: true, pois os sinais têm essa proteção definida implicitamente:

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

Esta abordagem não só melhora a segurança da sua aplicação, mas também contribui para a adesão aos padrões e práticas corretas da web. Ao utilizar métodos POST para ações que alteram o estado, você alcançará uma aplicação mais robusta e segura.