Как использовать атрибут #[Requires]

Разрабатывая веб-приложение, вы часто сталкиваетесь с необходимостью ограничить доступ к некоторым его частям. Возможно, вы хотите, чтобы часть запросов могла отправлять данные только через форму (то есть методом POST) или была доступна только для AJAX-вызовов. В Nette Framework 3.2 появился новый инструмент, позволяющий задавать такие ограничения изящно и наглядно: атрибут #[Requires].

Атрибут – это особая пометка в PHP, которую вы добавляете перед определением класса или метода. Поскольку по сути это класс, для работы следующих примеров нужно подключить конструкцию use:

use Nette\Application\Attributes\Requires;

Атрибут #[Requires] можно использовать с самим классом презентера и с этими методами:

  • action<Action>()
  • render<View>()
  • handle<Signal>()
  • createComponent<Name>()

Последние два метода относятся и к компонентам, поэтому атрибут можно использовать и с ними.

Если условия, заданные атрибутом, не выполнены, вызывается HTTP-ошибка 4xx.

HTTP-методы

Вы можете указать, какие HTTP-методы (такие как GET, POST и так далее) разрешены для доступа. Например, если вы хотите разрешить доступ только через отправку формы, задайте:

class AdminPresenter extends Nette\Application\UI\Presenter
{
	#[Requires(methods: 'POST')]
	public function actionDelete(int $id): void
	{
	}
}

Почему для действий, меняющих состояние, стоит использовать POST вместо GET и как это сделать? Прочитайте руководство.

Можно указать один метод или массив методов. Особый случай – значение '*', разрешающее все методы, чего презентеры по соображениям безопасности по умолчанию не допускают.

AJAX-вызовы

Если вы хотите, чтобы презентер или метод были доступны только для AJAX-запросов, используйте:

#[Requires(ajax: true)]
class AjaxPresenter extends Nette\Application\UI\Presenter
{
}

Тот же источник

Ради повышения безопасности вы можете потребовать, чтобы запрос приходил с того же домена. Это предотвращает уязвимость CSRF:

#[Requires(sameOrigin: true)]
class SecurePresenter extends Nette\Application\UI\Presenter
{
}

Для методов handle<Signal>() доступ с того же домена требуется автоматически. Поэтому если вы хотите разрешить доступ с любого домена, укажите:

#[Requires(sameOrigin: false)]
public function handleList(): void
{
}

Доступ через forward

Иногда полезно ограничить доступ к презентеру так, чтобы он был доступен только косвенно, например через методы forward() или switch() из другого презентера. Именно так защищаются презентеры ошибок, чтобы их нельзя было вызвать из URL:

#[Requires(forward: true)]
class ForwardedPresenter extends Nette\Application\UI\Presenter
{
}

На практике часто нужно пометить определённые представления, к которым можно попасть только по логике презентера. Опять же, чтобы их нельзя было открыть напрямую:

class ProductPresenter extends Nette\Application\UI\Presenter
{

	public function actionDefault(int $id): void
	{
		$product = $this->facade->getProduct($id);
		if (!$product) {
			$this->setView('notfound');
		}
	}

	#[Requires(forward: true)]
	public function renderNotFound(): void
	{
	}
}

Конкретные действия

Вы можете также ограничить какой-то код, например создание компонента, чтобы он был доступен только для определённых действий презентера:

class EditDeletePresenter extends Nette\Application\UI\Presenter
{
	#[Requires(actions: ['add', 'edit'])]
	public function createComponentPostForm()
	{
	}
}

Для одного действия массив писать не нужно: #[Requires(actions: 'default')]

Собственные атрибуты

Если вы хотите многократно использовать атрибут #[Requires] с одними и теми же настройками, вы можете создать собственный атрибут, который наследует #[Requires] и настраивает его под ваши нужды.

Например, #[SingleAction] разрешает доступ только через действие default:

#[\Attribute]
class SingleAction extends Nette\Application\Attributes\Requires
{
	public function __construct()
	{
		parent::__construct(actions: 'default');
	}
}

#[SingleAction]
class SingleActionPresenter extends Nette\Application\UI\Presenter
{
}

А #[RestMethods] разрешит доступ всеми HTTP-методами, используемыми для REST API:

#[\Attribute]
class RestMethods extends Nette\Application\Attributes\Requires
{
	public function __construct()
	{
		parent::__construct(methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE']);
	}
}

#[RestMethods]
class ApiPresenter extends Nette\Application\UI\Presenter
{
}

Заключение

Атрибут #[Requires] даёт вам большую гибкость и контроль над тем, как обращаются к вашим страницам. С помощью простых, но мощных правил вы можете повысить безопасность и правильность работы приложения. Как видите, использование атрибутов в Nette может не только упростить вашу работу, но и защитить её.