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.