Spiral como framework lleva 10 años en desarrollo, ahora en su segunda versión trae una propuesta interesante al desarrollar código con el Cycle ORM e incorporar cualquier librería de GoLang  a través del servidor de aplicaciones RoadRunner.

El crear una aplicación con Spiral es un proceso sencillo porque viene preconfigurado para tres diferrentes escenarios. Utilizaremos spiral/app para la creación de un API, en la cual tendremos usuarios y publicaciones de un blog.

Instalación

Empezamos la instalación utilizando composer:

composer create-project spiral/app

Después ejecutar la comprobación de su instalación:

php app.php configure

Comprobamos la ejecución del servidor  con el siguiente comando:

./spiral serve -v -d

Esto nos permite ver la aplicación corriendo desde el enlace:

http://localhost:8080

Configuración de base de datos

Ahora es necesario configurar la base de datos, como muchos frameworks modernos Spiral es capaz de conectarse con diferentes motores, para este ejemplo utilizaremos mysql.

Es necesario editar el archivo app/config/database.php y agregar el driver y conexión para mysql como se muestra en el ejemplo:

<?php

/**
 * This file is part of Spiral package.
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 */

declare(strict_types=1);

return [
    /**
     * Default database connection
     */
    'default'   => 'default',

    /**
     * The Spiral/Database module provides support to manage multiple databases
     * in one application, use read/write connections and logically separate
     * multiple databases within one connection using prefixes.
     *
     * To register a new database simply add a new one into
     * "databases" section below.
     */
    'databases' => [
        'default' => [
            'driver' => 'mysql'
        ],
    ],

    /**
     * Each database instance must have an associated connection object.
     * Connections used to provide low-level functionality and wrap different
     * database drivers. To register a new connection you have to specify
     * the driver class and its connection options.
     */
    'drivers'   => [
        'mysql'     => [
            'driver'     => \Spiral\Database\Driver\MySQL\MySQLDriver::class,
            'options'    => [
                'connection' => 'mysql:host='. env('DB_HOST').';dbname=' . env('DB_NAME'),
                'username'   => env('DB_USERNAME'),
                'password'   => env('DB_PASSWORD'),
            ]
        ],

    ]
];
app/config/database.php


Agregamos nuestras credenciales al archivo de configuración .env, como se muestra en el ejemplo:

SAFE_MIGRATIONS=true
DB_HOST=127.0.0.1
DB_NAME=spiral_demo
DB_USERNAME=root
DB_PASSWORD=
.env

Una vez configurado podemos realizar la comprobación de la conexión en terminal con el comando:

php app.php db:list

Creación de modelos

Para la creación de los modelos, Spiral utiliza su propio ORM llamado Cycle, por defecto utiliza anotaciones para configurar las propiedades, relaciones y la creación de migraciones.

Ejecutamos los siguientes comandos  para definir los modelos y campos de Post y User:

php app.php create:entity post -f id:primary -f title:string -f content:text -e
Creación del modelo Post
php app.php create:entity user -f id:primary -f name:string -e
Creación del modelo User

Con ello nos crea los siguientes archivos boilerplate en:

/app/app/src/Database/Post.php

/app/app/src/Database/User.php

<?php
/**
 * {project-name}
 *
 * @author {author-name}
 */
declare(strict_types=1);

namespace App\Database;

use Cycle\Annotated\Annotation as Cycle;

/**
 * @Cycle\Entity(repository="\App\Repository\PostRepository")
 */
class Post
{
    /**
     * @Cycle\Column(type = "primary")
     */
    public $id;

    /**
     * @Cycle\Column(type = "string")
     */
    public $title;

    /**
     * @Cycle\Column(type = "text")
     */
    public $content;

}
app/src/Database/Post.php

Al inspeccionar el archivo Post podemos ver las anotaciones creadas donde se define el tipo de campo, esto lo podemos utilizar para crear las migraciones de la base de datos con los siguientes comandos.

Inicializamos la tabla de migraciones:

php app.php migrate:init

Aplicamos la creación de tabla migraciones

php app.php migrate -vv


Detectamos los cambios en los modelos por medio del Cycle con el siguiente comando:

php app.php cycle:migrate -v

Lo siguiente nos crea los los archivos de migraciones ubicados en app/migrations/

Ejecutamos las migraciones para aplicarlo en la base de datos.

php app.php migrate -vv
Comprobamos los cambios con: php app.php db:list

Creación de la relaciones usando Cycle ORM

Para crear la relación de autor con User desde Post añadimos el atributo  author en la clase Post de la siguiente forma:

    /**
     * @Cycle\Relation\BelongsTo(target = "User", nullable = false)
     * @var User
     */
    public $author;


Para aplicar la relación en la base de datos ejecutamos nuevamente los comandos:

php app.php cycle:migrate -v

php app.php migrate -vv

Para crear la migración y  para aplicarla a la base de datos.

Ingresando datos con Faker


Ahora añadimos el package de Faker para agregar algunos Post y User de manea aleatoria.

composer require fzaninotto/faker

Creamos la clase que nos permitirá crear una instancia como singleton usando el patrón Factory.

namespace App\Bootloader;

use Faker\Factory;
use Faker\Generator;
use Spiral\Boot\Bootloader\Bootloader;

class FakerBootloader extends Bootloader
{

    protected const SINGLETONS = [
    	Generator::class => [self::class, 'fakerGenerator']
    ];
    
    private function fakerGenerator(): Generator
    {
    	return Factory::create(Factory::DEFAULT_LOCALE);
	}
}

Agregamos el Bootloader en el archivo app/src/App.php

    /*
     * Application specific services and extensions.
     */
    protected const APP = [
        Bootloader\LocaleSelectorBootloader::class,
        Bootloader\RoutesBootloader::class,

        // fast code prototyping
        Prototype\PrototypeBootloader::class,
        Bootloader\FakerBootloader::class,
    ];

Creamos los comandos para ejecutar los seeders agregando los registros con Faker

php app.php create:command seed/user seed:user

php app.php create:command seed/post seed:post

Agregamos el código para la creación de 100 usuarios

<?php
/**
 * {project-name}
 *
 * @author {author-name}
 */
declare(strict_types=1);

namespace App\Command\Seed;

use App\Database\User;
use Cycle\ORM\TransactionInterface;
use Faker\Generator;
use Spiral\Console\Command;

class UserCommand extends Command
{
    protected const NAME = 'seed:user';

    /**
     * Perform command
     */
    protected function perform(TransactionInterface $tr, Generator $faker): void
    {

         for ($i = 0; $i < 100; $i++) {
            $user = new User();
            $user->name = $faker->name;

            $tr->persist($user);
        }

        $tr->run();
    }
}
app/src/Command/Seed/UserCommand.php

Agregamos el código para generar mil posts por usuario

<?php
/**
 * {project-name}
 *
 * @author {author-name}
 */
declare(strict_types=1);

namespace App\Command\Seed;

use App\Database\Post;
use App\Repository\PostRepository;
use App\Repository\UserRepository;
use Cycle\ORM\TransactionInterface;
use Faker\Generator;
use Spiral\Console\Command;

class PostCommand extends Command
{
    protected const NAME = 'seed:post';

    public function __construct(UserRepository $usersRepository, PostRepository $postRepository, ?string $name = null)
    {
        parent::__construct($name);
        $this->postRepo = $postRepository;
        $this->usersRepo = $usersRepository;
    }

    protected function perform(TransactionInterface $tr, Generator $faker): void
    {
        $users = $this->usersRepo->findAll();

        for ($i = 0; $i < 1000; $i++) {
            $user = $users[array_rand($users)];

            $post = new Post();
            $post->author = $user;
            $post->title = $faker->sentence(12);
            $post->content = $faker->text(900);
            $tr->persist($post);

            $this->sprintf("New post: <info>%s</info>\n", $post->title);
        }
        $tr->run();

    }
}
app/src/Command/Seed/PostCommand.php

Ejecutamos los comandos con:

$ php app.php seed:user -vv

$ php app.php seed:post -vv

Creación del controlador

El controlador nos permitirá crear las peticiones REST vía API. Ejecutamos el comando para ejecutar el boilerplate del controlador.

php app.php create:controller post -a get -p 

<?php

declare(strict_types=1);

namespace App\Controller;

use App\Repository\PostRepository;
use Spiral\Prototype\Traits\PrototypeTrait;
use Spiral\Http\Exception\ClientException\NotFoundException;
use Spiral\Router\Annotation\Route;

class PostController
{
   use PrototypeTrait;

   private $postRepo;

   public function __construct(PostRepository $postRepository){
       $this->postRepo = $postRepository;
   }

   /**
    * @Route(route="/api/post/<id:\d+>", name="post.get", methods="GET")
    * @param string $id
    * @return array
    */
   public function get(string $id)
   {
      
       /** @var Post $post */
       $post = $this->postRepo->findByPK($id);
       if ($post === null) {
           throw new NotFoundException("post not found");
       }

       return [
           'post' => [
               'id'      => $post->id,
               'author'  => [
                   'id'   => $post->author->id,
                   'name' => $post->author->name
               ],
               'title'   => $post->title,
               'content' => $post->content,
           ]
       ];
   }
}

En el controlador hace uso de `Spiral\Router\Annotation\Route`  para definir las rutas  con anotaciones, solo es necesario agregar el siguiente paquete desde composer.

composer require spiral/annotated-routes
protected const LOAD = [
    ...
    ..
    .
    
	AnnotatedRoutesBootloader::class,

];
Agregamos la clase al archivo AppBootloader dentro de LOAD

Podemos realizar la comprobación de las rutas con el comando

php app.php route:list

Ejecutamos el servidor e ingresamos a la URL en nuestro cliente API

http://127.0.0.1:8080/api/post/22

{
  "post": {
    "id": 22,
    "author": {
      "id": 22,
      "name": "Nathanael Kessler DDS"
    },
    "title": "Molestiae quibusdam tempore quae saepe minima pariatur recusandae cumque cum tempora voluptas voluptas sint in deleniti in.",
    "content": "Qui est magnam vero rerum nesciunt ab animi. Nulla sint est exercitationem suscipit qui perspiciatis sequi. Itaque qui iusto eligendi rerum odit perferendis. Cupiditate illo veritatis dolorem qui rerum. Molestias omnis architecto ea. Provident dolorum impedit sit aut unde ut rerum. Reiciendis vel suscipit aut omnis ipsa rerum vel. Aut alias fugit amet iure est molestias. Ipsa sed nihil fuga ut maxime. Praesentium ipsa et eos laborum. Sunt esse et dolorem recusandae eveniet omnis. Et repudiandae quia et. Possimus dolor nostrum autem. Libero nihil id assumenda alias sed magni quibusdam. Mollitia aut assumenda eum voluptas itaque nihil a beatae. Ab quos in magni fugit dolore. Et quod natus dolor velit aperiam temporibus. Et temporibus quidem qui rerum. Rem fugit et nisi temporibus. Ut aut nihil aut non quo quia quas. Voluptatem voluptatum perspiciatis repudiandae fugit eos. Unde quia quis sed praesentium. Praesentium non iusto recusandae molestiae est atque earum."
  }
}


Con esto desarrollamos una pequeña API que nos regresa el detalle de cada post, configuramos Spiral para conectarse a una base de datos y agregamos modelos creando relaciones y sus migraciones.

Spiral framework nos ofrece un desarrollo rápido sin dejar de lado las buenas practicas  y la aplicación de patrones de diseño de manera natural en el framework.

El código del tutorial lo encuentras disponible en el siguiente github.

erickdbrito/spiral-demo
Tutorial creando un API con Spiral framework. Contribute to erickdbrito/spiral-demo development by creating an account on GitHub.

Referencias

https://spiral.dev/docs