За последние 24 часа нас посетил 85321 программист и 4735 роботов. Сейчас ищут 1813 программистов ...

Поясните Pipeline, а то я поломался

Тема в разделе "PHP для профи", создана пользователем Вероломство, 7 авг 2023.

  1. Вероломство

    Вероломство Активный пользователь

    С нами с:
    19 июн 2017
    Сообщения:
    626
    Симпатии:
    24
    Исходники:

    PHP:
    1. class Handler
    2. {
    3.     public function handle(Request $request): Response
    4.     {
    5.         return new Response(__METHOD__);
    6.     }
    7. }
    8.  
    9. class Pipeline
    10. {
    11.     private $handler;
    12.     private $middleware;
    13.  
    14.     public function __construct(Handler $handler, array $middleware)
    15.     {
    16.         $this->handler = $handler;
    17.         $this->middleware = $middleware;
    18.     }
    19.  
    20.     public function handle(Request $request): Response
    21.     {
    22.         $middleware = array_shift($this->middleware);
    23.  
    24.         if ($middleware !== null) {
    25.             return $middleware->handle($request, [$this, 'handle']);
    26.         }
    27.  
    28.         return $this->handler->handle($request);
    29.     }
    30. }
    31.  
    32. class Application
    33. {
    34.     private $middleware;
    35.  
    36.     public function __construct(array $middleware)
    37.     {
    38.         $this->middleware = $middleware;
    39.     }
    40.  
    41.     public function handle(Request $request): Response
    42.     {
    43.         return (new Pipeline(new Handler(), $this->middleware))->handle($request);
    44.     }
    45. }
    Классы для массива middleware

    PHP:
    1. class A
    2. {
    3.     public function handle(Request $request, callable $next): Response
    4.     {
    5.         return $next($request);
    6.     }
    7. }
    8.  
    9. class B
    10. {
    11.     public function handle(Request $request, callable $next): Response
    12.     {
    13.         return $next($request);
    14.     }
    15. }
    Собираем

    PHP:
    1. $application = new Application([
    2.     new A(),
    3.     new B()
    4. ]);
    5.  
    6. $response = $application->handle(new Request());
    7.  
    8. $response->send(); // Handler::handle
    Теперь, что мне непонятно?

    Если я сделаю, например:

    PHP:
    1. class A
    2. {
    3.     public function handle(Request $request, callable $next): Response
    4.     {
    5.         $response = $next($request);
    6.  
    7.         $response->setBody(__METHOD__);
    8.  
    9.         return $response;
    10.     }
    11. }
    12.  
    13. class B
    14. {
    15.     public function handle(Request $request, callable $next): Response
    16.     {
    17.         $response = $next($request);
    18.  
    19.         $response->setBody(__METHOD__);
    20.  
    21.         return $response;
    22.     }
    23. }
    То я имею в выводе -
    Код (Text):
    1. A::handle
    То есть в этом месте ломается мозг: отрабатывают ВСЕ классы массива middleware (A, B) и основной класс - Handler, я для теста в каждом из них задаю тело ответа, НО в ответе я имею содержимое тела, установленное в первом классе массива, то есть ладно там массив, но ведь в Pipeline по окончанию массива отрабатывает Handler, а в нём явно указано:

    PHP:
    1. class Handler
    2. {
    3.     public function handle(Request $request): Response
    4.     {
    5.         return new Response(__METHOD__);
    6.         // тут мы создаём НОВЫЙ объект, то есть в ответе должно быть - Handler::handle
    7.     }
    8. }
    Нифига я не врублюсь: почему отработали все классы И каждый по порядку обработки установил новое значение тела ответа, НО в ответе у меня содержимое, установленное в классе, который идёт первым?

    )))

    p.s. оно вроде и правильно, если что-то изменилось в ответе, то оно и должно вывестись только то, которое первое изменилось, но я тупо не пойму, как его не перезаписывают остальные хендлеры, я специально в каждом создал разное содержимое тела ответа )))

    они ведь отрабатывают - ПОСЛЕ
     
  2. Вероломство

    Вероломство Активный пользователь

    С нами с:
    19 июн 2017
    Сообщения:
    626
    Симпатии:
    24
    блин пока вопрос создавал по ходу догнал: Response создаётся в конце работы, доступ к нему идёт рекурсивно через $next, то есть объект ответа - ОДИН, мы меняем его содержимое типа как по ссылке на него через $next, получается что мы получаем ответ шиворот-навыворот)))

    то есть в ответе последнего класса Pipeline будет тело наипервейшего класса массива, где оно установлено, изначально оно установлено в основном Handler, а потом мы его меняем в любом middleware

    разобрался, кому не лень можете поделиться, а так уже не нужно
     
    #2 Вероломство, 7 авг 2023
    Последнее редактирование: 7 авг 2023
  3. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.129
    Симпатии:
    1.223
    Адрес:
    там-сям
    PHP:
    1. class Handler
    2. {
    3.     public function handle(Request $request): Response
    4.     {
    5.         return new Response(__METHOD__);
    6.         // тут мы создаём НОВЫЙ объект, то есть в ответе должно быть - Handler::handle
    7.     }
    8. }
    Непонятно написал. После return у тебя уже ничего не может выполняться. В ответе должен быть объект класса Response.

    PHP:
    1. $response = $application->handle(new Request());
    2.  
    3. $response->send(); // Handler::handle
    В переменной $response будет объект, который ты породил в Handler и потом нигде не изменяешь.

    При такой организации, каждый из цепочки "посредников" получает управление как до, так и после рождения "ответа". Они выполняются не просто по очереди, а вложены как матрёшки. В зависимости от каких-то условий, "посредники" могут прервать выполнение, или модифицировать ответ.

    В твоем примере не видно чего ты хотел добиться. Никто ничего не делает и логи не ведутся.
     
    Вероломство нравится это.
  4. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.129
    Симпатии:
    1.223
    Адрес:
    там-сям
    Поясню про до и после:
    PHP:
    1. public function handle(Request $request, callable $next)
    2. {
    3.     // place to do something before Handler, break an exception for example.
    4.     $response = $next($request);
    5.     // place to do something after Handler, modify or replace response object, for example.
    6.     return $response;
    7. }
    и если бы был отладочный вывод, ты мог бы увидеть такое:
    Код (Text):
    1.  
    2. A before
    3. B before
    4. Handler::handle()
    5. B after
    6. A after
    7. Response::send()
     
    Вероломство нравится это.
  5. Вероломство

    Вероломство Активный пользователь

    С нами с:
    19 июн 2017
    Сообщения:
    626
    Симпатии:
    24
    @artoodetoo ага, я как раз в разных вариантах стал колупать и понял суть, там before и after получается, то есть в Handler мы просто ссылаемся на Response-объект и по дефолту делаем - ДО

    спс
    --- Добавлено ---
    @artoodetoo а можно такой вопрос: вот есть массив middleware, а можно ли в каком-то из классов этого массива, пополнить массив middleware?

    потому что мы array_shift же используем, а значит наш массив middleware - может мутировать, он не иммутабельный

    есть ли смысл в этом направлении пилить?

    хочу типа в каком-то хендлере получать объект в котором есть свои предназначенные для него middleware, добавлять их а общий массив middleware, чтобы они выпонились в общем массиве

    так возможно?

    то есть во время обработки массива пополнять его и продолжать его обрабатывать
     
    #5 Вероломство, 7 авг 2023
    Последнее редактирование: 7 авг 2023
  6. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.841
    Симпатии:
    1.338
    Адрес:
    Лень
    Как же я люблю этот Symfony со своими Bundles :cool:
     
  7. artoodetoo

    artoodetoo Суперстар
    Команда форума Модератор

    С нами с:
    11 июн 2010
    Сообщения:
    11.129
    Симпатии:
    1.223
    Адрес:
    там-сям
    Реализовать конечно можно всё, в Laravel есть Controller middleware, например. Я не смотрел как это реализовано.
    Твой конструктор Pipeline наверное мог бы делать array_unshift или array_push к массиву "посредников" если переданный ему объект Handler имеет непустое свойство $middleware. Такое приходит на ум.
     
  8. Вероломство

    Вероломство Активный пользователь

    С нами с:
    19 июн 2017
    Сообщения:
    626
    Симпатии:
    24
    PHP:
    1. class App extends RouteCollector
    2. {
    3.     private $container;
    4.     private $middleware = [];
    5.  
    6.     public function __construct(Container $container = null)
    7.     {
    8.         $this->container = $container ?? new Container();
    9.     }
    10.  
    11.     public function getContainer(): Container
    12.     {
    13.         return $this->container;
    14.     }
    15.  
    16.     public function addMiddleware(string $class): void
    17.     {
    18.         $this->middleware[] = $class;
    19.     }
    20.  
    21.     public function mergeMiddleware(array $middleware): void
    22.     {
    23.         $this->middleware = array_merge($this->middleware, $middleware);
    24.     }
    25.  
    26.     public function __invoke(Request $request): Response
    27.     {
    28.         $middleware = array_shift($this->middleware);
    29.  
    30.         if ($middleware !== null) {
    31.             return (new $middleware)($request, $this);
    32.         }
    33.  
    34.         /** @var Result $result */
    35.         $result = $this->container->get(Result::class);
    36.  
    37.         return $this->container->get($result->getHandler())($request, new Response(), ...$result->getParameters());
    38.     }
    39. }
    40.  
    41. class RoutingMiddleware
    42. {
    43.     public function __invoke(Request $request, App $app): Response
    44.     {
    45.         foreach ($app->getRoutes() as $route) {
    46.  
    47.             if ($route->getMethods() && !in_array($request->getMethod(), $route->getMethods(), true)) {
    48.                 continue;
    49.             }
    50.  
    51.             $pattern = preg_replace('~{([a-z]+):([^}]+)}~', '(?P<$1>$2)', $route->getPattern());
    52.  
    53.             if (preg_match("~^$pattern$~", $request->getPath(), $matches)) {
    54.                 $app->mergeMiddleware($route->getMiddleware()); // добавили массив во время выполнения
    55.  
    56.                 $app->getContainer()->set(Result::class, function () use ($matches, $route) {
    57.                     return new Result(
    58.                         $route->getHandler(),
    59.                         array_values(array_filter($matches, 'is_string', ARRAY_FILTER_USE_KEY))
    60.                     );
    61.                 });
    62.  
    63.                 return $app($request);
    64.             }
    65.         }
    66.  
    67.         throw new Exception("Не найден {$request->getMethod()}-маршрут: {$request->getPath()}");
    68.     }
    69. }
    70.  
    71. $app = new App();
    72.  
    73. $app->get('/', HomeAction::class)
    74.     ->addMiddleware(FirstRouteMiddleware::class)
    75.     ->addMiddleware(SecondRouteMiddleware::class);
    76.  
    77. $app->addMiddleware(AppMiddleware::class);
    78. $app->addMiddleware(RoutingMiddleware::class);
    79.  
    80. $response = $app(new Request());
    81.  
    82. $response->send();
    выхлоп

    Код (Text):
    1. 'App\Support\Middleware\AppMiddleware::__invoke'
    2. 'App\Support\Middleware\FirstRouteMiddleware::__invoke'
    3. 'App\Support\Middleware\SecondRouteMiddleware::__invoke'
    4. 'App\Actions\Guest\HomeAction::__invoke'
    годиться или фуйню порю? :rolleyes:
     
  9. MouseZver

    MouseZver Суперстар

    С нами с:
    1 апр 2013
    Сообщения:
    7.841
    Симпатии:
    1.338
    Адрес:
    Лень
    Во первых почему Мозги App прилеплены с задницей RouteCollector ?

    Давай торты будут делать со вкусом туалетной бумаги.
    Тортик Zewa - мягкое как пердышко.