The Pokedex Project (2)- Codificando nuestros Puntos de Entrada de la aplicación.
Al momento de crear nuestra aplicación surgen varias necesidades detectadas al momento:
- Procesar las peticiones del cliente(Request) por medio de la URL que escriba en nuestro proyecto; esto incluye los parámetros permitidos a procesar.
- Procesar y configurar las rutas permitidas en nuestra aplicación. Al igual que procesar sus parámetros del Request ligadas a la ruta configurada. Por lo regular ejecutarán un método de algún Controller indicado o un closure(función).
- Procesar el Request y mandar la respuesta al cliente(Response).
- Tener un punto de entrada que encapsule estos componentes(Routes y Request), o al menos que los mande a llamar cuando se inicie la aplicación.
Entonces nuestros objetos principales serán: App, Route, Request, Response. Una vez teniendo en cuenta esto codificaremos dichos objetos.
Creando los objetos: App, Route, Request y Response
- - Navegamos a la carpeta Utils(src/Utils), creamos el directorio Routing(src/Utils/Routing) y creamos el archivo Route.php:
Copiar código:cd src/Utils && mkdir Routing && cd Routing && touch Route.php
2.- Editamos el archivo Route.php(src/Utils/Routing/Route.php):
Copiar código:<?php
namespace Utils\Routing;
use Utils\Request;
use Utils\Response;
class Route
{
protected $routes = [];
protected $request;
public function __construct(Request $request)
{
$this->setRequest($request);
}
public function add(string $uriString, $closure)
{
array_push($this->routes, [
'uri' => $uriString,
'closure' => $closure
]);
}
public function run()
{
$response = [
'uri' => '',
'closure' => false,
];
foreach ($this->routes as $route) {
if ($this->checkIfRoutesMatch($route['uri'])) {
$response = $route;
break;
}
}
(new Response($response['closure']))->send($this->request);
}
public function checkIfRoutesMatch($routeUri)
{
$isRoutesMatch = false;
$uriPattern = $this->request->getUriPattern($routeUri);
$routeAndUriMatch = $this->request->isRouteUrisMatch($uriPattern);
if ($routeAndUriMatch->result) {
$this->request->processParams($routeAndUriMatch->matches, $routeUri);
$isRoutesMatch = true;
}
return $isRoutesMatch;
}
/**
* @return mixed
*/
public function getRequestUri()
{
return $this->requestUri;
}
/**
* @param mixed $uri
*/
public function setRequestUri($uri): void
{
$this->requestUri = $uri;
}
/**
* @return mixed
*/
public function getRequest()
{
return $this->request;
}
/**
* @param mixed $request
*/
public function setRequest($request): void
{
$this->request = $request;
}
}
Como podemos ver, en la clase Route controlamos lo que nos mande por la URL(principalmente parámetros y rutas) y lo procesamos(método checkIfRoutesMatch), después de procesar la ruta lo mandamos al cliente(método send de la clase Response).
3.- Nos regresamos al directorio Utils y Creamos el archivo:
Copiar código:cd .. && touch Request.php
4.- Editamos el archivo Request.php(src/Utils/Request.php):
Copiar código:<?php
namespace Utils;
class Request
{
const PARAMETER_PATTERN = '/:([^\/]+)/';
const PARAMETER_REPLACEMENT = '(?<\1>[^/]+)';
protected $uri;
protected $params;
public function __construct(string $uri)
{
$this->setUri($uri);
}
public function isRouteUrisMatch(string $uriPattern)
{
return (object)[
'result' => (preg_match($uriPattern, $this->getUri(), $matches)),
'matches' => $matches,
];
}
public function getUriPattern(string $routeUri)
{
$uriPattern = preg_replace(self::PARAMETER_PATTERN, self::PARAMETER_REPLACEMENT, $routeUri);
$uriPattern = str_replace('/', '\/', $uriPattern);
$uriPattern = '/^' . $uriPattern . '\/*$/s';
return $uriPattern;
}
public function processParams($uriMatches, $routeUri)
{
preg_match_all(self::PARAMETER_PATTERN, $routeUri, $parameterNames);
$paramNames = array_flip($parameterNames[1]);
$this->setParams(array_intersect_key($uriMatches, $paramNames));
}
/**
* @return mixed
*/
public function getUri()
{
return $this->uri;
}
/**
* @param mixed $uri
*/
public function setUri($uri): void
{
$this->uri = $uri;
}
/**
* @return mixed
*/
public function getParams()
{
return $this->params;
}
/**
* @param mixed $params
*/
public function setParams($params): void
{
$this->params = $params;
}
}
Esta clase se encargara de procesar todos los parámetros que le mandemos a nuestra URL(metodo processParams), anteriormente obtiene el patrón valido de nuestra ruta definida en /routes/web.php(método getUriPattern) y por ultimo regresa un objeto con el resultado y las coincidencias de la ruta(metodo isRouteUrisMatch).
5.- Creamos y editamos el archivo Response.php(src/Utils/Response.php):
Copiar código:<?php
namespace Utils;
class Response
{
protected $closure;
public function __construct($closure)
{
$this->setClosure($closure);
}
public function send($request)
{
if ($this->closure['closure_type'] == 'string') {
echo $this->closure['closure'];
} elseif ($this->closure['closure_type'] == 'array') {
echo json_encode($this->closure['closure']);
} elseif ($this->closure['closure_type'] == 'closure' || $this->closure['closure_type'] == 'controller') {
$this->_executeClosure($request);
} else {
header("HTTP/1.0 404 Not Found");
exit('<h3>404 - Not Found</h3>');
}
}
private function _executeClosure($request)
{
$closure = $this->closure['closure'];
$parameters = $request->getParams();
$isControllerClosure = ($this->closure['closure_type'] == 'controller');
if ($isControllerClosure) {
$controller = $this->closure['object_info']['class_instance'];
$controllerMethod = $this->closure['object_info']['method'];
}
return ($isControllerClosure) ? $controller->$controllerMethod($parameters) : call_user_func_array($closure, $parameters);
}
/**
* @return mixed
*/
public function getClosure()
{
return $this->closure;
}
/**
* @param mixed $closure
*/
public function setClosure($closure): void
{
$controllerNamespace = '\App\Controllers\\';
$closureType = 'unknown';
$objectInfo = [];
if (is_string($closure)) {
$closureType = 'string';
$closureArr = explode('@', $closure);
if (count($closureArr) > 1) {
$closureType = 'controller';
$closureArr[0] = $controllerNamespace . $closureArr[0];
$controllerClass = new $closureArr[0];
$contollerMethod = $closureArr[1];
$objectInfo = [
'class_instance' => $controllerClass,
'method' => $contollerMethod,
];
}
}
if (is_array($closure)) {
$closureType = 'array';
}
if (is_callable($closure)) {
$closureType = 'closure';
}
$this->closure = [
'closure' => $closure,
'closure_type' => $closureType,
'object_info' => $objectInfo,
];
}
}
Esta clase regresa la respuesta después de haber procesado lo que mandamos en la URL, prácticamente identifica que tipo de ruta es(definida en routes/web.php): String, Array, Closure(Controller o Function); si no es ningún tipo antes mencionado regresara un error 404.
6.- Codificaremos nuestro punto de entrada, el cual inicializara nuestras clases antes creadas, creamos el archivo App.php(src/Utils/App.php):
Copiar código:<?php
namespace Utils;
use Utils\Routing\Route;
class App
{
public function __invoke(Route $route)
{
$route->run();
}
}
Ya teniendo nuestros objetos principales(App, Route, Request y Response), configuraremos nuestras rutas en routes/web.php.
Configurando nuestras rutas
Editamos routes/web.php:
Copiar código:<?php
$route = new \Utils\Routing\Route(new \Utils\Request($_SERVER['REQUEST_URI']));
$route->add('/', function () {
echo '<h2>Hello Pokedex!</h2>';
});
Aquí simplemente instanciaremos la clase Route y le pasamos como parámetro inyectado la clase Request(que recibe como parámetro en el constructor la super global $_SERVER). Después usamos el método add() de Route el cual el segundo parámetro puede ser: string, array o closure(function o controller). En este caso practico para probar solamente usaremos un closure que regrese un tag h2 de HTML.
Armando nuestro punto de entrada para su ejecución
Ahora modificaremos el archivo bootstrap.php(/bootstrap.php):
Copiar código:<?php
require __DIR__ . '/routes/web.php';
(new \Utils\App())($route);
En este archivo incluimos el archivo de configuración de nuestras rutas(routes/web.php), instanciamos la clase App que es nuestro punto de entrada y le pasamos la variable $route que es la instancia de Route adentro del archivo routes/web.php.
Ejecutando dump-autoload para carga de las clases
Por ultimo ejecutaremos dump-autoload de composer para que vaya agregando nuestras clases al autoload:
Copiar código:composer dump-autoload
Codificando ya nuestros puntos de entrada, probamos en el explorador y se debería de ver algo así:
Si queremos agregar otras rutas simplemente podemos modificar routes/web.php y codificarlas así:
Copiar código:$route->add('/str', 'Pikachu!!!');
$route->add('/arr', ['pokemon' => 'Charizard']);$route->add('/pokemon/:id', 'PokedexController@index');
Con esto ya tenemos un sistema que controla rutas y parámetros y nuestro punto de entrada en la aplicación, todo esto con PHP Vanilla sin usar framework alguno.
Pueden consultar el código de esta publicación en este repositorio: https://github.com/krsrk/pokedex-vanilla-php
La siguiente publicación esta lista y la puedes revisar, en ella empezaremos a codificar nuestro sistema de templates para la vista:
https://link.medium.com/rNvJ5jDjH9
Si les gusto la publicación denle claps, likes y comentarios.