The Pokedex Project (2)- Codificando nuestros Puntos de Entrada de la aplicación.

Chris Lopez
6 min readSep 7, 2020

--

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

  1. - 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.

--

--

Chris Lopez
Chris Lopez

Written by Chris Lopez

Laravel, PHP, Python, Js, Vue Developer

No responses yet