Silex con Doctrine DBAL y ORM

En este artículo explicaré cómo utilizar Doctrine y sus entidades en el microframework Silex.

Aunque no es obligatorio, os recomiendo que utilicéis el “esqueleto” para Silex que ha creado Fabien Potencier para estructurar los archivos, o al menos le echéis un ojo para tenerla como ejemplo.

Para tener una integración completa de Doctrine en Silex, necesitaréis los siguientes providers y librerías:

  • DoctrineServiceProvider
    Es un proveedor de servicios oficial, que nos permitirá utilizar DBAL, la capa que nos da Doctrine para trabajar de forma sencilla con PDO.
  • Doctrine ORM ServiceProvider de Dragonfly Development
    Este proveedor nos permitirá trabajar con Doctrine ORM y sus entidades.
  • DoctrineBridge
    Con DoctrineBridge tendremos las librerías necesarias para poder loguear nuestras consultas sin tener que hacer un desarrollo propio para ello.

Los pasos para tener todo funcionando son los siguientes:

Configurar las dependencias en el composer.json

Debemos añadir los siguientes requires para descargar las dependencias anteriores


"doctrine/dbal": "2.*",
"symfony/doctrine-bridge": "2.*",
"dflydev/doctrine-orm-service-provider": "1.0.*@dev"

Una vez añadidas las anteriores líneas a nuestros requires, actualizamos con composer:

php composer.phar update

Registrar los ServiceProvider

Si utilizamos el skeleton de Fabien, los incluiremos en el archivo app.php, en caso contrario, los añadiremos en el lugar donde estemos registrando el resto de los ServiceProviders.


$app->register(new Silex\Provider\DoctrineServiceProvider(), array(
    'db.options'    => array(
    'driver'        => 'pdo_mysql',
    'host'          => 'localhost',
    'dbname'        => 'name',
    'user'          => 'root',
    'password'      => '',
    'charset'       => 'utf8',
    'driverOptions' => array(1002 => 'SET NAMES utf8',),
  ),
));

$app->register(new Dflydev\Silex\Provider\DoctrineOrm\DoctrineOrmServiceProvider, array(
    "orm.em.options" => array(
         "mappings" => array(
            array(
               "type"      => "yml",
               "namespace" => "Entity",
               "path"      => realpath(__DIR__."/../config/doctrine"),
              ),
            ),
         ),
));

Como vemos, primero registramos el Provider oficial, en el que indicamos los datos de conexión. Si dispusiésemos de varios servidores de bases de datos, para dividir las escrituras y las lecturas por ejemplo, este sería el lugar para hacerlo.

A continuación, registramos el proveedor de dflydev para disponer del ORM. Es importante fijarse en los parámetros

  • type: Define qué tipo de mapeo queremos para las entidades de doctrine. Las posibilidades son annotations, yml o xml.
  • namespace: Indica el namespace para las clases del ORM.
  • path: Indica en qué carpeta tenemos la configuración de las entidades.

Yo he optado por utilizar un mapeo basado en Yaml y guardo los mapeos en la carpeta /config/doctrine. Las clases de las entidades estarán bajo el namespace Entity.

Añadir los comandos de Doctrine

Para disponer de los comandos que nos permitirán generar las entidades, vamos al archive console.php entro del directorio src si utilizamos el skeleton, en caso contrario vamos al archivo donde tengamos la configuración de la consola y añadimos lo siguiente:


$console->setHelperSet(new Symfony\Component\Console\Helper\HelperSet(array(
    'db' => new \Doctrine\DBAL\Tools\Console\Helper\ConnectionHelper($app["db"]),
    'em' => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($app["orm.em"])
)));

$console->addCommands(array(

  new \Doctrine\ORM\Tools\Console\Command\ClearCache\MetadataCommand,
  new \Doctrine\ORM\Tools\Console\Command\ClearCache\QueryCommand,
  new \Doctrine\ORM\Tools\Console\Command\ClearCache\ResultCommand,
  new \Doctrine\ORM\Tools\Console\Command\SchemaTool\CreateCommand,
  new \Doctrine\ORM\Tools\Console\Command\SchemaTool\DropCommand,
  new \Doctrine\ORM\Tools\Console\Command\SchemaTool\UpdateCommand,
  new \Doctrine\ORM\Tools\Console\Command\ConvertDoctrine1SchemaCommand,
  new \Doctrine\ORM\Tools\Console\Command\ConvertMappingCommand,
  new \Doctrine\ORM\Tools\Console\Command\EnsureProductionSettingsCommand,
  new \Doctrine\ORM\Tools\Console\Command\GenerateEntitiesCommand,
  new \Doctrine\ORM\Tools\Console\Command\GenerateProxiesCommand,
  new \Doctrine\ORM\Tools\Console\Command\GenerateRepositoriesCommand,
  new \Doctrine\ORM\Tools\Console\Command\InfoCommand,
  new \Doctrine\ORM\Tools\Console\Command\RunDqlCommand,
  new \Doctrine\ORM\Tools\Console\Command\ValidateSchemaCommand,
  new \Doctrine\DBAL\Tools\Console\Command\ImportCommand,
  new \Doctrine\DBAL\Tools\Console\Command\ReservedWordsCommand,
  new \Doctrine\DBAL\Tools\Console\Command\RunSqlCommand

));

Esto registrará los comandos y helpers que nos da doctrine para gestionar las entidades y el esquema de base de datos.

Uso en la aplicación

Tras estos pasos sólo nos queda usarlo. Para ello creamos el mapa de entidad Entity.User.dcm.yml en el directorio que hayamos indicado en la configuración del proveedor:

#config/doctrine/Entity.User.dcm.yml
Entity\User:
    type: entity
    table: user
    fields:
        id:
            type: integer
            id: true
            generator:
                strategy: AUTO
        email:
            type: string
            length: 255
        password:
            type: string
            length: 512
    lifecycleCallbacks: {  }

Ahora, mediante la línea de comandos ejecutamos lo siguiente.

php console orm:generate-entities src/ --generate-annotations=1

Esto nos creará la carpeta Entity/ en el directorio src/ con las clases de las entidades, en este caso la clase Entity\User.

Para crear la base de datos, utilizaríamos el comando

php console orm:schema-tool:create

Esto nos creará la base de datos en el servidor. Ahora ya sólo nos queda el realizar operaciones con nuestros usuarios.

$app->get("/test-users", function(Application $app){

  $user = new \Entity\User();
  $user->setEmail("example@simettric.com");
  $user->setPassword("lalala");

  $entity_manager = $app["orm.em"];

  $entity_manager->persist($user);
  $entity_manager->flush();

  $users = $entity_manager->getRepository("Entity\User")
                          ->findAll();

  return $app['twig']->render('users.html.twig',
                              array("users"=>$users));

});

Seguridad.

Si utilizamos el SecurityProvider, seguro que unos interesa realizar la autenticación con nuestros usuarios en la base de datos utilizando Doctrine.

Para ello lo primero que debemos hacer es crearnos un UserProvider.


namespace Lib\Provider;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\User;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;

class UserProvider implements UserProviderInterface
{
    private $app;

    public function __construct(\Silex\Application $app)
    {
        $this->app = $app;
    }

    public function loadUserByUsername($username){


        $em = $this->app["orm.em"];
        if($em instanceof \Doctrine\ORM\EntityManager){
            if(!$user = $em->getRepository("Entity\User")->findOneBy(array("username"=>$username))){
                throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
            }
        }

        return $user;

    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof \Entity\User) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUsername());
    }


    public function supportsClass($class)
    {
        return $class === '\Entity\User';
    }



}

Y en nuestra configuración de seguridad, añadir lo siguiente

$app->register(new Silex\Provider\SecurityServiceProvider(), array(
    'security.firewalls'    => array(
        'dev' => array('pattern' => '^/(_(profiler|wdt)|css|images|js)/', 
                       'security' => false),
        'site' => array(
            'pattern'     => '^/',
            'form'        => array('login_path' => 'login',
                                   'check_path' => 'login_check'),
            'users'       => $app->share(function () use ($app) {
                return new \Lib\Provider\UserProvider($app);
            }),
            'logout' => array('logout_path' => 'logout'),
        ),
    ),
    'security.encoders'  => array('Entity\User'=> array(
                                     'algorithm'        => 'sha1', 
                                     'iterations'       => 4,         
                                     'encode_as_base64' => false
))));

Con esto ya tenemos nuestro sistema de seguridad autenticando con nuestros usuarios de la base de datos, utilizando Doctrine.

32 comentarios en “Silex con Doctrine DBAL y ORM”

    1. Aunque en proyectos simples no es necesario, yo uso el skeleton bastante, se adapta muy bien a mi forma de trabajar y me ahorra tiempo a la hora de organizar los archivos cada vez que creo un proyecto.

      Un saludo!

  1. Aupa Asier,

    llevo jugando con Silex y con el esqueleto de Fabien unos meses (desde el BilboStack :) ).
    Yo usaba https://packagist.org/packages/taluu/doctrine-orm-provider en vez de el de dflydev.

    Hoy me he puesto con el de dflydev para probar siguiendo tu manual. Muy chulo, pero al usar la consola, me salía un error () y lo he corregido registrando un helper antes de registrar los comandos:
    $console->setHelperSet(new Symfony\Component\Console\Helper\HelperSet(
    array(‘em’ => new \Doctrine\ORM\Tools\Console\Helper\EntityManagerHelper($app[“orm.em”]))
    ));

    Por otro lado, la configuración en YAML no funcionaba y he tenido que añadir al composer:
    “symfony/yaml”: “2.*”

    A ver si hacemos algún día un coding dojo para aprender unos de otros! :D

    1. En efecto, se me olvidó comentar que hay que registrar ese helper y el de DB, lo añado, gracias!

      Yo uso Silex por defecto en todos los desarrollos, excepto si algún cliente nos pide usar Symfony2.
      Ahorra mucho tiempo y puedes ser más pragmático.

      Un saludo!

      1. Buenas noches, estoy siguiendo tus pasos pero me da un error al generar la carpeta entity.
        Creo que es por lo que dices de registrar el helper.
        ¿Como lo registro? Perdón estoy empezando con silex y estoy un poco verde.

        Comando ejecutado en el terminal:
        php console orm:generate-entities src/ –generate-annotations=1

        Respuesta obtenida:
        PHP Fatal error: Call to undefined method Silex\Application::setHelperSet()

  2. Hola,

    amigazo primero que todo excelente tutorial!

    ¿como puedo lo hago para generar una constraseña a un usuario?

    y lo otro, el mensaje que me da al hacer login es

    The user provider must return a UserInterface object.

    Saludos!

    1. Hola,

      Para generar la contraseña en Silex es bien fácil


      $encoder = $app['security.encoder_factory']->getEncoder($user);

      $password = $encoder->encodePassword('foo', $user->getSalt());

      En php5.4 $encoded = $app->encodePassword($user, ‘foo’);

      Para solucionar tu errores, el UserProvider tiene que implementar la interfaz UserProviderInterface

      Un saludo!

      1. Hi,
        I’m sorry I don’t speak spanish(just understant it), but
        thanks for this tutorial it helped me a lot !

        I’m trying to generate an encoded password for my users,
        I’m using the solution you mentioned here, but I got this error :

        No encoder has been configured for account “Entity\User”.

        Do you know how I can solve it ?

        Thanks

  3. Hola,
    Cuando intento generar las entidades obtengo lo siguiente:

    “No Metadata Classes to process”

    Y si intento crear la base de datos con las entidades hechas a mano obtengo lo mismo……alguna idea donde estoy metiendo la pata?

    1. Probablemente ya lo hayas solucionado, pero por si a alguien le sirve, el problema al usar anotaciones está en el alias del espacio de nombres.

      Normalmente usamos “use Doctrine\ORM\Mapping as ORM;”, pero Doctrine no reconoce el alias “ORM”, sino que buscará “Doctrine\ORM\Mapping”. Usando el espacio de nombres completo en las anotaciones, se arregla el problema:

      /**
      * @\Doctrine\ORM\Mapping\Entity
      * @\Doctrine\ORM\Mapping\Table(name=”users”)
      */
      class User

      Ahora ya, la solución “elegante” consiste en cambiar el espacio de nombres del Reader, tal como se explica en la documentación oficial: http://docs.doctrine-project.org/projects/doctrine-common/en/latest/reference/annotations.html#default-namespace

      Esto último todavía no he conseguido hacerlo, pero al menos sirve de orientación.

      1. Comento para completar la entrada. Ayer tuve que crear un formulario con un campo “Entity” y por defecto, éste no está activado, hay que registrar la extensión. Ya ha habido quien ha tenido ese problema: https://github.com/silexphp/Silex/issues/465

        Ahí le dan como solución este código: https://gist.github.com/umpirsky/3939837

        Pero parece que no funciona (al menos en las últimas versiones de Silex y Doctrine).

        No obstante, he encontrado esta librería que dice estar pensada específicamente para solucionar este problema:
        https://github.com/saxulum/saxulum-doctrine-orm-manager-registry-provider

        Aún no la he probado, pero tiene buena pinta.

  4. Exactamente en está linea recibo (y soluciono si lo comento):

    $users = $entity_manager->getRepository(“Entity\User”) ->findAll();

    Recibo este error:

    Whoops, looks like something went wrong getrepository.

    Otra pregunta que le tengo, es que cómo que getRepository sabe donde esta la clase mapeador de User, si sólo el método recibe el namespace como una cadena y no como una referencia o algo así. Existe alguna configuración que me estoy perdiendo?. No recibo el error si comento esa linea de código, realizo la inserción correctamente, pero error se produce por el método getRepository.

    1. Hola a tod@s,

      Sebas, has podido solucionar tu problema? A mi el error que me genera es que no encuentra la clase:

      “MappingException: Class ‘Entity\Oferta’ does not exist”

      Es el mismo error que el tuyo?

      Si no es así…alguien podría orientarme?

      Muchas gracias.

  5. Hola, a mi también me sale el mismo error que le salía a txurdi, me falla en setHelperSet() y lo intento registrar y sigue el mismo error.

    Cómo lo tendría que registrar para que no me tirara ese error?

    Otra pregunta, vosotros usáis Windows?

  6. Hola,

    he seguido las instrucciones para generar las entidades pero me sigue saliendo “No Metadata Classes to process”

    Agradezco la ayuda,

    Esta es mi entidad

    register(new DoctrineServiceProvider(), array(
    ‘db.options’ => array(
    ‘driver’ => ‘pdo_mysql’,
    ‘host’ => ‘localhost’,
    ‘dbname’ => ‘silex_db’,
    ‘user’ => ‘root’,
    ‘password’ => ‘root’,
    ‘charset’ => ‘utf8’,
    ‘driverOptions’ => array(1002 => ‘SET NAMES utf8’,),
    ),
    ));

    $app->register(new DoctrineOrmServiceProvider, array(
    “orm.em.options” => array(
    “mappings” => array(
    array(
    “type” => “annotation”,
    “namespace” => “Entity”,
    “path” => __DIR__.”/Entity”,
    ),
    ),
    ),
    ));

    Y esta es la salida en la consola

    mik3$ bin/console orm:schema-tool:create
    No Metadata Classes to process.

  7. Muy buen articulo, un detalle, cuando explicas el Entity.User.dcm.yml, así como esta en el ejemplo no funciona ya que necesita indentación, se que puede parecer obvio pero no para todos.
    Saludos!

    1. Me rectifico, por alguna extraña razón esta web a veces no te indenta el código en los bloques. Luego de comentar apareció correctamente, nos paso a mi y a un compañero.

  8. Hola Asier, he seguido al pie de la letra tu tutorial y me ha servido bastante, ahora estoy intentando meter 2 bases de datos pero no lo consigo, se donde tengo que tocar pero no se como colocarlo, utilizo silex y doctrine ORM con Dflydev provider, podrías ayudarme? muchas gracias de antemano!!

  9. Hola,

    siguiendo el tutorial me he atrancado al intentar generar las Entities, al lanzar el console obtengo este error:

    Fatal error: Uncaught InvalidArgumentException: Identifier “db” is not defined. in /var/www/html/vendor/pimple/pimple/src/Pimple/Container.php:96
    Stack trace:
    #0 /var/www/html/src/console.php(24): Pimple\Container->offsetGet(‘db’)
    #1 /var/www/html/bin/console(21): require(‘/var/www/html/s…’)
    #2 {main}
    thrown in /var/www/html/vendor/pimple/pimple/src/Pimple/Container.php on line 96

    ¿Puede alguien ayudarme?

    Muchas gracias

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *