Tutorial Jobeet con Symfony2 Día 3: Modelo de Datos

De WikiSalud
Saltar a: navegación, buscar
Este artículo es parte del proyecto Tutorial Jobeet con Symfony2

Contenido

El modelo relacional

Las historias de usuarios de el día previo describen los objetos principales de nuestro proyecto: jobs (ofertas de trabajo), affiliates (Afiliados), y categories (Categorías) Aquí esta la el diagrama de relacion de la entidad:

Diagrama de la relación entidad

Además de las columnas descritas en los casos, también hemos añadido created_at y columnas updated_at. Vamos a configurar Symfony2 para establecer su valor de forma automática cuando un objeto se guarda o se actualiza.

La base de datos

Para almacenar las ofertas de trabajo, afiliados y categorías en la base de datos, Symfony2 usa Doctrine ORM. Para definir los parametros de conexión a la base de datos, tienes que configurar el fichero app/config/parameters.ini (En este caso, usando postgresql)

app/config/parameters.ini
parameters:
    database_driver: pdo_pgsql
    database_host: 127.0.0.1
    database_port: null
    database_name: symfonia
    database_user: symfonia
    database_password: symfo13
...

Ahora que Doctrine conoce tu base de datos, puedes crear la base de datos si es que esta no esta creada (Y si el usuario tiene los privilegios para hacerlo)

php app/console doctrine:database:create

El esquema

Para contarle a Doctrine sobre nuestra base de datos, crearemos ficheros de metadata que describirán como nuestros objetos serán almacenados en la base de datos Debe crear antes el directorio src/Ens/JobeetBundle/Resources/config/doctrine/. Luego, el tipo de archivo dentro de Netbeans es YAML

src/Ens/JobeetBundle/Resources/config/doctrine/Category.orm.yml
Ens\JobeetBundle\Entity\Category:
 type: entity
 table: category
 id:
   id:
     type: integer
     generator: { strategy: AUTO }
 fields:
   name:
     type: string
     length: 255
     unique: true
 oneToMany:
   jobs:
     targetEntity: Job
     mappedBy: category
   category_affiliates:
     targetEntity: CategoryAffiliate
     mappedBy: category
src/Ens/JobeetBundle/Resources/config/doctrine/Job.orm.yml
Ens\JobeetBundle\Entity\Job:
 type: entity
 table: job
 id:
   id:
     type: integer
     generator: { strategy: AUTO }
 fields:
   type:
     type: string
     length: 255
     nullable: true
   company:
     type: string
     length: 255
   logo:
     type: string
     length: 255
     nullable: true
   url:
     type: string
     length: 255
     nullable: true
   position:
     type: string
     length: 255
   location:
     type: string
     length: 255
   description:
     type: text
   how_to_apply:
     type: text
   token:
     type: string
     length: 255
     unique: true
   is_public:
     type: boolean
     nullable: true
   is_activated:
     type: boolean
     nullable: true
   email:
     type: string
     length: 255
   expires_at:
     type: datetime
   created_at:
     type: datetime
   updated_at:
     type: datetime
     nullable: true
 manyToOne:
   category:
     targetEntity: Category
     inversedBy: jobs
     joinColumn:
       name: category_id
       referencedColumnName: id
 lifecycleCallbacks:
   prePersist: [ setCreatedAtValue ]
   preUpdate: [ setUpdatedAtValue ]
src/Ens/JobeetBundle/Resources/config/doctrine/Affiliate.orm.yml
Ens\JobeetBundle\Entity\Affiliate:
 type: entity
 table: affiliate
 id:
   id:
     type: integer
     generator: { strategy: AUTO }
 fields:
   url:
     type: string
     length: 255
   email:
     type: string
     length: 255
     unique: true
   token:
     type: string
     length: 255
   created_at:
     type: datetime
 oneToMany:
   category_affiliates:
     targetEntity: CategoryAffiliate
     mappedBy: affiliate
 lifecycleCallbacks:
   prePersist: [ setCreatedAtValue ]
src/Ens/JobeetBundle/Resources/config/doctrine/CategoryAffiliate.orm.yml
Ens\JobeetBundle\Entity\CategoryAffiliate:
 type: entity
 table: category_affiliate
 id:
   id:
     type: integer
     generator: { strategy: AUTO }
 manyToOne:
   category:
     targetEntity: Category
     inversedBy: category_affiliates
     joinColumn:
       name: category_id
       referencedColumnName: id
   affiliate:
     targetEntity: Affiliate
     inversedBy: category_affiliates
     joinColumn:
       name: affiliate_id
       referencedColumnName: id

El ORM

Ahora Doctrine puede generar las clases que definen nuestros objetos para nosotros con el siguiente comando:

php app/console doctrine:generate:entities EnsJobeetBundle

Si buscas en el directorio Entity de EnsJobeetBundle, encontrarás las recientemente creadas clases: Affiliate.php, Category.php, CategoryAffiliate.php and Job.php.
Abre Job.php y cambia los métodos created_at and updated_at como se indica abajo:

src/Ens/JobeetBundle/Entity/Job.php
// ...
public function setCreatedAtValue()
{
 if(!$this->getCreatedAt())
 {
   $this->created_at = new \DateTime();
 }
}
// ...
public function setUpdatedAtValue()
{
  $this->updated_at = new \DateTime();
}

Haz lo mismo modificando el método created_at de la clase Affiliate

src/Ens/JobeetBundle/Entity/Affiliate.php
// ...
public function setCreatedAtValue()
{
  $this->created_at = new \DateTime();
}

Esto hará que Doctrine establezca a los valores created_at y updated_at cuando guarde o actualice objetos: Este comportamiento fue definido en Job.orm.yml y Affiliate.orm.yml vistos arriba, precisamente en la sección lifecycleCallbacks con prePersist y preUpdate.

Pediremos también a Doctrine para que cree las tablas de nuestra base de datos (O actualizarlas para reflejar nuestra configuración) con el siguiente comando

php app/console doctrine:schema:update --force

Si intenta hacerlo desde Netbeans (U otro IDE), y su servidor de base de datos no aceptar conexiones remotas (Comportamiento por defecto en la mayoría), recibirá un error del tipo

[PDOException]                             
  SQLSTATE[HY000] [2002] Connection refused

Esta tarea solo debe ser usada durante el desarrollo de la aplicación

Datos iniciales

Las tablas han sido creadas en la base de datos, pero no hay datos en ellas. Para cualquier aplicación web, hay tres tipos de datos: initial data (Esta es necesaria para que la aplicación trabaje, en nuestro caso necesitamos categorías iniciales y un usuario admin), test data (Necesaria para que la aplicación sea probada) y user data (Creada por los usuarios durante la vida normal de la aplicación)

Para llenar la base de datos con datos iniciales usaremos DoctrineFixturesBundle. Para configurar este bundle tenemos que seguir los siguientes pasos

  • Modificar el fichero composer.json para añadir
composer.json
// ...
"require": {

"doctrine/doctrine-fixtures-bundle": "2.2.*"
},
  • Obtener composer.phar si no lo había hecho antes. (Ejecute desde consola en la raíz del proyecto)
curl -s http://getcomposer.org/installer | php
  • Obtener el bundle. (Ejecutar desde consola)
php composer.phar update

La salida por consola es bastante parecida a esto:

Actualizacion con composer.phar.png
  • Registra DoctrienFixturesBundle in app/AppKernel.php para que tenga acceso a los comandos que este bundle ofrece
app/AppKernel.php
public function registerBundles()
{
    $bundles = array(
        // ...
        new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle(),
    );
}

Cree el directorio src/Ens/JobeetBundle/DataFixtures/
Cree el directorio src/Ens/JobeetBundle/DataFixtures/ORM
Ahora que todo esta configurado crearemos algunas clases nuevas para cargar datos en un nuevo folder de nuestro bundle src/Ens/JobeetBundle/DataFixtures/ORM.

src/Ens/JobeetBundle/DataFixtures/ORM/LoadCategoryData.php
namespace Ens\JobeetBundle\DataFixtures\ORM;
 
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Ens\JobeetBundle\Entity\Category;
 
class LoadCategoryData extends AbstractFixture implements OrderedFixtureInterface
{
  public function load(ObjectManager $em)
  {
    $design = new Category();
    $design->setName('Design');
 
    $programming = new Category();
    $programming->setName('Programming');
 
    $manager = new Category();
    $manager->setName('Manager');
 
    $administrator = new Category();
    $administrator->setName('Administrator');
 
    $em->persist($design);
    $em->persist($programming);
    $em->persist($manager);
    $em->persist($administrator);
 
    $em->flush();
 
    $this->addReference('category-design', $design);
    $this->addReference('category-programming', $programming);
    $this->addReference('category-manager', $manager);
    $this->addReference('category-administrator', $administrator);
  }
 
  public function getOrder()
  {
    return 1; // the order in which fixtures will be loaded
  }
}


src/Ens/JobeetBundle/DataFixtures/ORM/LoadJobData.php
namespace Ens\JobeetBundle\DataFixtures\ORM;
 
use Doctrine\Common\Persistence\ObjectManager;
use Doctrine\Common\DataFixtures\AbstractFixture;
use Doctrine\Common\DataFixtures\OrderedFixtureInterface;
use Ens\JobeetBundle\Entity\Job;
 
class LoadJobData extends AbstractFixture implements OrderedFixtureInterface
{
  public function load(ObjectManager $em)
  {
    $job_sensio_labs = new Job();
    $job_sensio_labs->setCategory($em->merge($this->getReference('category-programming')));
    $job_sensio_labs->setType('full-time');
    $job_sensio_labs->setCompany('Sensio Labs');
    $job_sensio_labs->setLogo('sensio-labs.gif');
    $job_sensio_labs->setUrl('http://www.sensiolabs.com/');
    $job_sensio_labs->setPosition('Web Developer');
    $job_sensio_labs->setLocation('Paris, France');
    $job_sensio_labs->setDescription('You\'ve already developed websites with symfony and you want to work with Open-Source technologies. You have a minimum of 3 years experience in web development with PHP or Java and you wish to participate to development of Web 2.0 sites using the best frameworks available.');
    $job_sensio_labs->setHowToApply('Send your resume to fabien.potencier [at] sensio.com');
    $job_sensio_labs->setIsPublic(true);
    $job_sensio_labs->setIsActivated(true);
    $job_sensio_labs->setToken('job_sensio_labs');
    $job_sensio_labs->setEmail('job@example.com');
    $job_sensio_labs->setExpiresAt(new \DateTime('2014-10-10'));
 
    $job_extreme_sensio = new Job();
    $job_extreme_sensio->setCategory($em->merge($this->getReference('category-design')));
    $job_extreme_sensio->setType('part-time');
    $job_extreme_sensio->setCompany('Extreme Sensio');
    $job_extreme_sensio->setLogo('extreme-sensio.gif');
    $job_extreme_sensio->setUrl('http://www.extreme-sensio.com/');
    $job_extreme_sensio->setPosition('Web Designer');
    $job_extreme_sensio->setLocation('Paris, France');
    $job_extreme_sensio->setDescription('Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in.');
    $job_extreme_sensio->setHowToApply('Send your resume to fabien.potencier [at] sensio.com');
    $job_extreme_sensio->setIsPublic(true);
    $job_extreme_sensio->setIsActivated(true);
    $job_extreme_sensio->setToken('job_extreme_sensio');
    $job_extreme_sensio->setEmail('job@example.com');
    $job_extreme_sensio->setExpiresAt(new \DateTime('2014-10-10'));
 
    $em->persist($job_sensio_labs);
    $em->persist($job_extreme_sensio);
 
    $em->flush();
  }
 
  public function getOrder()
  {
    return 2; // the order in which fixtures will be loaded
  }
}

Una vez que las fixtures han sido escritas, puedes cargarlas por la línea de comandos usando el comando doctrine:fixtures:load

php app/console doctrine:fixtures:load

Revisa ahora la base de datos, debería ver los datos cargados en la tabla
El archivo de la fixture job referencia dos imágenes:Descargalas de sensio-labs.gif, extreme-sension.gif y colócalas en el directorio web/uploads/jobs/, directorio que habrá que crear antes

Miralo en acción en el navegador

¡Hagamos ahora un poco de magia! Corre el siguiente comando:

php app/console doctrine:generate:crud --entity=EnsJobeetBundle:Job --route-prefix=job --with-write --format=yml

Si no ha cuidado del todo el problema de permisos, un error como el siguiente puede producirse

Importing the CRUD routes: FAILED

                                                                   
  The command was not able to configure everything automatically.  
  You must do the following changes manually.                      
                                                                   

- Import the bundle's routing resource in the bundle routing file
  (/home/alortiz/public_html/segundo.salud.gob.sv/src/Ens/JobeetBundle/Resources/config/routing.yml).

    EnsJobeetBundle_job:
        resource: "@EnsJobeetBundle/Resources/config/routing/job.yml"
        prefix:   /job

Esto creará un nuevo controlador src/Ens/JobeetBundle/Controllers/JobController.php con acciones para listar, crear, editar y borrar ofertas de trabajo (y sus correspondientes plantillas, formularios y rutas) Para ver esto en el navegador debemos importar las nuevas rutas que creamos en src/Ens/JobeetBundle/Recources/config/routing/job.yml en nuestro archivo principal de rutas

src/Ens/JobeetBundle/Resources/config/routing.yml
EnsJobeetBundle_job:
    resource: "@EnsJobeetBundle/Resources/config/routing/job.yml"
    prefix: /job
 
EnsJobeetBundle_homepage:
    pattern:  /hello/{name}
    defaults: { _controller: EnsJobeetBundle:Default:index }

También necesitamos añadir a metodo __toString() a nuestra clase Category para ser usada por

src/Ens/JobeetBundle/Entity/Category.php
// src/Ens/JobeetBundle/Entity/Category.php
// ...
public function __toString()
{
  return $this->getName();
}

Limpia la cache:

php app/console cache:clear --env=prod
php app/console cache:clear --env=dev

Ahora puedes probar el nuevo controlador en un navegador, en el entorno de desarrollo http://<dominio>/app_dev.php/job/

Primera vista01.png

Ahora puedes crear y editar ofertas de trabajo. Prueba a dejar un campo requerido en blanco, o intenta ingresar una fecha inválida. Así es: Symfony ha creado reglas básicas de validación mirando dentro del esquema de base de datos.

Primera vista02.png
Herramientas personales
Espacios de nombres

Variantes
Acciones
Navegación
Herramientas