Introducing Espresso
Tags: php, framework, http, webdev
Published: 2019-05-20 21:44:53

---
title: Introducing Espresso
published: true
description: Un micro-framework de PHP fuertemente inspirado en Express.js
tags: php,framework,http,webdev
---

Cuando comencé a utilizar Node me enamoré de muchas cosas. Una de las primeras cosas fue el sistema de módulos: una forma bastante inteligente de exportar estado y comportamiento, manteniendo encapsulamiento y sin preocuparnos por tener un DI Container. Sin embargo, una de las cosas que más me gustó también es lo flexible y potente que es Express.js, la capa HTTP por antonomasia de Node.

Me gustó tanto que tenía muchas ganas de usarlo en PHP. Si bien es cierto, PHP cuenta con algunos microframeworks similares como Expressive y Slim, aún había algunas cosas que no me agradaban del todo en ellos.

Por ello, me decidí a crear Espresso, un clon de Express.js para PHP. Actualmente se encuentra en versión `1.0.0-alpha1`. Esto significa que la api ya está bien definida y funciona, pero lo que faltan son extensivas pruebas unitarias antes de sentirme confiado de liberarlo con un tag productivo.

La idea de este artículo es doble. La primera, es dar a conocer el proyecto y buscar personas que quieran contribuir al mismo por medio de probarlo, jugar con él, buscar bugs y ofrecer pull requests. La segunda, es mostrar a potenciales usuarios cómo se usa.

## Creando la Instancia de Espresso

Espresso es interoperable. En el mundo PHP, esto quiere decir que sigue los estándares HTTP definidos por PHP-FIG. Por ello, para usarlo, el requerimiento mínimo es que cuentes con una implementación de PSR-7 y una función que emita una Respuesta PSR-7.

Personalmente recomiendo instalar el fabuloso `zendframework/zend-diactoros` y `http-interop/response-sender`.

Entonces, para instalar Espresso y estas dos sugerencias, copia este comando en la carpeta de tu proyecto y podrás empezar:

```bash
composer require espresso/app dev-master zendframework/zend-diactoros http-interop/response-sender
```

> NOTA: Espresso en `dev-master` sólo es necesario en la fase alpha.

Para crear una instancia de Espresso es tan simple como:

```php
// No te olvides de requerir el autoload de composer antes!
$app = new Espresso(
    [\Zend\Diactoros\ServerRequestFactory::class, 'fromGlobals'],
    '\Http\Response\send'
);
```

Estos dos parámetros le permiten a Espresso poder llamar a su método `run`. Lo que este método hace, es crear la instancia de `ServerRequestInterface` usando el callable, obtener una instancia de `ResponseInterface` del pipeline de middleware, y luego emitir esa respuesta usando el callable provisto.

Una vez creada la instancia de Espresso, es muy fácil inyectar middleware:

```php
$app->use(new LoggingMiddleware());
```

## Registrando rutas en Espresso

Para registrar una ruta en Espresso, tienes a tu disposición seis métodos, que son bastante clásicos: `get`, `post`, `put`, `patch`, `delete` y `any`. Cada uno de estos métodos toma el *path* de la ruta y un número indefinido de instancias de `MiddlewareInterface`.

```php
// Este handler sirve la página de inicio
$app->get('/', new HomePageHandler());
// Este handler sirve el panel de administración, pero primero se
// asegura que el usuario esté autenticado.
$app->get('/admin', new BasicAuthMiddleware(), new AdminPanelHandler());
```

Por supuesto, puedes pasar *placeholders* a las rutas que definas. Éstos serán inyectados en los atributos de `ServerRequestInterface` bajo el nombre dado al placeholder:

```php
$app->get('/users/:id', function ($req) {
    $id = $req->getAttribute('id'); // Esto devuelve el valor de id.
});
```

> Nota que además se pueden pasar callables o instancias de `RequestHandlerMiddleware`. 

A diferencia de otros micro-frameworks en PHP, Espresso no cuenta con el tradicional sistema de enrutamiento consistente en un *Route Collector* y un *Dispatcher*. En vez de eso, cada ruta es un middleware en sí mismo, y recibe una instancia de `ServerRequestInterface` desde la cual obtiene información para ver si hay un match.

Esto podría parecer ineficiente, y lo sería si definimos cada ruta de nuestra aplicación directamente en la instancia de Espresso. Sin embargo, Espresso cuenta con otro concepto tomado desde Express.js: la habilidad de incrustar un componente enrutable bajo un *path*. Por ejemplo, pensemos en un módulo de usuarios:

```php
$userModule = new RoutableHttpComponent();

// Definimos algunas rutas relativas a este módulo
$userModule->get('/', function() {
    // Devolver una lista de usuarios aquí
});
$userModule->post('/', function() {
    // Crear un usuario aquí
});
$userModule->get('/:id', function() {
    // Devolver un usuario específico aquí
});

// Y luego las importamos en la aplicación bajo un path en común
$app->use('/users', $userModule);
```

En realidad, Espresso no es más que una extensión de este `RoutableHttpComponent` que posee el método `run`, y algunos métodos de configuración. Podrías hacer tu propia aplicación usando sólo el `RoutableHttpComponent`.

## Espresso e Inyección de Dependencias

Si usas un contenedor de inyección de dependencias que implementa la interfaz definida por PSR-11, entonces puedes hacer que Espresso lo use para resolver cualquier depdendencia pasada a los métodos antes mencionados.

Esto puede hacerse usando el método `setContainer` de Espresso:

```php
$app->setContainer($aContainerInstance);
```

Luego, podrás ser capaz de definir rutas e inyectar middleware sólamente referenciando el nombre del servicio:

```php
$app->get('/', 'app.homepage_handler');
```

Internamente, Espresso resolverá este servicio y lo instanciará de manera *Lazy* por defecto.

Cada componente puede recibir una instancia del container también.

```php
$module = new RoutableHttpComponent();
$module->setContainer($aContainerInstance);
```

## Otras utilidades

La instancia de Espresso cuenta con unas cuantas utilidades comunes para hacer tu vida más simple. Estoy pensando en añadir muchas otras más y sería genial poder tener feedback al respecto.

### Sesión

La primera utilidad es la habilidad de identificar únicamente un cliente a través de cookies que conforman una sesión. Podemos hacer esto llamando al método `$app->session();`. Este método toma tres argumentos y sólo el primero es obligatorio:

1. Un *secret key* de 32 bits. Puede ser generado en la terminal con: `php -r "echo bin2hex(random_bytes(16));"`
2. Un nombre para la cookie de sesión. Por defecto es `_espresso`. 
3. El tiempo de expiración de la sesión como string de `DateTime`. Por defecto es `+1week`.

Es importante notar que el manejo de sesión en Espresso no usa el handler nativo de PHP, sino que encripta un string `json` que guarda los datos de sesión en una cookie del lado del cliente. Cualquier modificación es inmediatamente detectada, lo que rehace la sesión e invalida cualquier autenticación previa. Si la sesión es modificada, la cookie vuelve a setearse. 

La ventaja de este método es su simplicidad, efectividad, seguridad y escalabilidad. La desventaja es que no puedes colocar demasiados datos en la sesión, debido a que las cookies poseen capacidad bastante limitada.

### Body Parsers

Si estás construyendo una Api REST o procesando input de formularios, Espresso provee un método para configurar rápidamente el *parsing* del cuerpo de una *request*. Tan solo llamando el método `$app->parseBody()` y pasandole un string, ya sea `json` o `form` (o ambos), automáticamente tendrás tu input disponible como un arreglo en el método `ServerRequestInterface::getParsedBody()`.

```php
$app->parseBody('json');
```

## "¡Tú puedes contribuir!"

Hay unas cuantas cosas más que me gustaría añadir a Espresso, ¡y estoy muy ansioso de escuchar ideas! Si tienes algún feature que quieras proponer, puedes abrir un *issue* en el [repositorio de Github](https://github.com/espresso-php/app) o puedes instalar el [paquete de composer](https://packagist.org/packages/espresso/app) y jugar un poco con el framework.

Mientras tú haces eso, yo estaré escribiendo pruebas unitarias y añadiendo algunos features extra. Una vez obtenido un 90% de coverage (que probablemente será en menos de dos semanas) lanzaré la versión `1.0.0-rc1`. A mediados de Junio espero poder hacer el lanzamiento de `1.0.0`.