Вынос кода в Middleware

Использование посредников middleware для выноса повторяющегося инфраструктурного кода из контроллеров. Очистка ввода и централизованная обработка доменных исключений.

  • 00:00:22 Дублирование парсинга JSON
  • 00:03:41 Философия middleware
  • 00:06:01 Подключения BodyParsingMiddleware
  • 00:07:55 Порядок подключения в Slim
  • 00:09:21 Упрощение контроллера
  • 00:09:56 Автоочистка пробелов из ввода
  • 00:15:06 Очистка пустых файлов
  • 00:18:51 DomainExceptionHandler для доменных исключений
  • 00:23:20 Логирование доменных ошибок
  • 00:26:40 Тесты подтверждения токена
  • 00:29:03 Контроллер подтверждения регистрации
  • 00:31:08 Отсутствие удобной валидации ввода
Скрытый контент (код, слайды, ...) для подписчиков. Открыть →
Дмитрий Елисеев
elisdn.ru
Комментарии (13)
Arunas

Спасибо.

Ответить
Denis

Добрый день.

А как быть с middleware, если мне надо для какого - то опредленного контроллера добавить что - то специфическое? Какую - то дополнительную обработку.

И еще вопрос - Вот получили мы запрос в коноллере и ручками теперь собираем все в команду. А можно как - то автоматизировать данный процесс? Допустим, есть такие вещи, как JmsSerializer или подобный пакет от Симфони, можно данные вещи использовать для заполнения команды? Оно там даже должно немного ругать на неверно заполненные поля. Ведь бывают ситуации, когда с фронта приходит не 2 поля а 10 -15 , а то и 35 (если есть большая бизнес логика).

Ответить
Дмитрий Елисеев

Если для определённого, то привязывем через add() напрямую к маршруту или группе:

$app->get('/cabinet', CabinetAction::class)->add(AuthMiddleware::class);
Ответить
Дмитрий Елисеев

Да, можно автоматизировать заполнение через symfony/serializer.

Ответить
Алексей

Здравствуйте Дмитрий, спасибо как всегда и вопрос, а в Симфони такое можно сделать только через EventSubscriberInterface и отлавливать kernel request я правильно понимаю?

или есть возможность упростить там? Спасибо!

Ответить
Дмитрий Елисеев

Да, там нет middleware. Только через подписку на события kernel.controller и kernel.exception.

Ответить
uhamurad

Допустим, у нас возникла острая необходимость все-таки оставить значение одного из параметров запроса неизменным и не применять к нему мидлвар ClearEmptyInput (например, вводимый пользователь пароль может содержать пробел в любом месте). Как лучше всего поступить в данном случае, чтобы не вычеркивать мидлвар ClearEmptyInput из общего списка обработчиков в /api/config/middleware.php?

Мой вариант:

1) Создать объект, например, ClearEmptyInputRules, который содержит правила исключения параметров запроса из обработки: метод except будет добавлять исключение, а метод check проверять запрос на то, можно ли к нему применить фильтрацию

class ClearEmptyInputRules
{
	private array $exceptRules = [];

	public function except($method, $uri, $paramName) 
	{
	    $this->exceptRules[] = [$method, $uri, $paramName];
	}

	public function check(ServerRequestInterface $request, String $paramName) 
	{
		foreach ($this->exceptRules as $rule])
		{
			if (
				$request->getMenthod() == $rule[0]
				&& $request->getUri() == $rule[1]
				&& $paramName == $rule[2]
			)
			{
				return false;
			}
		}
		return true;
	}
}

2) В конфигах контейнера зависимостей вписать необходимые нам правила (например, в файл /api/config/common/inputFilters.php)

return [
	ClearEmptyInputRules::class => static function (ContainerInterface $container) {

	    $rules = new ClearEmptyInputRules();

	    $rules->except('POST', 'v1/api/auth/login', 'password');

	    return $rules;
	},
];

3) Добавить в класс ClearEmptyInput зависимость от ClearEmptyInputRules

class ClearEmptyInput implements MiddlewareInterface
{    
	
	//+++++++++
	private ClearEmptyInputRules $rules;

	//+++++++++
	public function __constructor(ClearEmptyInputRules $rules) 
	{
	    $this->rules = $rules;
	}

	public function process(ServerRequestInterface $request, RequestHandlerInterface $handler): ResponseInterface
	{

		//+++++++++
		if ($this->rules->check($request)){

			$request = $request
				->withParsedBody(self::filterStrings($request->getParsedBody()))
				->withUploadedFiles(self::filterFiles($request->getUploadedFiles()));

		}

		return $handler->handle($request);
	}   
	
}

(Код писал не самым оптимальным образом - скорее, чтоб он был наглядным)

Я в правильном направлении думаю?

Ответить
Дмитрий Елисеев

Да, можно по-разному:

  • Убрать из общего списка и применять к каждому нужному маршруту вручную.
  • Добавить поле $except (как сделано в Laravel) или $rules как у вас прямо в middleware.
  • Сделать обёртку Except или RulesMiddleware и оборачивать им любой оригинальный middleware.
Ответить
Dmitriy

Я заметил, что вы много где используете приватные статичные методы и потом обращаетесь к ним через self. Не могу понять в чем тут выигрыш перед обычными приватными методами и this. Подскажите пожалуйста.

Ответить
Дмитрий Елисеев

Обычный динамический метод привязан к каждому экземпляру объекта $this.

Статический же - это по сути просто функция, записанная в классе.

Так что если нам нужна функция, которой не нужен сам текущий объект $this, то нет особого смысла её делать динамическим методом.

Ответить
Vladimir

Добрый день.

Как правильнее будет реализовать функционал похожего DomainExceptionHandler, в случае если я хочу выводить slim flash, в котором просто нужно отловить ошибку, записать эту ошибку в сессию, и дальше выполнить все остальные действия в Action, отобразив при этом в рендере уже просто ошибку из сессии?

Ответить
Дмитрий Елисеев

Такой DomainExceptionHandler получится сделать только в API. Если же рендерятся представления с формой, то тогда придётся всё оборачивать в try-catch и заполнять сессию вручную. Например, в Symfony так:

class SignUpController extends AbstractController
{
    public function request(Request $request, Handler $handler): Response
    {
        $command = new Command();
        $form = $this->createForm(Form::class, $command);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            try {
                $handler->handle($command);
                $this->addFlash('success', 'Check your email.');
                return $this->redirectToRoute('home');
            } catch (\DomainException $e) {
                $this->errorHandler->handle($e);
                $this->addFlash('error', $e->getMessage());
            }
        }

        return $this->render('auth/signup.html.twig', [
            'form' => $form->createView(),
        ]);
    }
}
Ответить
Vladimir

понял, спасибо большое

Ответить
Зарегистрируйтесь или войдите чтобы оставить комментарий

Или войти через:

Yandex
MailRu
GitHub
Google