Rutes i controladors
Una de les tasques que ha de realitzar el nostre controlador frontal és enrutar, és a dir, analitzar la sol·licitud de l'usuari i activar el controlador associat. Actualment el nostre "router" és molt bàsic i simplement fa una inclusió de la pàgina que volem activar.
Una ruta estarà vinculada a un URL que ens donarà accés a un controlador. És important, a l'hora de definir les rutes seguir una convenció, nosaltres seguirem aquesta: REST resource naming.
Així doncs, el router s'ha d'encarregar de:
- Gestionar la taula de rutes, on es vincula cada ruta a un controlador.
- Resoldre les sol·licituds, és a dir, buscar el controlador vinculat a la ruta.
El router
Per a disposar d'un component d'enrutament farem ús del component Routing
de Symfony.
composer require symfony\routing
Aquest component ens permetrà definir les rutes de la nostra aplicació:
// routes.php
use Symfony\Component\Routing;
$routes = new Routing\RouteCollection();
$routes->add('index', new Routing\Route('/'));
$routes->add('login', new Routing\Route('/login'));
return $routes;
El primer paràmetre és el nom que li volem donar a la ruta, aquest nom ha de ser
únic. El segon paràmetre és un objecte de tipus Route
.
Les dues primeres rutes simplement defineixen dos URL vàlids de la nostra aplicació.
return
en fitxers
Fent return $routes;
en el fitxer que serà inclòs aconseguim un comportament
semblant al de les funcions, com veurem a continuació.
El controlador frontal quedaria així:
// public/index.php
require_once __DIR__.'/../bootstrap.php';
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing;
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../routes.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
try {
extract($matcher->match($request->getPathInfo()), EXTR_SKIP);
include sprintf(__DIR__.'/../%s.php', $_route);
} catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred', 500);
}
$response->send();
Serà el mètode match
de la classe UrlMatcher
el que s'encarregarà de
buscar el controlador associat a l'URL sol·licitat.
En usar com a nom de la ruta el nom del fitxer sense .php
ens permet fer la inclusió
usant el nom.
A més, incloure la funcionalitat dintre d'un bloc try...catch
ens
permetrà poder gestionar les excepcions globalment a l'aplicació.
Els controladors
Com hem vist en la introducció, el controlador és el component del patró MVC que s'encarrega de rebre la sol·licitud de l'usuari, interactuar en el model i la vista i retornar la informació al client.
Els controladors solen implementar-se en una classe (ara els tenim en fitxers), els mètodes de la qual, són les diferents accions que es poden realitzar.
Per exemple:
namespace App\Controller;
...
class DefaultController
{
public function index(Request $request): Response
{
$logger = Registry::get("logger");
$tweetRepository = Registry::get(TweetRepository::class);
$tweets = $tweetRepository->findAll();
$logger->info("S'ha consultat la pagina", $tweets);
$content = View::render('index', 'default', compact('tweets'));
return new Response($content);
}
}
Per poder activar els controladors caldrà introduir els següents canvis en la definició de rutes:
// src/routes.php
use Symfony\Component\Routing;
$routes = new Routing\RouteCollection();
$routes->add('index', new Routing\Route('/',
['_controller'=>'App\Controller\DefaultController::index']));
$routes->add('login',
new Routing\Route(
path: '/login',
defaults: ['_controller'=>'App\Controller\DefaultController::login'],
methods: ["GET"])
);
$routes->add('login_process',
new Routing\Route(
path:'/login',
defaults: ['_controller'=>'App\Controller\DefaultController::loginProcess'],
methods: ["POST"])
);
return $routes;
Per a definir els controladors associats a cada URL usarem el segon paràmetre (defaults
) del
constructor de Route
, associant la clau _controller
al controlador. A més, també
podem restringir les rutes a mètodes concrets. En aquest cas l'URL /login
activarà un controlador
diferent si la sol·licitud és POST
o GET
.
Parametres amb nom
Recorda que a partir de PHP 8.0 podem passar els paràmetres pel seu nom.
El nostre controlador frontal quedarà així:
require_once __DIR__ . '/../bootstrap.php';
//...
$request = Request::createFromGlobals();
$routes = include __DIR__.'/../routes.php';
$context = new Routing\RequestContext();
$context->fromRequest($request);
$matcher = new Routing\Matcher\UrlMatcher($routes, $context);
$controllerResolver = new ControllerResolver();
$argumentResolver = new ArgumentResolver();
try {
$request->attributes->add($matcher->match($request->getPathInfo()));
$controller = $controllerResolver->getController($request);
$arguments = $argumentResolver->getArguments($request, $controller);
$response = call_user_func_array($controller, $arguments);
} catch (Routing\Exception\ResourceNotFoundException $exception) {
$response = new Response('Not Found', 404);
} catch (Exception $exception) {
$response = new Response('An error occurred: ' . $exception->getMessage(), 500);
}
$response->send();
Les classes ControllerResolver
i ArgumentResolver
s'encarreguen d'esbrinar,
a partir de la sol·licitud, el controlador i els seus arguments, respectivament.
Formen part del component http-kernel
de Symfony que caldrà instal·lar:
composer require symfony/http-kernel
La funció call_user_func_array
executa el controlador amb els seus arguments.
Reflection (o reflexió)
La reflexió és quan un objecte és capaç d'examinar-se de forma retrospectiva per informar dels seus mètodes, propietats o classes durant l'execució del script.