poniedzia艂ek, 25 maja 2020

Analiza Symfony - HttpKernel::handleRaw()

private function handleRaw(Request $request, int $type = self::MASTER_REQUEST): Response
{
    $this->requestStack->push($request);

    // request
    $event = new RequestEvent($this, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::REQUEST);

    if ($event->hasResponse()) {
        return $this->filterResponse($event->getResponse(), $request, $type);
    }

    // load controller
    if (false === $controller = $this->resolver->getController($request)) {
        throw new NotFoundHttpException(sprintf('Unable to find the controller for path "%s". The route is wrongly configured.', $request->getPathInfo()));
    }

    $event = new ControllerEvent($this, $controller, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
    $controller = $event->getController();

    // controller arguments
    $arguments = $this->argumentResolver->getArguments($request, $controller);

    $event = new ControllerArgumentsEvent($this, $controller, $arguments, $request, $type);
    $this->dispatcher->dispatch($event, KernelEvents::CONTROLLER_ARGUMENTS);
    $controller = $event->getController();
    $arguments = $event->getArguments();

    // call controller
    $response = $controller(...$arguments);

    // view
    if (!$response instanceof Response) {
        $event = new ViewEvent($this, $request, $type, $response);
        $this->dispatcher->dispatch($event, KernelEvents::VIEW);

        if ($event->hasResponse()) {
            $response = $event->getResponse();
        } else {
            $msg = sprintf('The controller must return a "Symfony\Component\HttpFoundation\Response" object but it returned %s.', $this->varToString($response));

            // the user may have forgotten to return something
            if (null === $response) {
                $msg .= ' Did you forget to add a return statement somewhere in your controller?';
            }

            throw new ControllerDoesNotReturnResponseException($msg, $controller, __FILE__, __LINE__ - 17);
        }
    }

    return $this->filterResponse($response, $request, $type);
}
Zale偶no艣ci klasy HttpKernel do innych komponent贸w Symfony


1. Zg艂oszenie zdarzenia 


    Metoda przyjmuje jako pierwszy parametr obiekt 偶膮dania klasy Request pochodz膮cej z komponentu HttpFoundation, zwracaj膮c przy tym obiekt Response z tego samego komponentu.

    Parametr Request w pierwszej kolejno艣ci wrzucany jest na stos innych 偶膮da艅, kt贸ry jest instancj膮 klasy HttpFoundation\RequestStack

    Nast臋pnie zg艂aszane jest zdarzenie 偶膮dania za pomoc膮 wstrzykni臋tego do konstruktora klasy HttpKernel,  obiektu implementuj膮cego EventDispatcherInterface pochodz膮cego z zewn臋trznego komponentu EventDispatcher. Zdarzenie symbolizowane jest przez instancj膮 klasy HttpKernel\Event\RequestEvent, a powi膮zana z nim nazwa zdarzenia to kernel.request. W przypadku gdy do aktualnego 偶膮dania istnieje powi膮zana odpowied藕, metoda w tym momencie j膮 zwr贸ci.


2. Dobieranie kontrolera


    Za to zadanie odpowiedzialny jest obiekt ControllerResolverInterface (Constructor Injection) b臋d膮cy cz臋艣ci膮 komponentu j膮dra. Metoda getController przyjmuje jako parametr obiekt Request i w przypadku powodzenia operacji zwraca zmienn膮 typu callable (akcja kontrolera) b膮d藕 - gdy nie zostanie sparowany odpowiedni kontroler - bool'owski false.  W klasie konkretnej resolver'a ControllerResolver,  znajduj膮 si臋 szczeg贸艂y implementacyjne dotycz膮ce doboru kontrolera, zagadnienie to wymaga rozszerzenia w oddzielnym wpisie. W przypadku gdy:

  • nie dojdzie do sparowania kontrolera z obiektem 偶膮dania, wyrzucony zostanie wyj膮tek HttpKernel\Exception\NotFoundHttpException
  • \InvalidArgumentException b臋dzie wyrzucony gdy dojdzie do wewn臋trznych problem贸w resolver'a np. gdy sparowana akcja kontrolera nie b臋dzie typu callable,
    Po pomy艣lnym wybraniu kontrolera na podstawie obiektu Request, zostanie zg艂oszone zdarzenie kernel.controller za po艣rednictwem  instancje EventDispatcher przechowywanej w chronionym polu $dispatcher

Ewentualna podmiana kontrolera


    Jako 偶e mamy mo偶liwo艣膰 nas艂uchiwania na zdarzenie kernel.controller (KernelEvents::CONTROLLER)  oraz dost臋p do utworzonego obiektu Event\ControllerEvent w user land'owych klasach Listener/Subscriber, kt贸ry posiada metod臋 publicznego interfejsu function setController(callable $controller): void, w ciele omawianej metody handleRaw musi znajdowa膰 si臋 ewentualne pobranie innej instancji kontrolera:

$event = new ControllerEvent($this, $controller, $request, $type);
$this->dispatcher->dispatch($event, KernelEvents::CONTROLLER);
$controller = $event->getController();



3. Pobranie argument贸w akcji


    Za to zadanie odpowiedzialny jest wstrzykni臋ty do konstruktora klasy HttpKernel obiekt implementuj膮cy interfejs ArgumentResolverInterface (nale偶膮cy do tego samego komponentu). W tym przypadku jako argumenty nale偶膮cej do niego metody getArguments, przekazywana jest instancja klasy Request oraz callable $controllerLogika ukryta za t膮 warstw膮 abstrakcji nie b臋dzie tutaj opisywana poniewa偶 zas艂uguje na osobny wpis - wa偶ne jest, 偶e metoda zwraca tablic臋 ze wszystkimi zadeklarowanymi w akcji parametrami.

    Nast臋pne kroki procesu przebiegaj膮 podobnie co w przypadku kontrolera: 

  1. utworzenie obiektu zdarzenia ControllerArgumentsEvent
  2. wys艂anie go za po艣rednictwem EventDispatcher'a z przypisan膮 nazw膮 kernel.controller_arguments 
  3. pobranie do zmiennych $controller i $arguments prawdopodobnie zmienionych instancji z obiekt贸w typu *Event



4. Wywo艂anie akcji kontrolera


// call controller
$response = $controller(...$arguments);
   
    Posiadaj膮c callable akcji oraz wszystkie wymagane argumenty, nic nie stoi na przeszkodzie by pozyska膰 obiekt Response b臋d膮cy zwrotk膮 ka偶dej akcji kontrolera w Symfony. Mog艂aby do tego procesu zosta膰 u偶yta funkcja call_user_func_array() jednak autorzy zdecydowali si臋 na wykorzystanie Operatora Variadic.


5. Zwr贸cenie obiektu Response


    W przypadku gdy zmienna $response nie przechowuje obiektu klasy Response, emitowane jest zdarzenie kernel.view. W tym miejscu, algorytm oczekuje, 偶e istniej膮 nas艂uchiwacze, kt贸re zapewni膮 obiektowi zdarzenia ViewEvent, po偶膮dan膮 przez niego instancje klasy Response. Je偶eli takowa nie zostanie wykryta, prawdopodobnie developer nie zwr贸ci艂 nic w akcji kontrolera, czego konsekwencj膮 b臋dzie wyrzucenie wyj膮tku ControllerDoesNotReturnResponseException. W procesie implementacji mo偶na uchroni膰 si臋 przed tego typu problemem, deklaruj膮c typ zwracany przez akcj臋. Dzi臋ki temu w przypadku ewentualnego pomini臋cia return'a, zostaniemy o tym poinformowani ju偶 podczas kompilacji.

    Gdy mamy dost臋p do obiektu Response,  metoda go zwraca i emituje - po艣rednio, przez metody prywatne - dwa zdarzenia: kernel.response oraz kernel.finish_request (艣ci膮gaj膮c przy tym instancje Request ze stosu RequestStack). 


Podsumowanie wszystkich emitowanych zdarze艅 w metodzie handleRaw