Construye una micro-app MVC con Symfony

Hoy aplicaremos algunas mejoras a la agenda PHP basada en archivos CSV que comenzamos a desarrollar en el curso de programación PHP guay. En el último de los tres vídeos que explican cómo programar la aplicación proponíamos a los alumnos apuntados al curso que mejoraran la seguridad y la escalabilidad de la app poniendo un Front Controller.

Symfony

Recapitulemos un poco…

Si recién has aterrizado en este post vía Google, te recuerdo que en el curso de programación PHP guay hemos programado una agenda CSV en estas tres entregas (incluyen video):

  1. ¡Aprende a programar 1 agenda CSV con PHP!
  2. Continuamos programando 1 agenda CSV con PHP
  3. Felicidades, ¡ya terminamos la programación de nuestra agenda CSV con PHP!

Sobre los frameworks de desarrollo

Al poco tiempo de proponer la mejora nos dimos cuenta de que el nivel técnico de la misma era alto para la mayoría de personas apuntadas en PHP guay. Si este es tu caso, ¡no te agobies! Quizás nos hemos pasado un poco.

Si tu nivel de PHP es básico o intermedio no hace falta que entiendas exactamente todo lo que explicaremos hoy. Entender bien todo esto requiere tiempo de estudio y práctica. Sin embargo, si lo lees por encima, este ejercicio te puede servir para acercarte al mundo de los frameworks PHP desde una perspectiva generalista.

¿Sabías que a medio y largo plazo los desarrolladores profesionales terminan trabajando con frameworks? Te recomiendo que eches un vistazo a este videotutorial donde explico qué es un framework.

De una agenda PHP a un micro-framework PHP

Vamos a ensamblar nuestra agenda PHP en un micro-framework construido con algunos componentes de Symfony. Por si no lo sabes, Symfony es un framework PHP formado por componentes desacoplados y autónomos, así que llegado el caso siempre puedes tomar un componente Symfony y ponerlo en tu aplicación web para solucionar un problema concreto.

Lo primero que tenemos que hacer es poner el Front Controller. Por cierto, puedes consultar la solución Front Controller de nuestra agenda PHP en este post que publiqué en El Baúl del Programador. Como puedes ver, poner el front controller implica que primero debemos crear la carpeta public y luego tenemos que poner dentro el archivo index.php.

Figura 1. Carpeta public y archivo index.php

En resumen…

  • La carpeta public es la única accesible por los usuarios de Internet.
  • index.php es el front controller.
  • El servidor web canaliza todas las peticiones HTTP hacia index.php.
  • Con esto ganamos en seguridad (ahora solo hay 1 punto de acceso) y en escalabilidad (ahora podemos tomar la lógica de inicialización que está esparcida en varios archivos y la podemos poner en 1 solo sitio).

Nuestra micro-app MVC basada en Symfony utiliza estos componentes:

  • HttpFoundation es una capa OO para trabajar con HTTP.
  • HttpKernel es el núcleo del micro-framework. Permite atender una petición HTTP y devolver una respuesta pertinente por medio del despachador de eventos.
  • Routing asocia una petición HTTP a una acción determinada.

¡No dejes de clicar en los enlaces de los componentes de arriba para acceder a la documentación oficial y ver cómo funcionan!

Código de index.php

Tras inicializar los componentes Symfony necesarios, ya podemos desplazar a index.php la lógica CRUD de la agenda PHP original:

<?php
// Lógica de inicialización (bootstrap logic) de la app
// Creación de constantes...
define('APP_PATH', realpath(dirname(__FILE__)).'/..');
// Incluimos la clase People...
require_once APP_PATH.'/vendor/AgendaPHPGuay/People.php';
// Inicializamos la carga automática de clases...
$loader = require_once APP_PATH.'/vendor/autoload.php';
$loader->register();
// Importamos el componente HttpFoundation
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\RedirectResponse;
// Importamos el componente HttpKernel
use Symfony\Component\HttpKernel\HttpKernel;
use Symfony\Component\HttpKernel\Controller\ControllerResolver;
use Symfony\Component\HttpKernel\EventListener\RouterListener;
// Importamos el componente EventDispatcher
use Symfony\Component\EventDispatcher\EventDispatcher;
// Importamos el componente Routing
use Symfony\Component\Routing\Matcher\UrlMatcher;
use Symfony\Component\Routing\RequestContext;
use Symfony\Component\Routing\RouteCollection;
use Symfony\Component\Routing\Route;
// Componente AgendaPHPGuay
use \AgendaPHPGuay\People;

// Comenzamos creando las rutas, y programando la lógica controladora
$routes = new RouteCollection();

// Añadir un contacto
$routes->add('/people/add', new Route('/people/add', array('_controller' => function (Request $request) {
    switch ($request->getMethod()) {
        case 'POST':
            // Ahora en la versión Symfony trasladamos aquí la lógica PHP
            // que había antes en add.php
            People::getInstance(__DIR__.'/../data/people.csv')
                ->add($_POST)
                ->write();

            return new RedirectResponse('/people/list');
            break;
        default:
            return new Response(include APP_PATH.'/people/add.php');
            break;
    }
},
                                                          )));

// Listar los contactos
$routes->add('/people/list', new Route('/people/list', array('_controller' => function (Request $request) {
    // Ahora en la versión Symfony trasladamos aquí la lógica PHP
    // que había antes en list.php
    $people = People::getInstance(__DIR__.'/../data/people.csv')
        ->getCsv()
        ->people;

    return new Response(include APP_PATH.'/people/list.php');
},
                                                            )));

// Actualizar un contacto
$routes->add('/people/update', new Route('/people/update/{name}/{surname}/{email}', array('_controller' => function (Request $request) {
    switch ($request->getMethod()) {
        case 'POST':
            // Ahora en la versión Symfony trasladamos aquí la lógica PHP
            // que había antes en update.php
            People::getInstance(__DIR__.'/../data/people.csv')
                ->update($_POST)
                ->write();

            return new RedirectResponse('/people/list');
            break;
        default:
            return new Response(include APP_PATH.'/people/update');
            break;
    }
},
                                                                                         )));

// Borrar un contacto
$routes->add('/people/delete', new Route('/people/delete/{email}', array('_controller' => function (Request $request) {
    // Ahora en la versión Symfony trasladamos aquí la lógica PHP
    // que había antes en delete.php
    People::getInstance(__DIR__.'/../data/people.csv')
        ->delete(urldecode($request->get('email')))
        ->write();

    return new RedirectResponse('/people/list');
},
                                                                        )));

$request = Request::createFromGlobals();

$context = new RequestContext();
$context->fromRequest($request);

$matcher = new UrlMatcher($routes, $context);

$dispatcher = new EventDispatcher();
$dispatcher->addSubscriber(new RouterListener($matcher));

$resolver = new ControllerResolver();

$kernel = new HttpKernel($dispatcher, $resolver);
$kernel->handle($request)->send();

De este modo, los archivos CRUD se convierten en vistas MVC, por así decir, con solo código HTML y lógica PHP liviana; por ejemplo, sentencias condicionales, bucles, etc., pero nunca con lógica de negocio.

Ejemplo, archivo add.php:

<!DOCTYPE html>
<html lang="es">
    <?php include '../views/head.php'; ?>
    <body>
        <div class="container-narrow">
            <div class="masthead">
                <h3 class="muted">Agenda PHP guay</h3>
            </div>
            <div class="jumbotron">                
                <a class="btn btn-large btn-success" href="/people/list"><< Volver atrás</a>
            </div>
            <hr/>
            <h3>Añadir contacto</h3>
            <form role="form" method="post">                                   
                <div class="form-group">
                    <label for="name">Nombre:</label>
                    <input type="text" class="form-control" id="name" name="name" placeholder="Escribe un nombre..." required>
                </div>
                <div class="form-group">
                    <label for="surname">Apellido:</label>
                    <input type="text" class="form-control" id="surname" name="surname" placeholder="Escribe un apellido..." required>
                </div>
                <div class="form-group">
                    <label for="email">Email:</label>
                    <input type="email" class="form-control" id="email" name="email" placeholder="Escribe un email..." required>
                </div>                
                <button type="submit" class="btn btn-default">Guardar contacto</button>
            </form>
            <hr/>
            <?php include '../views/footer.php'; ?>
        </div> <!-- /container -->
        <script src="/assets/bootstrap/js/bootstrap.min.js"></script>
    </body>
</html>

Conclusión

En general, las aplicaciones de tamaño pequeño no necesitan construirse con toda la potencia de un framework de desarrollo. En estos casos, el desarrollador es el responsable de aplicar las ideas de análisis y los patrones de diseño que garanticen la calidad de dicha aplicación.

¡Pero ya no hay excusa! En la actualidad hay muchas facilidades para desarrollar con garantías de éxito aplicaciones MVC ligeras. Con la llegada de los micro-frameworks como Slim, o gracias a los componentes modulares de Symfony HttpKernel, HttpFoundation y Routing ya puedes aplicar buenas prácticas de programación y patrones de diseño de software desde el principio.

En esta entrada hemos tomado la agenda que desarrollamos en el curso PHP guay y la hemos ensamblado con tres componentes de Symfony hasta transformarla en una micro aplicación MVC. Esto ha sido posible porque cuando comenzamos a programar nuestra agenda PHP lo hicimos aplicando desde el principio algunas ideas de análisis y patrones de diseño.