Creando una REST Api para PHP con el Framework Slim 3

Crearemos una REST Api sencilla usando el micro-framework Slim 3 para PHP.

Rodríguez Patiño, Eduardo
2020-09-27 | 42,568 lecturas

En esta nueva entrada nos toca ver la creación de una REST Api usando el framework Slim 3 de PHP. Lo primero que haremos es decargarlo vía Composer, si no sabes usar Composer te recomiendo leer la siguiente entrada.

Acerca de Slim

Es un framework para crear REST Api usando PHP que es es bastante amigable, práctico y poderoso a la hora de implementar. Por defecto, el framework en lo que te ayuda es en la creación del API y su comunicación con el cliente, es decir:

  • Validación de tipos de petición (GET, POST, INPUT, DELETE ..)
  • Mddleware: para interceptar el antes y después de cada petición.
  • Mapeo de rutas bastante flexible

La autenticación y persistencia a datos lo hacemos a nuestro antojo, podemos usar un ORM como Doctrine, Propel, nuestro propia librería para la persistencia o solo PDO y para la autenticación podríamos usar Json Web Token (tema que veremos más adelante). En resumen, Slim nos da el libre albedrío para elegir con que trabajar.

Descargando Slim

El comando para descargar la dependencia es el siguiente:

composer create-project slim/slim-skeleton nombre_de_la_app

Como se darán cuenta, estamos descargando una dependencia llamada Slim Skeleton. Esto, basicamente es el framework Slim pero que ya implementa una distribución de carpetas para trabajar comodamente.

  • logs: mediante la dependencia Monolog, podemos hacer trace a nuestras rutas para ir guardando en el log posible errores, o cosas que queramos hacer seguimiento.
  • public: se encuentra nuestro index.php que levanta toda la API.
  • src: configuraciones que podemos hacer.
  • templates: es el equivalente a una carpeta views en MVC, pero como hacemos REST no vamos a usar esto. Podríamos crear una ruta que muestre HTML puro, tal vez para tema de documentación posiblemente.
  • vendor: las dependencias manejadas para nuestro proyecto vía Composer.

Optimizando nuestro proyecto

Hasta este punto ya entedemos la arquitectura que nos ofreció Skeleton, ahora vamos a optimizar el proyecto para poder trabajar más comodos. Vamos a crear una carpeta App que contedrá lo siguiente.

  • app: la carpeta base, en esta vamos agregar nuestro código.
    • route: vamos a crear nuestras propias rutas para trabajar de manera más ordenada, algo similar a lo que sería el patron MVC, vamos a crear "controladores".
    • model: nuestras clases que van a comunicarse con la base de datos.
    • lib: componentes adicionales que creemos para nuestro proyecto, utilitarios, entre otros.
    • app_loader.php: es un archivo PHP que se encargará de cargar automaticamente todo lo que agreguemos a la carpeta app.

app_loader.php, este lo debemos agregar en la raíz de nuestra carpeta app

<?php
$base = __DIR__ . '/../app/';

$folders = [
    'lib',
    'model',
    'route'
];

foreach($folders as $f)
{
    foreach (glob($base . "$f/*.php") as $filename)
    {
        require $filename;
    }
}

Abrimos el archivo public/index.php y agregamos el siguiente código, justo antes del $app->run();

// Register my App
require __DIR__ . '/../app/app_loader.php';

Y con esto, nuestro proyecto va a reconocer todo los archivos que haya en la carpeta app y los va a levantar automaticamente para que sea reconocido.

Slim, creando nuestra primera ruta

No voy a entrar en detalles teóricos, vamos a implementar el código defrente, para mayor información visitamos la web original de Slim. Agregamos un nuevo archivo (user_route) a la carpeta route y empezamos a codificar.

Como buena práctica y para trabajar más ordenado vamos hacer uso de los grupos, este nos permite agrupar nuestras rutas.

$app->group('/user/', function () {
    
    $this->get('test', function ($req, $res, $args) {
        return $res->getBody()
                   ->write('Hello Users');
    });
    
});
  • $req, hace referencia a la clase Request de Slim.
  • $res, hace referencia a la clase Response de Slim.
  • $args, nos permite capturar los valores que hayamos establecido en nuestra URI de la Ruta.

NOTA: los objetos en Slim son en cadena, es decir cada llamada a un método, retorna el objeto completo, por lo tanto si queremos setear un valor y otro valor, deberemos hacerlo en cadena. Algo como esto:

return $a->setTitle('Titulo')
         ->setMessage('Hola mundo');

Acerca de los Grupos en Slim

Como hemos usado los grupos, ya estamos agrupando todas las rutas dentro de la jerarquía /users/, es decir todo las rutas que esten por debajo de este nivel van a comenzar desde la siguiente manera por ejemplo:

  • users/test
  • users/get
  • users/getall

Para probar esto vamos a ejecutar la siguiente ruta, en mi caso esta es:

http://localhost/slim_app/public/user/test

Deberíamos visualizar el mensaje "Hello Users". Si hasta este punto no hay problemas vamos a crear las demás rutas para hacer CRUD a nuestro usuario.

Creando las rutas de CRUD e implementación de PDO

user_model.php

  • Los métodos que implementa nuestro modelo siempre responderán el objeto Response, este lo hemos creado en la carpeta Lib y nos sirve para menejar un Standard en las respuesta que haremos al cliente.
  • La instancia de PDO está en la carpeta lib.
<?php
namespace App\Model;

use App\Lib\Database;
use App\Lib\Response;

class UserModel
{
    private $db;
    private $table = 'empleado';
    private $response;

    public function __CONSTRUCT()
    {
        $this->db = Database::StartUp();
        $this->response = new Response();
    }

    public function GetAll()
    {
        try
        {
            $result = array();

            $stm = $this->db->prepare("SELECT * FROM $this->table");
            $stm->execute();

            $this->response->setResponse(true);
            $this->response->result = $stm->fetchAll();

            return $this->response;
        }
        catch(Exception $e)
        {
            $this->response->setResponse(false, $e->getMessage());
            return $this->response;
        }
    }

    public function Get($id)
    {
        try
        {
            $result = array();

            $stm = $this->db->prepare("SELECT * FROM $this->table WHERE id = ?");
            $stm->execute(array($id));

            $this->response->setResponse(true);
            $this->response->result = $stm->fetch();

            return $this->response;
        }
        catch(Exception $e)
        {
            $this->response->setResponse(false, $e->getMessage());
            return $this->response;
        }  
    }

    public function InsertOrUpdate($data)
    {
        try 
        {
            if(isset($data['id']))
            {
                $sql = "UPDATE $this->table SET 
                            Nombre          = ?, 
                            Apellido        = ?,
                            Correo          = ?,
                            Sexo            = ?,
                            Sueldo          = ?,
                            Profesion_id    = ?,
                            FechaNacimiento = ?
                        WHERE id = ?";

                $this->db->prepare($sql)
                     ->execute(
                        array(
                            $data['Nombre'], 
                            $data['Apellido'],
                            $data['Correo'],
                            $data['Sexo'],
                            $data['Sueldo'],
                            $data['Profesion_id'],
                            $data['FechaNacimiento'],
                            $data['id']
                        )
                    );
            }
            else
            {
                $sql = "INSERT INTO $this->table
                            (Nombre, Apellido, Correo, Sexo, Sueldo, Profesion_id, FechaNacimiento, FechaRegistro)
                            VALUES (?,?,?,?,?,?,?,?)";

                $this->db->prepare($sql)
                     ->execute(
                        array(
                            $data['Nombre'], 
                            $data['Apellido'],
                            $data['Correo'],
                            $data['Sexo'],
                            $data['Sueldo'],
                            $data['Profesion_id'],
                            $data['FechaNacimiento'],
                            date('Y-m-d')
                        )
                    ); 
            }

            $this->response->setResponse(true);
            return $this->response;
        }catch (Exception $e) 
        {
            $this->response->setResponse(false, $e->getMessage());
        }
    }

    public function Delete($id)
    {
        try 
        {
            $stm = $this->db
                        ->prepare("DELETE FROM $this->table WHERE id = ?");                   

            $stm->execute(array($id));

            $this->response->setResponse(true);
            return $this->response;
        } catch (Exception $e) 
        {
            $this->response->setResponse(false, $e->getMessage());
        }
    }
}

user_route.php

El código es bastante fácil de entender, hemos usado 2 métodos para nuestra API. Esta responde unicamente a peticiones del tipo GET o POST.

  • El tercer parámetro de nuestras nos permites obtener los parámetros que hayamos establecido en nuestra URI.
    Nota: me refiero a parámetros como miruta/{name}, los parámetros de una queryString no son válidos para los $arguments (?name=eduardo) ``` $this->get('get/{id}', function ($req, $res, $args) { $args['id'] }); ```
  • Para el caso de las que implementa POST, para capturar los parámetros hacemos uso del Request ($req) accediendo al método $req->getParsedBody() el cual retornará todo los valores que nos hayan enviado. ``` $this->post('save', function ($req, $res) { $data = $req->getParsedBody(); }); ```
<?php
use App\Model\UserModel;

$app->group('/user/', function () {

    $this->get('test', function ($req, $res, $args) {
        return $res->getBody()
                   ->write('Hello Users');
    });

    $this->get('getAll', function ($req, $res, $args) {
        $um = new UserModel();

        return $res
           ->withHeader('Content-type', 'application/json')
           ->getBody()
           ->write(
            json_encode(
                $um->GetAll()
            )
        );
    });

    $this->get('get/{id}', function ($req, $res, $args) {
        $um = new UserModel();

        return $res
           ->withHeader('Content-type', 'application/json')
           ->getBody()
           ->write(
            json_encode(
                $um->Get($args['id'])
            )
        );
    });

    $this->post('save', function ($req, $res) {
        $um = new UserModel();

        return $res
           ->withHeader('Content-type', 'application/json')
           ->getBody()
           ->write(
            json_encode(
                $um->InsertOrUpdate(
                    $req->getParsedBody()
                )
            )
        );
    });

    $this->post('delete/{id}', function ($req, $res, $args) {
        $um = new UserModel();

        return $res
           ->withHeader('Content-type', 'application/json')
           ->getBody()
           ->write(
            json_encode(
                $um->Delete($args['id'])
            )
        );
    });

});

Con esto finalizamos el ejemplo de Slim, posiblemente te haya parecido algo duro el tutorial pero cuando le agarres el truco verás que es bastante sencillo. Adjunto la descarga para que puedan revisar el código.

PD: les comento que estoy creando un nuevo curso profesional para aprender a crear una REST Api junto a PHP implementando las mejores prácticas. Veremos desde la creación de la API hasta su consumo y seguridad en el cliente/servidor.