Diferència entre revisions de la pàgina «A2- Framework de capes per a DDBB (20h)»

De wikiserver
Dreceres ràpides: navegació, cerca
(SUBIR FICHEROS)
(Mapeando relaciones)
 
(Hi ha 42 revisions intermèdies del mateix usuari que no es mostren)
Línia 291: Línia 291:
 
         //recupera el repositorio de la entidad Entidad
 
         //recupera el repositorio de la entidad Entidad
 
         $repositorio = $this->getDoctrine()->getRepository('CosasBundle:entidad');
 
         $repositorio = $this->getDoctrine()->getRepository('CosasBundle:entidad');
         $resultados=$repositorio->'''findOneByNombre'''($nombre);  //busca la primera fila que empiece por Nombre, si fuera findOneByEdad buscaría por edad
+
         $resultados=$repositorio->findOneByNombre($nombre);  //busca la primera fila que empiece por Nombre, si fuera findOneByEdad buscaría por edad
         //$resultados=$repositorio->'''find'''(5);    //busca por id
+
         //$resultados=$repositorio->find(5);    //busca por id
 
         return $this->render('CosasBundle:Default:formulario.html.twig',array("resul"=>$resultados));
 
         return $this->render('CosasBundle:Default:formulario.html.twig',array("resul"=>$resultados));
 
     }
 
     }
Línia 369: Línia 369:
 
</source>
 
</source>
  
 +
 +
 +
 +
<span style="color:red">'''ELIMINAR datos en la base de datos (fila)'''</span>
 +
 +
 +
'''ROUTING'''
 +
<source lang="php">
 +
formu_actualizar:
 +
    path:    /eliminar/{id}
 +
    defaults: { _controller: CosasBundle:Default:eliminar } 
 +
 +
</source>
 +
 +
'''CONTROLADOR'''
 +
<source lang="php">
 +
    public function eliminarAction($id)
 +
    {
 +
 +
        $em = $this->getDoctrine()->getManager();
 +
        $dato = $em->getRepository('CosasBundle:entidad')->find($id);
 +
 +
        if (!$dato) {
 +
            throw $this->createNotFoundException('No product found for id '.$id );
 +
        }
 +
        $em->remove($dato);
 +
        $em->flush();
 +
        return $this->redirectToRoute('formu_listar');  //tenemos que poner el path que está en el routing.yml en este caso listar
 +
 +
    }
 +
</source>
 +
 +
<!--
 
==  Persistint objectes a la base de dades ==
 
==  Persistint objectes a la base de dades ==
 
Ara que tens mapeada una entitat Product i la seva taula product corresponent, ja pots persistir la informació en la base de dades. De fet, persistir informació dins d'un controlador és bastant senzill. Afegeix el següent mètode al controlador DefaultController del bundle:
 
Ara que tens mapeada una entitat Product i la seva taula product corresponent, ja pots persistir la informació en la base de dades. De fet, persistir informació dins d'un controlador és bastant senzill. Afegeix el següent mètode al controlador DefaultController del bundle:
Línia 426: Línia 459:
 
Una vegada que obtens el repositori, tens accés a tot tipus de mètodes útils:
 
Una vegada que obtens el repositori, tens accés a tot tipus de mètodes útils:
  
==Actualitzant un objecte==
+
-->
 +
 
 +
<!--==Actualitzant un objecte==
 
Una vegada que hagis obtingut un objecte de Doctrine, actualitzar-ho és relativament fàcil. Suposem que l'aplicació disposa d'una ruta que actualitza la informació del producte amb identificador '''id''':
 
Una vegada que hagis obtingut un objecte de Doctrine, actualitzar-ho és relativament fàcil. Suposem que l'aplicació disposa d'una ruta que actualitza la informació del producte amb identificador '''id''':
  
Línia 443: Línia 478:
 
     $product->setName('New product name!');
 
     $product->setName('New product name!');
 
     $em->flush();
 
     $em->flush();
+
  //Com pot ser que imaginis, el mètode '''remove() avisa a Doctrine que vols eliminar aquesta
 +
  //entitat de la base de dades''', però no l'esborra realment.
 +
  // La consulta '''DELETE corresponent no es genera ni s'executa fins que no s'invoca
 +
  // el mètode flush()'''.
 
     return $this->redirect($this->generateUrl('homepage'));
 
     return $this->redirect($this->generateUrl('homepage'));
 
}
 
}
Línia 455: Línia 493:
 
#Invocar al mètode '''flush()''' del entity manager.
 
#Invocar al mètode '''flush()''' del entity manager.
 
Observa que no fa falta cridar al mètode '''$em->persist($product)'''. Aquest mètode sive per avisar a Doctrine que vas a manipular un determinat objecte. En aquest cas, com l'objecte $product ho has obtingut mitjançant una consulta a Doctrine, aquest ja sap que ha d'estar atent als possibles canvis de l'objecte.
 
Observa que no fa falta cridar al mètode '''$em->persist($product)'''. Aquest mètode sive per avisar a Doctrine que vas a manipular un determinat objecte. En aquest cas, com l'objecte $product ho has obtingut mitjançant una consulta a Doctrine, aquest ja sap que ha d'estar atent als possibles canvis de l'objecte.
==Eliminant un objecte==
+
 
 +
-->
 +
 
 +
<!-- ==Eliminant un objecte==
  
 
Eliminar objectes és un procés similar, però requereix invocar el mètode remove() del entity manager:
 
Eliminar objectes és un procés similar, però requereix invocar el mètode remove() del entity manager:
Línia 463: Línia 504:
 
</source>
 
</source>
 
Com pot ser que imaginis, el mètode '''remove() avisa a Doctrine que vols eliminar aquesta entitat de la base de dades''', però no l'esborra realment. La consulta '''DELETE corresponent no es genera ni s'executa fins que no s'invoca el mètode flush()'''.
 
Com pot ser que imaginis, el mètode '''remove() avisa a Doctrine que vols eliminar aquesta entitat de la base de dades''', però no l'esborra realment. La consulta '''DELETE corresponent no es genera ni s'executa fins que no s'invoca el mètode flush()'''.
 +
-->
  
 
== Exercici ==
 
== Exercici ==
 
Actualitza l'exercici CRUD realitzat a la UF anterior per a que utilitzi BD amb Doctrine.
 
Actualitza l'exercici CRUD realitzat a la UF anterior per a que utilitzi BD amb Doctrine.
php bin/console generate:doctrine:crud
+
 
 +
'''solució:'''
 +
 
 +
<!--php bin/console generate:doctrine:crud-->
  
 
== Buscant Objectes amb el generador de consultes de Doctrine==
 
== Buscant Objectes amb el generador de consultes de Doctrine==
Línia 472: Línia 517:
 
Imagina que vols buscar tots aquells productes el preu dels quals sigui superior a 19.99 i retornar els resultats ordenats del més barat al més car. Aquesta cerca es pot realitzar de la següent manera amb el''' QueryBuilder de Doctrine''':
 
Imagina que vols buscar tots aquells productes el preu dels quals sigui superior a 19.99 i retornar els resultats ordenats del més barat al més car. Aquesta cerca es pot realitzar de la següent manera amb el''' QueryBuilder de Doctrine''':
 
<source lang="php">
 
<source lang="php">
$repository = $this->getDoctrine()
+
$repository = $this->getDoctrine()->getRepository('MiBundle:Product');
    ->getRepository('AcmeStoreBundle:Product');
 
 
   
 
   
 
$query = $repository->createQueryBuilder('p')
 
$query = $repository->createQueryBuilder('p')
Línia 479: Línia 523:
 
     ->setParameter('price', '19.99')
 
     ->setParameter('price', '19.99')
 
     ->orderBy('p.price', 'ASC')
 
     ->orderBy('p.price', 'ASC')
     ->getQuery();
+
     ->getQuery(); //retorna l'objecte de tipus Query amb el qual realment s'executa la consulta.
 
   
 
   
$products = $query->getResult();
+
$products = $query->getResult(); //devuelve array de resultados
 +
print_r($products);
 
</source>
 
</source>
  
Línia 499: Línia 544:
 
$query = $em->createQuery(
 
$query = $em->createQuery(
 
     'SELECT p
 
     'SELECT p
       FROM AcmeStoreBundle:Product p
+
       FROM MiBundle:Product p
 
       WHERE p.price > :price
 
       WHERE p.price > :price
 
   ORDER BY p.price ASC'
 
   ORDER BY p.price ASC'
)->setParameter('price', '19.99');
+
)->setParameter('price', '2');
 
   
 
   
 
$products = $query->getResult();
 
$products = $query->getResult();
 +
 +
print_r($products);
 
</source>
 
</source>
  
 
La sintaxi DQL és increïblement poderosa, permetent-te unir fàcilment diferents entitats (el tema de les relacions s'explica més endavant), realitzar agrupacions, etc. Per a més informació, consulta la documentació oficial de [http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html Doctrine Query Language].
 
La sintaxi DQL és increïblement poderosa, permetent-te unir fàcilment diferents entitats (el tema de les relacions s'explica més endavant), realitzar agrupacions, etc. Per a més informació, consulta la documentació oficial de [http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/dql-doctrine-query-language.html Doctrine Query Language].
  
Altra forma
+
'''Altra forma RECOMANADA'''
 
<source lang="java">
 
<source lang="java">
$sql = "SELECT * FROM Product";
+
        $sql = "SELECT * FROM Product";  // "SELECT * FROM Product WHERE name='zapato'";  
 
         $em = $this->getDoctrine()->getManager();
 
         $em = $this->getDoctrine()->getManager();
 
         $stmt = $em->getConnection()->prepare($sql);
 
         $stmt = $em->getConnection()->prepare($sql);
 
         $stmt->execute();
 
         $stmt->execute();
 
         $todo=$stmt->fetchAll();
 
         $todo=$stmt->fetchAll();
 +
        print_r($todo);
 
</source>
 
</source>
  
Línia 781: Línia 829:
  
 
[[Fitxer:category.png|center]]
 
[[Fitxer:category.png|center]]
 +
 +
Utilitzar
 +
<source lang="php">
 +
 +
php bin/console doctrine:generate:entities App
 +
 +
</source>
 +
 +
per a generar tots el get, set i add/remove del array
 +
 +
Després fem una actualització a la base de dades i podrem vore les relacions
  
 
=== Emmagatzemant les entitats relacionades===
 
=== Emmagatzemant les entitats relacionades===
Línia 860: Línia 919:
  
 
===Uniendo registros relacionados===
 
===Uniendo registros relacionados===
En els exemples anteriors, es realitzen dues consultes: la primera per a l'objecte original (Category per exemple) i la segona per el/els objectes relacionats (un array de Product per exemple).
+
<source lang=php>
 +
 
 +
//Le pasas la categoria (idcategoria) y te devuelve los productos que están relacionados con su Clave Ajena (mediante id_categoria)
 +
 
 +
public function obtenerInnerJoinCategoryProduct($id){
  
Si saps per endavant que vas a necessitar les dades de tots els objectes, pots estalviar-te una consulta fent una unió o "join" en la primera consulta. Es pot realitzar utilitzant repositoris propis i afegint els mètodes que necessitem.
+
      $sql='SELECT * FROM category INNER JOIN product ON category.id = product.category_id WHERE product.category_id='.$id;
  
<source lang="php">
+
        $em = $this->getDoctrine()->getManager();
// src/Acme/StoreBundle/Entity/ProductRepository.php
+
        $stmt = $em->getConnection()->prepare($sql);
public function findOneByIdJoinedToCategory($id)
+
        $stmt->execute();
{
+
        $todo=$stmt->fetchAll();
     $query = $this->getEntityManager()
+
    try {
 +
        return $todo;
 +
    } catch (\Doctrine\ORM\NoResultException $e) {
 +
        return null;
 +
    }
 +
 
 +
}
 +
</source>
 +
 
 +
En els exemples anteriors, es realitzen dues consultes: la primera per a l'objecte original (Category per exemple) i la segona per el/els objectes relacionats (un array de Product per exemple).
 +
 
 +
Si saps per endavant que vas a necessitar les dades de tots els objectes, pots estalviar-te una consulta fent una unió o "join" en la primera consulta. Es pot realitzar utilitzant repositoris propis i afegint els mètodes que necessitem.
 +
 
 +
<source lang="php">
 +
// src/Acme/StoreBundle/Entity/ProductRepository.php
 +
public function findOneByIdJoinedToCategory($id)
 +
{
 +
     $query = $this->getEntityManager()
 
         ->createQuery(
 
         ->createQuery(
 
             'SELECT p, c FROM AcmeStoreBundle:Product p
 
             'SELECT p, c FROM AcmeStoreBundle:Product p
Línia 986: Línia 1.066:
 
== Generar Entitats des de una BD creada ==
 
== Generar Entitats des de una BD creada ==
 
El primer pas per construir classes d'entitat a partir d'una base de dades és permeten que Doctrine introspecti la base de dades i generiu les corresponents metadades. Les metadades son les diferentes informacions que s'afegeixen als arxius que associen les propietats d'una classe amb el corresponent camp de la taula.
 
El primer pas per construir classes d'entitat a partir d'una base de dades és permeten que Doctrine introspecti la base de dades i generiu les corresponents metadades. Les metadades son les diferentes informacions que s'afegeixen als arxius que associen les propietats d'una classe amb el corresponent camp de la taula.
  $ php app/console doctrine:mapping:import --force AcmeBlogBundle xml
+
  $ php bin/console doctrine:mapping:import --force EjercicioBundle xml
 
Aquesta comanda permet a Doctrine fer una introspecció de la base de dades i generar les metadades en format XML.  
 
Aquesta comanda permet a Doctrine fer una introspecció de la base de dades i generar les metadades en format XML.  
 
Una vegada es generen les metadades, pots demanar a Doctrine que crei les classes (Entity) associades executant les següents comandes:
 
Una vegada es generen les metadades, pots demanar a Doctrine que crei les classes (Entity) associades executant les següents comandes:
  $ php app/console doctrine:mapping:convert annotation ./src
+
  $ php bin/console doctrine:mapping:convert annotation ./src
  $ php app/console doctrine:generate:entities AcmeBlogBundle
+
  $ php bin/console doctrine:generate:entities EjercicioBundle
  
 
=Pràctica Doctrine =
 
=Pràctica Doctrine =
Línia 1.386: Línia 1.466:
  
 
<source lang="php">
 
<source lang="php">
 +
use Symfony\Component\HttpFoundation\Request;
 +
 +
......
 +
 
public function registerAction(Request $request)
 
public function registerAction(Request $request)
 
     {
 
     {
Línia 1.439: Línia 1.523:
  
  
9. Ahora vamos a crear otro Bundle admin, para separar el login y el acceso entre usuarios y administrador.
+
-----------------------------------------
 +
 
 +
''' INICIO DE LOGIN USUARIOS '''
 +
 
 +
 
 +
-> En la versión 2 se recomendaba crear diferentes bundles y redirigir si es un usuario admin a AdminBundle y si es un usuario normal a otro Bundle.
 +
 
 +
 
 +
Ahora vamos a crear otro Bundle admin, para separar el login y el acceso entre usuarios y administrador.
  
php bin/console generate:bundle --namespace=Admin --format=yml
+
    php bin/console generate:bundle --namespace=Admin --format=yml
  
  
10. Una vez instalado el Bundle, tenemos que irnos a app->config->routing para separar las diferentes rutas de nuestros bundles  
+
Una vez instalado el Bundle, tenemos que irnos a app->config->routing para separar las diferentes rutas de nuestros bundles  
  
 
<source lang="php">
 
<source lang="php">
Línia 1.463: Línia 1.555:
 
</source>
 
</source>
  
'''1) OPCIÓN.''' Logearnos con http_basic(login cutre ), nos vamos a security y añadimos todo el contenido de a continuación con eso podremos entrar en la seccion admin si ya estas en la base de datos.
+
Para realizar el logout, hay que añadir en el routing principal, además de lo añadido en security
 
 
https://symfony.com/doc/3.0/security/entity_provider.html#configure-security-to-load-from-your-entity
 
 
 
 
<source lang="php">
 
<source lang="php">
security:
+
admin:
     encoders:
+
     resource: "@AdminBundle/Controller/"
      UsuariosBundle\Entity\User: bcrypt
+
    type:    annotation
 +
    prefix:   /admin
  
     # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
+
usuarios:
     providers:
+
     resource: "@UsuariosBundle/Controller/"
        nuestros_usuarios:
+
     type:     annotation
            entity:
+
    prefix:   /
                class: UsuariosBundle:User
 
                property: username
 
  
    firewalls:
+
app:
        # disables authentication for assets and the profiler, adapt it according to your needs
+
    resource: "@AppBundle/Controller/"
        dev:
+
    type:     annotation
            pattern: ^/(_(profiler|wdt)|css|images|js)/
 
            security: false
 
        admin:
 
            pattern: ^/admin
 
            http_basic: ~
 
            provider: nuestros_usuarios
 
  
        main:
+
logout:
            anonymous: ~
+
    path: /usuarios/logout
 
</source>
 
</source>
  
'''2) OPCIÓN.''' LOGIN con FORMULARIO.
 
  
https://symfony.com/doc/3.0/security/form_login_setup.html
+
-> En la versión 3 se recomienda como buenas prácticas en caso de tener un usuario admin que vaya a un controlador y los usuarios normales a otro o al Default.
 +
 
 +
'''Crear un nuevo Controller''' [https://symfony.com/doc/master/bundles/SensioGeneratorBundle/commands/generate_controller.html enlace]
 +
 
 +
php bin/console generate:controller --route-format=annotation
 +
 
  
Debemos modificar los parámetros de nuestro security, como queremos tener una ruta de admin y otra usuarios, en el cual alojaremos un login que podrá acceder cualquier persona y para el resto de contenido solo las personas autorizadas.  
+
Como ejemplo cómo redirigir a otro Controlador.
  
 
<source lang="php">
 
<source lang="php">
 +
mi_homepage:
 +
    path:    /
 +
    defaults: { _controller: MiBundle:Default:index }
 +
 +
mi_Admin:
 +
    path:    /admin
 +
    defaults: { _controller: MiBundle:Admin:index } 
 +
</source>
 +
 +
 +
 +
 +
== LOGIN BÁSICO USUARIOS EN MEMORIA==
 +
 +
'''Modificamos el FIREWALL'''
 +
 +
https://symfony.com/doc/3.2/security.html
 +
 +
https://symfony.com/doc/3.2/reference/configuration/security.html
 +
 +
<source lang="yml">
 
security:
 
security:
 
     encoders:
 
     encoders:
       UsuariosBundle\Entity\User: bcrypt
+
       UsuariosBundle\Entity\User: bcrypt   #seguridad encriptación para el usuarios
 
+
      Symfony\Component\Security\Core\User\User: plaintext  #seguridad encriptación para el admin
    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
+
 
 
     providers:
 
     providers:
        our_db_provider:
+
            in_memory:
            entity:
+
                memory:   #usuarios en memoria
                class: UsuariosBundle:User
+
                    users:
                property: username
+
                        admin:
 +
                            password: 1234
 +
                            roles: 'ROLE_ADMIN'
  
 
     firewalls:
 
     firewalls:
         # disables authentication for assets and the profiler, adapt it according to your needs
+
          
         dev:
+
         dev: #deshabilitamos la seguridad para el modo desarrollador
 
             pattern: ^/(_(profiler|wdt)|css|images|js)/
 
             pattern: ^/(_(profiler|wdt)|css|images|js)/
 
             security: false
 
             security: false
  
 
         admin:
 
         admin:
             pattern: ^/admin
+
          # anonymous: ~    #deshabilitamos para que no pueda accedir ninguno y nos aparece el login básico
 +
             pattern: ^/admin
 
             http_basic: ~
 
             http_basic: ~
            provider: our_db_provider
+
 
         usuarios:
+
         main:
            pattern: ^/usuarios
 
 
             anonymous: ~
 
             anonymous: ~
            form_login:
 
                  login_path: /usuarios/login
 
                  check_path: /usuarios/login
 
            logout:
 
                  path: /usuarios/logout
 
                  target: /usuarios
 
 
    access_control:
 
        - { path: ^/usuarios/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
 
        - { path: ^/usuarios, roles: ROLE_USER }
 
 
</source>
 
</source>
  
Añadimos un controlador de LOGIN y modificamos todos los controladores para que empiecen en /usuarios
+
== LOGIN BÁSICO USUARIOS EN BBDD ==
  
<source lang="php">
+
$2y$13$D3esHHJkhPQnFsKzAlDW/.RExL5/GtWbWJmzUUsRmUXrxfiFiLhte
  
  /**
+
Logearnos con http_basic(login cutre ), nos vamos a security y añadimos todo el contenido de a continuación con eso podremos entrar en la seccion admin si ya estas en la base de datos.
    * @Route("/usuarios")
 
    */
 
    public function indexAction()
 
    {
 
        return $this->render('UsuariosBundle:Default:index.html.twig');
 
    }
 
  
 +
https://symfony.com/doc/3.0/security/entity_provider.html#configure-security-to-load-from-your-entity
  
    /**
+
<source lang="php">
    * @Route("/usuarios/login", name="login")
+
security:
    */
+
     encoders:
     public function loginAction(Request $request)
+
      UsuariosBundle\Entity\User: bcrypt
    {
 
  
        $authenticationUtils = $this->get('security.authentication_utils');
+
    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
 +
    providers:
 +
        nuestros_usuarios:
 +
            entity:
 +
                class: UsuariosBundle:User
 +
                property: username
  
         // get the login error if there is one
+
    firewalls:
         $error = $authenticationUtils->getLastAuthenticationError();
+
         # disables authentication for assets and the profiler, adapt it according to your needs
 +
         dev:
 +
            pattern: ^/(_(profiler|wdt)|css|images|js)/
 +
            security: false
 +
        admin:
 +
            pattern: ^/admin
 +
            http_basic: ~
 +
            provider: nuestros_usuarios
  
         // last username entered by the user
+
         main:
        $lastUsername = $authenticationUtils->getLastUsername();
+
            anonymous: ~
 +
</source>
  
        return $this->render(
+
== LOGIN CON FORMULARIO. ==  
            'UsuariosBundle:Default:login.html.twig',
 
            array(
 
                // last username entered by the user
 
                'last_username' => $lastUsername,
 
                'error'        => $error,
 
            )
 
        );
 
    }
 
  
 +
https://symfony.com/doc/3.0/security/form_login_setup.html
  
 +
Debemos modificar los parámetros de nuestro security, como queremos tener una ruta de admin y otra usuarios, en el cual alojaremos un login que podrá acceder cualquier persona y para el resto de contenido solo las personas autorizadas.
  
    /**
+
<source lang="php">
    * @Route("/usuarios/register", name="user_registration")
+
security:
    */
+
     encoders:
     public function registerAction(Request $request)
+
      MiBundle\Entity\User: bcrypt
    {
+
      #cost: 12
        // 1) build the form
 
        $user = new User();
 
        $form = $this->createForm(UserType::class, $user);
 
  
        // 2) handle the submit (will only happen on POST)
+
    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
         $form->handleRequest($request);
+
    providers:
        if ($form->isSubmitted() && $form->isValid()) {
+
         our_db_provider:
 +
            entity:
 +
                class: MiBundle:User
 +
                property: username
  
            // 3) Encode the password (you could also do this via Doctrine listener)
+
    firewalls:
             $password = $this->get('security.password_encoder')
+
        # disables authentication for assets and the profiler, adapt it according to your needs
                ->encodePassword($user, $user->getPlainPassword());
+
        dev:
             $user->setPassword($password);
+
             pattern: ^/(_(profiler|wdt)|css|images|js)/
 +
             security: false
  
             // 4) save the User!
+
        admin:
             $em = $this->getDoctrine()->getManager();
+
             pattern: ^/admin
             $em->persist($user);
+
             http_basic: ~
             $em->flush();
+
             provider: our_db_provider
 
+
        usuarios:
             // ... do any other work - like sending them an email, etc
+
             pattern: ^/usuarios 
            // maybe set a "flash" success message for the user
+
            anonymous: ~
 
+
             form_login:
            //return $this->redirectToRoute('replace_with_some_route');
+
                  login_path: /usuarios/login
            return new Response("Usuario Registrado");
+
                  check_path: /usuarios/login
        }
+
            logout:
 
+
                  path: /usuarios/logout
        return $this->render(
+
                  target: /usuarios
            'UsuariosBundle:Default:register.html.twig',
 
            array('form' => $form->createView())
 
        );
 
    }
 
  
 +
    access_control:
 +
        - { path: ^/usuarios/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
 +
        - { path: ^/usuarios, roles: ROLE_USER }
 
</source>
 
</source>
  
Y la vista que irá nuestro login
+
Añadimos un controlador de LOGIN y modificamos todos los controladores para que empiecen en /usuarios
  
 
<source lang="php">
 
<source lang="php">
{# app/Resources/views/security/login.html.twig #}
 
{# ... you will probably extends your base template, like base.html.twig #}
 
<body>
 
{% if error %}
 
    <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
 
{% endif %}
 
  
<form action="{{ path('login') }}" method="post">
+
  /**
     <label for="username">Username:</label>
+
    * @Route("/usuarios")
     <input type="text" id="username" name="_username" value="{{ last_username }}" />
+
    */
 +
    public function indexAction()
 +
     {
 +
        return $this->render('UsuariosBundle:Default:index.html.twig');
 +
     }
  
    <label for="password">Password:</label>
 
    <input type="password" id="password" name="_password" />
 
  
     {#
+
     /**
        If you want to control the URL the user
+
    * @Route("/usuarios/login", name="login")
        is redirected to on success (more details below)
+
    */
        <input type="hidden" name="_target_path" value="/account" />
+
     public function loginAction(Request $request)
     #}
+
    {
  
    <button type="submit">login</button>
+
        $authenticationUtils = $this->get('security.authentication_utils');
</form>
 
</body>
 
</source>
 
  
Para realizar el logout, hay que añadir en el routing principal, además de lo añadido en security
+
        // get the login error if there is one
<source lang="php">
+
        $error = $authenticationUtils->getLastAuthenticationError();
admin:
 
    resource: "@AdminBundle/Controller/"
 
    type:    annotation
 
    prefix:  /admin
 
  
usuarios:
+
        // last username entered by the user
    resource: "@UsuariosBundle/Controller/"
+
        $lastUsername = $authenticationUtils->getLastUsername();
    type:    annotation
 
    prefix:  /
 
  
app:
+
        return $this->render(
    resource: "@AppBundle/Controller/"
+
            'UsuariosBundle:Default:login.html.twig',
     type:    annotation
+
            array(
 +
                // last username entered by the user
 +
                'last_username' => $lastUsername,
 +
                'error'        => $error,
 +
            )
 +
        );
 +
     }
  
logout:
 
    path: /usuarios/logout
 
</source>
 
  
y en la vista
 
<source lang="php">
 
<body>Hello World User!
 
  
<a href="/usuarios/logout">salir</a>
+
    /**
</body>
+
    * @Route("/usuarios/register", name="user_registration")
</source>
+
    */
 +
    public function registerAction(Request $request)
 +
    {
 +
        // 1) build the form
 +
        $user = new User();
 +
        $form = $this->createForm(UserType::class, $user);
  
si queremos ver una serie de parámetros de nuestros usuarios
+
        // 2) handle the submit (will only happen on POST)
 +
        $form->handleRequest($request);
 +
        if ($form->isSubmitted() && $form->isValid()) {
  
<source lang="php">
+
            // 3) Encode the password (you could also do this via Doctrine listener)
//desde la vista
+
            $password = $this->get('security.password_encoder')
{% if is_granted('ROLE_USER') %}{% endif %}
+
                ->encodePassword($user, $user->getPlainPassword());
 +
            $user->setPassword($password);
  
{{ dump(app.user) }}
+
            // 4) save the User!
 +
            $em = $this->getDoctrine()->getManager();
 +
            $em->persist($user);
 +
            $em->flush();
  
 +
            // ... do any other work - like sending them an email, etc
 +
            // maybe set a "flash" success message for the user
  
{% if app.user.username=='admin' %}{% endif %}
+
            //return $this->redirectToRoute('replace_with_some_route');
 +
            return new Response("Usuario Registrado");
 +
        }
  
 +
        return $this->render(
 +
            'UsuariosBundle:Default:register.html.twig',
 +
            array('form' => $form->createView())
 +
        );
 +
    }
  
// desde el controlador
 
if($this->getUser()){}
 
 
if($this->getUser()->getUsername()=="conserjeria") {}
 
 
</source>
 
</source>
  
=== SUBIR FICHEROS ===
+
Y la vista que irá nuestro login
https://symfony.com/doc/current/controller/upload_file.html
 
 
 
  
'''Entity'''
 
 
<source lang="php">
 
<source lang="php">
namespace MiBundle\Entity;
+
{# app/Resources/views/security/login.html.twig #}
 +
{# ... you will probably extends your base template, like base.html.twig #}
 +
<body>
 +
{% if error %}
 +
    <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
 +
{% endif %}
  
use Doctrine\ORM\Mapping as ORM;
+
<form action="{{ path('login') }}" method="post">
use Symfony\Component\Validator\Constraints as Assert;
+
    <label for="username">Username:</label>
 +
    <input type="text" id="username" name="_username" value="{{ last_username }}" />
  
class Product
+
    <label for="password">Password:</label>
{
+
     <input type="password" id="password" name="_password" />
     // ...
 
  
     /**
+
     {#
    * @ORM\Column(type="string")
+
        If you want to control the URL the user
    *
+
        is redirected to on success (more details below)
    * @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
+
        <input type="hidden" name="_target_path" value="/account" />
    * @Assert\File(mimeTypes={ "application/zip" })
+
     #}
    */
 
     private $brochure;
 
  
     public function getBrochure()
+
     <button type="submit">login</button>
    {
+
</form>
        return $this->brochure;
+
</body>
    }
+
</source>
  
     public function setBrochure($brochure)
+
Para realizar el logout, hay que añadir en el routing principal, además de lo añadido en security
     {
+
<source lang="php">
        $this->brochure = $brochure;
+
mi:
 +
     resource: "@MiBundle/Resources/config/routing.yml"
 +
     prefix:  /
 +
 
 +
app:
 +
    resource: "@AppBundle/Controller/"
 +
    type:    annotation
  
        return $this;
+
logout:
     }
+
     path: /usuarios/logout
}
 
 
</source>
 
</source>
  
'''ProductType'''
+
y en la vista
 
<source lang="php">
 
<source lang="php">
// src/Form/ProductType.php
+
<body>Hello World User!
namespace MiBundle\Form;
 
  
use MiBundle\Entity\Product;
+
<a href="{{path("logout")}}">salir</a>
use Symfony\Component\Form\AbstractType;
+
</body>
use Symfony\Component\Form\FormBuilderInterface;
+
</source>
use Symfony\Component\OptionsResolver\OptionsResolver;
 
use Symfony\Component\Form\Extension\Core\Type\FileType;
 
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 
use Symfony\Component\Form\Extension\Core\Type\ResetType;
 
  
class ProductType extends AbstractType
+
si queremos ver una serie de parámetros de nuestros usuarios
{
 
    public function buildForm(FormBuilderInterface $builder, array $options)
 
    {
 
        $builder
 
            // ...
 
            ->add('brochure', FileType::class, array('label' => 'Brochure (PDF file)'))
 
            ->add('salvar',SubmitType::class)
 
            // ...
 
        ;
 
    }
 
  
    public function configureOptions(OptionsResolver $resolver)
+
<source lang="php">
    {
+
//desde la vista
        $resolver->setDefaults(array(
+
{% if is_granted('ROLE_USER') %}{% endif %}
            'data_class' => Product::class,
+
{% if is_granted('ROLE_ADMIN') %} <a href="...">Borrar</a>{% endif %}
        ));
 
    }
 
}
 
</source>
 
  
 +
{{ dump(app.user) }}
  
'''CONTROLADOR'''
 
<source lang="php">
 
  
/**
+
{% if app.user.username=='admin' %}{% endif %}
    * @Route("/new", name="app_product_new")
 
    */
 
    public function newAction(Request $request)
 
    {
 
        $product = new Product();
 
        $form = $this->createForm(ProductType::class, $product);
 
        $form->handleRequest($request);
 
  
        if ($form->isSubmitted() && $form->isValid()) {
 
            // $file stores the uploaded PDF file
 
            /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
 
            $file = $product->getBrochure();
 
  
            $fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();
+
// desde el controlador
 +
if($this->getUser()){}
  
            // moves the file to the directory where brochures are stored
+
if($this->getUser()->getUsername()=="conserjeria") {}
            $file->move(
 
                $this->getParameter('brochures_directory'),
 
                $fileName
 
            );
 
  
            // updates the 'brochure' property to store the PDF file name
 
            // instead of its contents
 
                $product->setBrochure($fileName);
 
  
            // ... persist the $product variable or any other work
+
//Denegar el acceso desde el controlador
         
+
  $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');
            return $this->redirect($this->generateUrl('app_product_list'));
+
</source>
        }
 
  
        return $this->render('MiBundle:Default:vista.html.twig', array(
+
Crear Usuarios Sin Login desde el controlador
            'form' => $form->createView(),
 
        ));
 
    }
 
  
/**
+
https://stackoverflow.com/questions/5886713/automatic-post-registration-user-authentication/19578674#19578674
    * @return string
+
 
    */
+
<source lang="php">
    private function generateUniqueFileName()
+
 
    {
+
$user = new User();
        // md5() reduces the similarity of the file names generated by
+
$user->setUserName("julio");
        // uniqid(), which is based on timestamps
+
$user->setEmail("julio@julio.com");
        return md5(uniqid());
+
$user->setPassword("1234");
    }
 
  
 +
        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
 +
        $this->get('security.token_storage')->setToken($token);
 +
        $this->get('session')->set('_security_main', serialize($token));
 
</source>
 
</source>
  
  
 +
=== SUBIR FICHEROS ===
 +
https://symfony.com/doc/current/controller/upload_file.html
  
  
'''VISTA'''
+
'''Entity'''
 
<source lang="php">
 
<source lang="php">
<body>
+
namespace MiBundle\Entity;
<h1>Añadir nuevo producto</h1>
 
{{ form_start(form) }}
 
{# ... #}
 
  
{{ form_row(form.brochure) }}
+
use Doctrine\ORM\Mapping as ORM;
{{ form_end(form) }}
+
use Symfony\Component\Validator\Constraints as Assert;
  
</body>
+
class Product
</source>
+
{
 +
    // ...
 +
 
 +
    /**
 +
    * @ORM\Column(type="string")
 +
    *
 +
    * @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
 +
    * @Assert\File(mimeTypes={ "application/zip" })
 +
    */
 +
    private $brochure;
 +
 
 +
    public function getBrochure()
 +
    {
 +
        return $this->brochure;
 +
    }
  
'''app->config->config.yml'''
+
    public function setBrochure($brochure)
<source lang="php">
+
    {
 +
        $this->brochure = $brochure;
  
parameters:
+
        return $this;
     locale: en
+
     }
    brochures_directory: '%kernel.root_dir%/../web/imagenes'
+
}
 
</source>
 
</source>
  
 +
'''ProductType'''
 
<source lang="php">
 
<source lang="php">
</source>
+
// src/Form/ProductType.php
 +
namespace MiBundle\Form;
  
===CKEditor===
+
use MiBundle\Entity\Product;
https://symfony.com/doc/master/bundles/IvoryCKEditorBundle/index.html
+
use Symfony\Component\Form\AbstractType;
 +
use Symfony\Component\Form\FormBuilderInterface;
 +
use Symfony\Component\OptionsResolver\OptionsResolver;
 +
use Symfony\Component\Form\Extension\Core\Type\FileType;
 +
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 +
use Symfony\Component\Form\Extension\Core\Type\ResetType;
  
1.- Instalamos el
+
class ProductType extends AbstractType
 
 
$ composer require egeloen/ckeditor-bundle
 
 
 
$ php bin/console ckeditor:install
 
 
 
$ php bin/console assets:install web
 
 
 
2.- Añadir el bundle en app/AppKernel.php,
 
 
 
<source lang="php">
 
class AppKernel extends Kernel
 
 
{
 
{
     public function registerBundles()
+
     public function buildForm(FormBuilderInterface $builder, array $options)
 
     {
 
     {
         $bundles = array(
+
         $builder
            new Ivory\CKEditorBundle\IvoryCKEditorBundle(),
 
 
             // ...
 
             // ...
         );
+
            ->add('brochure', FileType::class, array('label' => 'Brochure (PDF file)'))
 +
            ->add('salvar',SubmitType::class)
 +
            // ...
 +
         ;
 +
    }
  
         // ...
+
    public function configureOptions(OptionsResolver $resolver)
 +
    {
 +
         $resolver->setDefaults(array(
 +
            'data_class' => Product::class,
 +
        ));
 
     }
 
     }
 
}
 
}
 
</source>
 
</source>
  
3. Creamos la entidad Ejemplo, que tenga dos atributos(descripcion/top), para que veamos la diferencia.
 
 
4. Creamos el formulario (EjemploType), descripción de tipo CKEditorType y top de tipo TextareaType.
 
  
 +
'''CONTROLADOR'''
 
<source lang="php">
 
<source lang="php">
use Symfony\Component\Form\AbstractType;
 
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
 
use Symfony\Component\Form\FormBuilderInterface;
 
use Symfony\Component\OptionsResolver\OptionsResolver;
 
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
 
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 
  
class EjemploType extends AbstractType
+
/**
{
+
     * @Route("/new", name="app_product_new")
    /**
 
     * @param FormBuilderInterface $builder
 
    * @param array $options
 
 
     */
 
     */
     public function buildForm(FormBuilderInterface $builder, array $options)
+
     public function newAction(Request $request)
 
     {
 
     {
         $builder
+
         $product = new Product();
            ->add('descripcion',CKEditorType::class)
+
        $form = $this->createForm(ProductType::class, $product);
             ->add('top',TextareaType::class)
+
        $form->handleRequest($request);
             ->add('salvar',SubmitType::class)
+
 
        ;
+
        if ($form->isSubmitted() && $form->isValid()) {
    }
+
             // $file stores the uploaded PDF file
   
+
            /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
    /**
+
            $file = $product->getBrochure();
    * @param OptionsResolver $resolver
+
 
    */
+
            $fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();  //genera un nombre único para la extensión guardada de antes.
    public function configureOptions(OptionsResolver $resolver)
+
 
    {
+
             // mueve el fichero al directorio donde las "brochures" son almacenadas.
         $resolver->setDefaults(array(
+
            $file->move(
             'data_class' => 'MiBundle\Entity\Ejemplo'
+
                $this->getParameter('brochures_directory'),
         ));
+
                $fileName
     }
+
            );
}
+
 
</source>
+
            // actualiza la'brochure' propiedad para almacenar el fichero PDF
 +
            // instead of its contents
 +
                $product->setBrochure($fileName);
 +
 
 +
            // ... persist the $product variable or any other work
 +
         
 +
            return $this->redirect($this->generateUrl('app_product_list'));  //redirige al sitio indicado.
 +
        }
 +
 
 +
         return $this->render('MiBundle:Default:vista.html.twig', array(
 +
             'form' => $form->createView(),
 +
         ));
 +
     }
  
5. Añadimos en el controlador.
 
<source lang="php">
 
 
  /**
 
  /**
     * @Route("/editor", name="nuevo_editpr")
+
     * @return string
 
     */
 
     */
     public function editorAction(Request $request)
+
    private function generateUniqueFileName()
 +
    {
 +
        // md5() reduces the similarity of the file names generated by
 +
        // uniqid(), which is based on timestamps
 +
        return md5(uniqid());
 +
    }
 +
 
 +
</source>
 +
 
 +
 
 +
 
 +
 
 +
'''VISTA'''
 +
<source lang="php">
 +
<body>
 +
<h1>Añadir nuevo producto</h1>
 +
{{ form_start(form) }}
 +
{# ... #}
 +
 
 +
{{ form_row(form.brochure) }}
 +
{{ form_end(form) }}
 +
 
 +
</body>
 +
</source>
 +
 
 +
'''app->config->config.yml'''
 +
<source lang="php">
 +
 
 +
parameters:
 +
    locale: en
 +
    brochures_directory: '%kernel.root_dir%/../web/imagenes'
 +
</source>
 +
 
 +
<source lang="php">
 +
</source>
 +
 
 +
===CKEditor===
 +
https://symfony.com/doc/master/bundles/IvoryCKEditorBundle/index.html
 +
 
 +
1.- Instalamos el
 +
 
 +
$ composer require egeloen/ckeditor-bundle
 +
 
 +
$ php bin/console assets:install web
 +
 
 +
2.- Añadir el bundle en app/AppKernel.php,
 +
 
 +
<source lang="php">
 +
class AppKernel extends Kernel
 +
{
 +
    public function registerBundles()
 +
    {
 +
        $bundles = array(
 +
            new Ivory\CKEditorBundle\IvoryCKEditorBundle(),
 +
            // ...
 +
        );
 +
 
 +
        // ...
 +
    }
 +
}
 +
</source>
 +
 
 +
3. Creamos la entidad Ejemplo, que tenga dos atributos(textArea/editorcke), para que veamos la diferencia.
 +
 
 +
4. Creamos el formulario (EjemploType), descripción de tipo CKEditorType y top de tipo TextareaType.
 +
 
 +
<source lang="php">
 +
use Symfony\Component\Form\AbstractType;
 +
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
 +
use Symfony\Component\Form\FormBuilderInterface;
 +
use Symfony\Component\OptionsResolver\OptionsResolver;
 +
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
 +
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
 +
 
 +
class EjemploType extends AbstractType
 +
{
 +
    /**
 +
    * @param FormBuilderInterface $builder
 +
    * @param array $options
 +
    */
 +
    public function buildForm(FormBuilderInterface $builder, array $options)
 +
    {
 +
        $builder
 +
            ->add('descripcion',CKEditorType::class)
 +
            ->add('top',TextareaType::class)
 +
            ->add('salvar',SubmitType::class)
 +
        ;
 +
    }
 +
   
 +
    /**
 +
    * @param OptionsResolver $resolver
 +
    */
 +
    public function configureOptions(OptionsResolver $resolver)
 +
    {
 +
        $resolver->setDefaults(array(
 +
            'data_class' => 'MiBundle\Entity\Ejemplo'
 +
        ));
 +
    }
 +
}
 +
</source>
 +
 
 +
5. Añadimos en el controlador.
 +
<source lang="php">
 +
/**
 +
    * @Route("/editor", name="nuevo_editpr")
 +
    */
 +
     public function editorAction(Request $request)
 
     {
 
     {
 
         $Ejemplo = new Ejemplo();
 
         $Ejemplo = new Ejemplo();
         $form = $this->createForm(EjemploType::class, $Ejemplo);
+
         $form = $this->createForm(EjemploType::class, $Ejemplo);
         $form->handleRequest($request);
+
         $form->handleRequest($request);
 
+
 
         if ($form->isSubmitted() && $form->isValid()) {
+
         if ($form->isSubmitted() && $form->isValid()) {
 
+
 
             $em = $this->getDoctrine()->getManager();
+
             $em = $this->getDoctrine()->getManager();
             //Con el método persist() le decimos que el objeto pasado por argumento sea guardado para ser insertado
+
             //Con el método persist() le decimos que el objeto pasado por argumento sea guardado para ser insertado
             // decirle a Doctrine que desea (eventualmente) guardar el Producto (no hay consultas aún)
+
             // decirle a Doctrine que desea (eventualmente) guardar el Producto (no hay consultas aún)
             $em->persist($Ejemplo);
+
             $em->persist($Ejemplo);
             // realmente ejecuta las consultas (es decir, la consulta INSERT)
+
             // realmente ejecuta las consultas (es decir, la consulta INSERT)
             //y la inserción en sí se realizará cuando ejecutemos el método flush(). Con esta orden Doctrine generará la sentencia INSERT necesaria
+
             //y la inserción en sí se realizará cuando ejecutemos el método flush(). Con esta orden Doctrine generará la sentencia INSERT necesaria
             $em->flush();
+
             $em->flush();
          
+
          
             die();
+
             die();
             // return $this->render('MiBundle:Default:listar.html.twig');
+
             // return $this->render('MiBundle:Default:listar.html.twig');
            
+
            
         }
+
         }
 +
 
 +
        return $this->render('MiBundle:Default:vista.html.twig', array('form' => $form->createView(), ));
 +
    }
 +
</source>
 +
 
 +
5. Vista
 +
<source lang="php">
 +
<body>
 +
 
 +
{{ form_start(form) }}
 +
{{ form_widget(form) }}
 +
{{ form_end(form) }}
 +
</body>
 +
</source>
 +
 
 +
==AJAX==
 +
 
 +
'''VISTA'''
 +
<source lang="php">
 +
 
 +
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
 +
....
 +
    setInterval(ajaxCall, 1000);
 +
 
 +
function ajaxCall(){
 +
$.ajax({
 +
                url:'{{ (path('listadotareas')) }}',
 +
                type: "POST",
 +
                dataType: "json",
 +
                data: {
 +
                    "valor": "vacio"
 +
                },
 +
                async: true,
 +
                success: function (data)
 +
                {
 +
                    console.log(data)
 +
                    $('div#ajax-results').html(data.output);
 +
 
 +
                }
 +
            });
 +
 
 +
            };
 +
</source>
 +
 
 +
'''ROUTING'''
 +
<source lang="php">
 +
 
 +
listadotareas:
 +
    path:    /listadotareas
 +
    defaults: { _controller: MiBundle:Default:listadotareas }
 +
 
 +
 
 +
</source>
 +
 
 +
'''CONTROLADOR'''
 +
<source lang="php">
 +
use Symfony\Component\HttpFoundation\Request;
 +
use Symfony\Component\HttpFoundation\JsonResponse
 +
.........
 +
public function listadotareasAction(Request $request)
 +
    {
 +
 
 +
 
 +
  if($request->request->get('valor')){
 +
     
 +
        $datos = ['info' => $request->request->get('valor')];
 +
        return new JsonResponse($datos);
 +
    }
 +
 
 +
    return $this->render('MiBundle:Default:inicio.html.twig');
 +
}
 +
</source>
  
        return $this->render('MiBundle:Default:vista.html.twig', array('form' => $form->createView(), ));
+
'''EJERCICIO'''
    }
 
</source>
 
  
5. Vista
+
Instalar uno de los mejores Bundle de Symfony, por ejemplo, '''EASYADMINBUNDLE'''
<source lang="php">
 
<body>
 
  
{{ form_start(form) }}
+
https://symfony.com/blog/the-30-most-useful-symfony-bundles-and-making-them-even-better
{{ form_widget(form) }}
 
{{ form_end(form) }}
 
</body>
 
</source>
 

Revisió de 15:49, 29 abr 2019

Symfony https://diego.com.es/certificacion-symfony

RESUMEN

instalar symfony ()
composer create-project symfony/framework-standard-edition daw/ "3.0.*"

instalar servidor propio
composer require server --dev

ejecutarlo
php bin/console server:run
php bin/console server:start
php bin/console server:stop
php bin/console server:stop

Crear controlador
php bin/generate:controller

crear bundle 
php bin/console generate:bundle --namespace=MiBundle --format=yml

crear formulario
php bin/console doctrine:generate:form PruebaBundle:Entidades

enlace apache
http://localhost/sym3/web/app_dev.php/pruebas/julio/fdsa

crear base datos
php bin/console doctrine:database:create  (hay que cambiar el nombre de dataBase_name de parameters.yml)

Generar Entidades
php bin/console doctrine:generate:entity    (Hay que poner EjerciciosBundle:Eventos)

Generar Entidades si modificamos algo de nuestro proyecto en entidades
php bin/console doctrine:generate:entities EjercicioBundle/Entity/Entidades

Borrar Base datos
php bin/console doctrine:database:drop --force

Crear Tablas
php bin/console doctrine:schema:create

Actualizar tablas
php bin/console doctrine:schema:update --force

$repository = $this->getDoctrine()->getRepository(EjecicioBundle::Eventos);

Generar Formularios
php bin/console doctrine:generate:form EjercicioBundle:Eventos

para ver Rutas (get,post...)
php bin/console debug:router

Crear CRUD (crear entidad, conexiones BBDD, tabla)
php bin/console generate:doctrine:crud

Serializar 
composer require symfony/finder

Generar Entidades desde una BD creada
php bin/console doctrine:mapping:import --force EjercicioBundle xml
php bin/console doctrine:mapping:convert annotation ./src
php bin/console doctrine:generate:entities EjercicioBundle

Limpiar Cache
php bin/console cache:clear --env=prod && php bin/console cache:warmup --env=prod

Doctrine

Una de les tasques més comunes i desafiadores per a qualsevol aplicació implica la persistència i la lectura d'informació cap a i des d'una base de dades. Encara que el framework Symfony no integra cap ORM per defecte, l'edició estàndard de Symfony, que és la distribució més utilitzada, ve integrada amb Doctrine, una biblioteca, l'únic objectiu de la qual és donar eines poderoses per fer-ho fàcil.

La llibreria Doctrine proporciona eines per simplificar l'accés i maneig de la informació de la base de dades.

La millor manera per explicar el framework doctrine és mitjançant exmples. Per aixó, es configura l'accés a la base de dades amb doctrine i s'exemplificarà amb la creació d'un objecte anomenat Product.

Configuracio de Doctrine i la Base de Dades

Ès necessari configurar la informació per accedir a la base de dades. Per convenció, aquesta informació es configura en l'arxiu app/config/parameters.yml:

# app/config/parameters.yml
parameters:
    database_driver:   pdo_mysql
    database_host:     localhost
    database_name:     test_project
    database_user:     root
    database_password: password

Ara que doctrine ja coneix l'usuari, la contrasenya i la Base de Dades a utilitzar, pots crear-la amb la següent comanda:

$ php bin/console doctrine:database:create

Creant una classe Entity

Imagina que estàs desenvolupant una aplicació en la qual vas a mostrar productes. Oblidant-te de Doctrine i de les bases de dades, segurament estàs pensant a utilitzar un objecte Product per representar als productes. Crea aquesta classe dins del directori Entity del bundle AcmeStoreBundle:

// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
 
class Product
{
    protected $name;
 
    protected $price;
 
    protected $description;
}

És una classe molt senzilla que només s'utilitza per emmagatzemar dades. Encara que es tracta d'una classe molt bàsica, compleix el seu objectiu de representar als productes de la teva aplicació. No obstant això, aquesta classe no es pot guardar en una base de dades — és només una classe PHP simple.

Podràs generar les classes de tipus entitat més fàcilment amb la següent comanda. Una vegada executat, Doctrine et farà diverses preguntes per generar l'entitat de forma interactiva:

$ php bin/console doctrine:generate:entity

Mapeig d'objectes PHP a tables de BD

En comptes de treballar amb files i taules, Doctrine et permet guardar i obtenir objectes sencers a partir de la informació de la base de dades. El truc perquè això funcioni consisteix en mapear una classe PHP a una taula de la base de dades i després, mapear les propietats de la classe PHP a les columnes d'aquesta taula:

Php-bd.png

Només has d'afegir algunes metadades a la classe PHP per configurar com es mapean la classe Product i les seves propietats. Aquestes metadades es poden configurar en arxius YAML, XML o directament mitjançant anotacions a la pròpia classe PHP:

// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Entity
 * @ORM\Table(name="product")
 */
class Product
{
    /**
     * @ORM\Id
     * @ORM\Column(type="integer")
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    protected $id;
 
    /**
     * @ORM\Column(type="string", length=100)
     */
    protected $name;
 
    /**
     * @ORM\Column(type="decimal", scale=2)
     */
    protected $price;
 
    /**
     * @ORM\Column(type="text")
     */
    protected $description;
}

El nom de la taula és opcional i si ho omets, es genera automàticament en funció del nom de la classe PHP.

Pots consultar la documentació oficial de Doctrine sobre el mapeig. Tingues en compte que en la documentació de Doctrine no s'explica que si utilitzes anotacions, has de prefixar-les totes amb la cadena ORM\ (per exemple, ORM\Column(...)). Igualment, no t'oblidis d'afegir la declaració use Doctrine\ORM\Mapping as ORM; al principi de les teves classes per importar el prefix ORM\.

Consulta la secció Quoting reserved words de la documentació de Doctrine per conèixer la llista completa de paraules reservades.

Generant getters i setters

Recordem la classe Product que havíem creat:

// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
 
class Product
{
    protected $name;
 
    protected $price;
 
    protected $description;
}

Doctrine ja sap com persistir els objectes de tipus Product en la base de dades, però aquesta classe no és molt útil de moment. Com Product és una classe PHP normal i corrent, és necessari crear mètodes getters i setters' (getName(), setName(), etc.) per poder accedir a les seves propietats (perquè són de tipus protected). Com això és bastant habitual, existeix un comando perquè Doctrine anyada aquests mètodes automàticament:

$ php bin/console doctrine:generate:entities Acme/StoreBundle/Entity/Product

Executa aquestes comandes quan estiguis desenvolupant l'aplicació. En el servidor de producció has d'utilitzar les migracions que proporciona el bundle DoctrineMigrationsBundle.

Creant les taules de la base de dades (el esquema)

Encara que tens una classe Product utilitzable amb informació de mapatge perquè Doctrine sàpiga persistir-la, encara no tens la seva corresponent taula product en la base de dades. Afortunadament, Doctrine pot crear automàticament totes les taules necessàries en la base de dades (una per a cada entitat coneguda de la teva aplicació). Per a això, executa la següent comanda:

$ php bin/console doctrine:schema:update --force

Internament compara l'estructura que hauria de tenir la teva base de dades (segons la informació de mapatge de les teves entitats) amb l'estructura que realment té i genera les sentències SQL necessàries per actualitzar l'estructura de la base de dades.

En altres paraules, si afegeixes una nova propietat a la classe Product i executes aquest comando una altra vegada, es genera una sentència de tipus ALTER TABLE per afegir la nova columna a la taula product existent.

Buscant objectes a la base de dades

Usando DOCTRINE vamos a recuperar los datos de la base de datos en función de la entidad.

Métodos Mágicos Para obtener datos de las tablas tenemos varios métodos:


findAll(): Obtiene todos los registros de la tabla. Retorna un array.

find(): Obtiene un registro a partir de la clave primaria de la tabla.

findBy(): Obtiene los registros encontrados pudiendo pasar como argumentos los valores que irían dentro del WHERE. Retorna un array.

findOneBy() : obtiene un registro pudiendo pasar como argumentos los valores que irían dentro del WHERE.


// consulta por la clave principal (generalmente 'id')
$product = $repository->find($id);
 
// métodos con nombres dinámicos para buscar un valor en función de alguna columna
$product = $repository->findOneById($id);
$product = $repository->findOneByName('foo');
 
// obtiene todos los productos
$products = $repository->findAll();
 
// busca productos basándose en el valor de una columna
$products = $repository->findByPrice(19.99);

També pots utilitzar els mètodes findBy i findOneBy per obtenir objectes en funció de vàries condicions:

// busca un producto con ese nombre y ese precio
$product = $repository->findOneBy(array(
    'name'  => 'foo', 'price' => 19.99
));
 
// obtiene todos los productos con un nombre determinado
// y ordena los resultados por precio
$product = $repository->findBy(
    array('name'  => 'foo'),
    array('price' => 'ASC')
);


FINDALL

Routing

formu_listar:
    path:     /listar
    defaults: { _controller: CosasBundle:Default:listar }

Controlador

public function listarAction()
    {
        $repositorio = $this->getDoctrine()->getRepository('CosasBundle:entidad');
        $datos=$repositorio->findAll();
        //print_r($datos);
        return $this->render('CosasBundle:Default:formulario.html.twig',array("dades"=>$datos));
    }

Vista

<ul>
    {% for dato in dades %}
        <li>{{ dato.nombre }}</li>
        <li>{{ dato.edad }}</li>
    {% endfor %}
</ul>


FINDBYONE.... - FIND


ROUTING

formu_findbyone:
    path:     /buscarPorNombre/{nombre}
    defaults: { _controller: CosasBundle:Default:buscarPorNombre, nombre: 'julio' }

CONTROLADOR

public function buscarPorNombreAction($nombre)
    {
        //recupera el repositorio de la entidad Entidad
        $repositorio = $this->getDoctrine()->getRepository('CosasBundle:entidad');
        $resultados=$repositorio->findOneByNombre($nombre);   //busca la primera fila que empiece por Nombre, si fuera findOneByEdad buscaría por edad
        //$resultados=$repositorio->find(5);     //busca por id
        return $this->render('CosasBundle:Default:formulario.html.twig',array("resul"=>$resultados));
    }

VISTA

<li>{{ resul.nombre }}{{ resul.edad }} </li>


INSERTAR DENTRO DE LA BASE DATOS


Routing

formu_insertar:
    path:     /insertar
    defaults: { _controller: CosasBundle:Default:insertar }

Controlador

public function insertarAction()
    {
        //nuevo objeto tipo entidad
        $enti = new entidad();
        $enti->setNombre("JULIO");
        $enti->setEdad("30");

        //Doctrine
        $em = $this->getDoctrine()->getManager();
        //Con el método persist() le decimos que el objeto pasado por argumento sea guardado para ser insertado 
        // decirle a Doctrine que desea (eventualmente) guardar el Producto (no hay consultas aún)
        $em->persist($enti);
        // realmente ejecuta las consultas (es decir, la consulta INSERT) 
        //y la inserción en sí se realizará cuando ejecutemos el método flush(). Con esta orden Doctrine generará la sentencia INSERT necesaria
        $em->flush();
        return $this->render('CosasBundle:Default:formulario.html.twig',array("idEntidad"=>$enti->getId()));
    }

Vista

El identificador: {{ idEntidad }}


ACTUALIZAR nuevo datos en la base de datos (fila)


ROUTING

formu_actualizar:
    path:     /actualizar/{id}
    defaults: { _controller: CosasBundle:Default:actualizar, id: '1' }  #le pasas un valor por defecto en caso de no ponerlo en la url

CONTROLADOR

public function actualizarAction($id)
    {

        $em = $this->getDoctrine()->getManager();
        $dato = $em->getRepository('CosasBundle:entidad')->find($id);

        if (!$dato) {
            throw $this->createNotFoundException('No product found for id '.$id );
        }
        $dato->setNombre('Otro Nombre');
        $em->flush();
        return $this->redirectToRoute('formu_listar');   //tenemos que poner el path que está en el routing.yml en este caso listar

    }



ELIMINAR datos en la base de datos (fila)


ROUTING

formu_actualizar:
    path:     /eliminar/{id}
    defaults: { _controller: CosasBundle:Default:eliminar }

CONTROLADOR

public function eliminarAction($id)
    {

        $em = $this->getDoctrine()->getManager();
        $dato = $em->getRepository('CosasBundle:entidad')->find($id);

        if (!$dato) {
            throw $this->createNotFoundException('No product found for id '.$id );
        }
        $em->remove($dato);
        $em->flush();
        return $this->redirectToRoute('formu_listar');   //tenemos que poner el path que está en el routing.yml en este caso listar

    }



Exercici

Actualitza l'exercici CRUD realitzat a la UF anterior per a que utilitzi BD amb Doctrine.

solució:


Buscant Objectes amb el generador de consultes de Doctrine

Imagina que vols buscar tots aquells productes el preu dels quals sigui superior a 19.99 i retornar els resultats ordenats del més barat al més car. Aquesta cerca es pot realitzar de la següent manera amb el QueryBuilder de Doctrine:

$repository = $this->getDoctrine()->getRepository('MiBundle:Product');
 
$query = $repository->createQueryBuilder('p')
    ->where('p.price > :price')
    ->setParameter('price', '19.99')
    ->orderBy('p.price', 'ASC')
    ->getQuery();  //retorna l'objecte de tipus Query amb el qual realment s'executa la consulta.
 
$products = $query->getResult(); //devuelve array de resultados
print_r($products);

L'objecte QueryBuilder conté tots els mètodes necessaris per construir la consulta. En cridar al mètode getQuery(), el query builder retorna l'objecte de tipus Query amb el qual realment s'executa la consulta. El mètode getResult() retorna un array de resultats. Per obtenir solament un resultat, utilitza getSingleResult() (que llança una excepció quan no hi ha cap resultat) o getOneOrNullResult():

$product = $query->getOneOrNullResult();

Consulta la documentació de QueryBuilder per obtenir més informació.

Buscant objetes amb DQL

A més del QueryBuilder, Doctrine també et permet realitzar consultes directament amb el seu llenguatge DQL:

$em = $this->getDoctrine()->getManager();
$query = $em->createQuery(
    'SELECT p
       FROM MiBundle:Product p
      WHERE p.price > :price
   ORDER BY p.price ASC'
)->setParameter('price', '2');
 
$products = $query->getResult();

print_r($products);

La sintaxi DQL és increïblement poderosa, permetent-te unir fàcilment diferents entitats (el tema de les relacions s'explica més endavant), realitzar agrupacions, etc. Per a més informació, consulta la documentació oficial de Doctrine Query Language.

Altra forma RECOMANADA

$sql = "SELECT * FROM Product";  // "SELECT * FROM Product WHERE name='zapato'"; 
        $em = $this->getDoctrine()->getManager();
        $stmt = $em->getConnection()->prepare($sql);
        $stmt->execute();
        $todo=$stmt->fetchAll();
        print_r($todo);

MANY to MANY DOCTRINE relacionar tablas https://symfony.com/doc/current/doctrine/associations.html

RELACIÓN MANY to MANY

http://wikiserver.infomerce.es/images/e/ed/Manytomany.png

1. Creamos las dos entidades. (usando annotation) php bin/console doctrine:generate:entity

2. Generamos las dos Entidades

ENTRENADOR

/**
 * Entrenador
 *
 * @ORM\Table(name="entrenador")
 * @ORM\Entity(repositoryClass="FutbolBundle\Repository\EntrenadorRepository")
 */
class Entrenador
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="nombre", type="string", length=255)
     */
    private $nombre;

    /**
     * @var string
     *
     * @ORM\Column(name="apellidos", type="string", length=255)
     */
    private $apellidos;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set nombre
     *
     * @param string $nombre
     *
     * @return Entrenador
     */
    public function setNombre($nombre)
    {
        $this->nombre = $nombre;

        return $this;
    }

    /**
     * Get nombre
     *
     * @return string
     */
    public function getNombre()
    {
        return $this->nombre;
    }

    /**
     * Set apellidos
     *
     * @param string $apellidos
     *
     * @return Entrenador
     */
    public function setApellidos($apellidos)
    {
        $this->apellidos = $apellidos;

        return $this;
    }

    /**
     * Get apellidos
     *
     * @return string
     */
    public function getApellidos()
    {
        return $this->apellidos;
    }
}


EQUIPO

/**
 * Equipo
 *
 * @ORM\Table(name="equipo")
 * @ORM\Entity(repositoryClass="FutbolBundle\Repository\EquipoRepository")
 */
class Equipo
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="nombre", type="string", length=255)
     */
    private $nombre;

    /**
     * @var string
     *
     * @ORM\Column(name="tipo", type="string", length=255)
     */
    private $tipo;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set nombre
     *
     * @param string $nombre
     *
     * @return Equipo
     */
    public function setNombre($nombre)
    {
        $this->nombre = $nombre;

        return $this;
    }

    /**
     * Get nombre
     *
     * @return string
     */
    public function getNombre()
    {
        return $this->nombre;
    }

    /**
     * Set tipo
     *
     * @param string $tipo
     *
     * @return Equipo
     */
    public function setTipo($tipo)
    {
        $this->tipo = $tipo;

        return $this;
    }

    /**
     * Get tipo
     *
     * @return string
     */
    public function getTipo()
    {
        return $this->tipo;
    }
}

Relacions i associacions de entitats (ONE to MANY)

Suposa que els productes de l'aplicació pertanyen a una (i només a una) categoria. En aquest cas, necessitaràs un objecte de tipus Category i una manera de relacionar un objecte Product a un objecte Category. Per crear entitats, pots fer-lo com s'ha vist en els apartats anteriors o pots utilitzar la següent comanda:

$ php app/console doctrine:generate:entity
      --entity="AcmeStoreBundle:Category"
      --fields="name:string(255)"

La comanda anterior genera la entitad Category amb un id, amb un camp name i els getters i setters corresponents.

Mapeando relaciones

Per relacionar les entitats Category i Product, has de crear en primer lloc una propietat anomenada producte a la classe Category:

// src/Acme/StoreBundle/Entity/Category.php
 
// ...
use Doctrine\Common\Collections\ArrayCollection;
 
class Category
{
    // ...
 
    /**
     * @ORM\OneToMany(targetEntity="Product", mappedBy="category")
     */
    protected $products;
 
    public function __construct()
    {
        $this->products = new ArrayCollection();
    }
}

El codi del mètode __construct() és important perquè Doctrine requereix que la propietat $products sigui un objecte de tipus ArrayCollection. Aquest objecte es comporta gairebé exactament com un array, però afegeix certa flexibilitat. Si utilitzar aquest objecte et sembla rar, imagina que és un array normal i ja està.

A continuació, com cada classe Product es pot relacionar exactament amb un objecte Category(i només un), pots afegir una propietat $category a la classe Product:

// src/Acme/StoreBundle/Entity/Product.php
 
// ...
class Product
{
    // ...
 
    /**
     * @ORM\ManyToOne(targetEntity="Category", inversedBy="products")
     * @ORM\JoinColumn(name="category_id", referencedColumnName="id")
     */
    protected $category;
}

Les metadades de la propietat $category a la classe Product li diu a Doctrine que la classe relacionada és Category i que ha de guardar l'id de la categoria associada en un camp anomenat category_id de la taula product. En altres paraules, l'objecte Category relacionat s'emmagatzema en la propietat $category', però internament Doctrine persisteix aquesta relació emmagatzemant el valor de l'id la categoria en la columna category_id de la taula product.

Category.png

Utilitzar

php bin/console doctrine:generate:entities App

per a generar tots el get, set i add/remove del array

Després fem una actualització a la base de dades i podrem vore les relacions

Emmagatzemant les entitats relacionades

El següent codi mostra un exemple de com usar les entitats relacionades dins d'un controlador de Symfony:

// ...
 
use Acme\StoreBundle\Entity\Category;
use Acme\StoreBundle\Entity\Product;
use Symfony\Component\HttpFoundation\Response;
 
class DefaultController extends Controller
{
    public function createProductAction()
    {
        $category = new Category();
        $category->setName('Main Products');
 
        $product = new Product();
        $product->setName('Foo');
        $product->setPrice(19.99);
        // relaciona este producto con una categoría
        $product->setCategory($category);
 
        $em = $this->getDoctrine()->getManager();
        $em->persist($category);
        $em->persist($product);
        $em->flush();
 
        return new Response(
            'Created product id: '.$product->getId()
            .' and category id: '.$category->getId()
        );
    }
}

Després d'executar aquest codi, s'afegeix una fila en les taules category i product. La columna product.category_id per al nou producte s'estableix al valor de l'id de la nova categoria. Doctrine s'encarrega de gestionar aquestes relacions automàticament.

Obtenint els objectes relacionats

Quan vulguis obtenir els objectes associats, la forma de treballar és molt similar. Primer busques un objecte $product i després accedeixes al seu objecte Category associat:

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Product')
        ->find($id);
 
    $categoryName = $product->getCategory()->getName();
 
    // ...
}

En aquest exemple, primer busques un objecte Product en funció del valor del seu id. Això fa que s'executi una sola sentència SQL per obtenir les dades de l'objecte $product. Després, quan es realitza la trucada $product->getCategory()->getName(), Doctrine realitza automàticament una altra consulta SQL per obtenir les dades de l'objecte Category relacionat amb aquest Product.

Relacions1.png

La clau és que pots accedir fàcilment a les dades de la categoria relacionada amb el producte, però no tens les seves dades fins que realment els necessitis (això és el que es diu lazy loading o càrrega diferida d'informació).

També pots realitzar cerques en el sentit contrari de la relació:

public function showProductAction($id)
{
    $category = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Category')
        ->find($id);
 
    $products = $category->getProducts();
 
    // ...
}

En aquest cas, ocorre el mateix: primer busques un únic objecte Category, i després Doctrine fa una segona consulta per recuperar els objectes Product relacionats, però només si tractes d'accedir a la seva informació (és a dir, només quan invoquis a ->getProducts()). La variable $products és un array de tots els objectes Product relacionats amb l'objecte Category indicat (i relacionat a través del valor category_id dels productes).

Uniendo registros relacionados

//Le pasas la categoria (idcategoria) y te devuelve los productos que están relacionados con su Clave Ajena (mediante id_categoria)

public function obtenerInnerJoinCategoryProduct($id){

       $sql='SELECT * FROM category INNER JOIN product ON category.id = product.category_id WHERE product.category_id='.$id; 

        $em = $this->getDoctrine()->getManager();
        $stmt = $em->getConnection()->prepare($sql);
        $stmt->execute();
        $todo=$stmt->fetchAll();
     try {
        return $todo;
     } catch (\Doctrine\ORM\NoResultException $e) {
        return null;
    }

}

En els exemples anteriors, es realitzen dues consultes: la primera per a l'objecte original (Category per exemple) i la segona per el/els objectes relacionats (un array de Product per exemple).

Si saps per endavant que vas a necessitar les dades de tots els objectes, pots estalviar-te una consulta fent una unió o "join" en la primera consulta. Es pot realitzar utilitzant repositoris propis i afegint els mètodes que necessitem.

// src/Acme/StoreBundle/Entity/ProductRepository.php
public function findOneByIdJoinedToCategory($id)
{
    $query = $this->getEntityManager()
        ->createQuery(
            'SELECT p, c FROM AcmeStoreBundle:Product p
            JOIN p.category c
            WHERE p.id = :id'
        )->setParameter('id', $id);
 
    try {
        return $query->getSingleResult();
    } catch (\Doctrine\ORM\NoResultException $e) {
        return null;
    }
}

Pots utilitzar aquest mètode en el controlador per obtenir un objecte Product i el seu corresponent Category amb una sola consulta:

public function showAction($id)
{
    $product = $this->getDoctrine()
        ->getRepository('AcmeStoreBundle:Product')
        ->findOneByIdJoinedToCategory($id);
 
    $category = $product->getCategory();
 
    // ...
}

Repositori de classes personalitzat

Per desacoblar el codi, per poder crear tests fàcilment i per reutilitzar les consultes, és millor crear una classe pròpia de tipus repositori i incloure en ella tots els mètodes que necessitis per realitzar les consultes.

Per a això, afegeix en la informació de mapatge de l'entitat la ruta de la nova classe del teu repositori:

// src/Acme/StoreBundle/Entity/Product.php
namespace Acme\StoreBundle\Entity;
 
use Doctrine\ORM\Mapping as ORM;
 
/**
 * @ORM\Entity(repositoryClass="Acme\StoreBundle\Entity\ProductRepository")
 */
class Product
{
    //...
}

Doctrine pot generar la classe de repositori buida executant el mateix comando que vas utilitzar anteriorment per generar els getters i els setters:

$ php app/console doctrine:generate:entities Acme

Ara pots afegir un nou mètode anomenat findAllOrderedByName() a la classe del repositori recentment generat. Aquest mètode busca totes les entitats de tipus Product ordenades alfabèticament.

// src/Acme/StoreBundle/Entity/ProductRepository.php
namespace Acme\StoreBundle\Entity;
 
use Doctrine\ORM\EntityRepository;
 
class ProductRepository extends EntityRepository
{
    public function findAllOrderedByName()
    {
        return $this->getEntityManager()
            ->createQuery(
                'SELECT p FROM AcmeStoreBundle:Product p ORDER BY p.name ASC'
            )
            ->getResult();
    }
}

Ja pots utilitzar aquest nou mètode per realitzar la consulta dins d'un controlador de Symfony:

$em = $this->getDoctrine()->getManager();
$products = $em->getRepository('AcmeStoreBundle:Product')
               ->findAllOrderedByName();

Encara que utilitzis una classe repositori pròpia, encara pots fer ús dels mètodes de cerca predeterminats com find() i findAll().

Altres tipus de relacions

Per saber com es configuren i s'utilitzen altres tipus de relacions (ONE to ONE, MANY TO MANY) pots veure la documentació oficial de doctrine: Mapeig d'associacions.

Callbacks

En ocasions, és necessari realitzar una acció just abans o després d'inserir, actualitzar o eliminar una entitat. Aquest tipus d'accions es coneixen com lifecycle callbacks, ja que són mètodes (o callbacks) que s'executen en alguna de les diferents etapes per les quals passa una entitat (lifecycle)

/**
 * @ORM\Entity()
 * @ORM\HasLifecycleCallbacks()
 */
class Product
{
    // ...
}

Ara ja pots dir a Doctrine que executi un mètode en qualsevol dels esdeveniments disponibles durant el cicle de vida de l'entitat.

/**
 * @ORM\PrePersist
 */
public function setCreatedValue()
{
    $this->created = new \DateTime();
}

Ara, just abans de persistir l'entitat per primera vegada, Doctrine executa automàticament aquest mètode i establirà el camp created a la data actual.Consulta la documentació sobre esdeveniments de Doctrine per obtenir molta més informació.


Generar Entitats des de una BD creada

El primer pas per construir classes d'entitat a partir d'una base de dades és permeten que Doctrine introspecti la base de dades i generiu les corresponents metadades. Les metadades son les diferentes informacions que s'afegeixen als arxius que associen les propietats d'una classe amb el corresponent camp de la taula.

$ php bin/console doctrine:mapping:import --force EjercicioBundle xml

Aquesta comanda permet a Doctrine fer una introspecció de la base de dades i generar les metadades en format XML. Una vegada es generen les metadades, pots demanar a Doctrine que crei les classes (Entity) associades executant les següents comandes:

$ php bin/console doctrine:mapping:convert annotation ./src
$ php bin/console doctrine:generate:entities EjercicioBundle

Pràctica Doctrine

Es vol crear un portal per veure vídeos. Necessitem:

  • Afegir vídeo (nom, Arxiu, Descripció)
  • Veure tots els Vídeos: Mostra un llistat de vídeos. Al clicar en un vídeo es pot:
    • Veure el vídeo en un reproductor HTML5
    • Valorar el vídeo (1-5)
    • Afegir un comentari anònim
  • Eliminar un vídeo. També elimines els comentaris i les seves valoracions.
  • Top Vídeos: Llista ordenada amb els vídeos millors valorats.(Pàgina on es veuen directament els vídeos on el 1er vídeo és el millor valorat.)

Webgrafia

S'ha utilitzat el llibre oficial que podeu trobar a la web de doctrine:Doctrine

Altres llibres sobre doctrine utilitzats els podeu trobar a : http://librosweb.es/libros/

https://symfony.com/doc/3.0/doctrine/registration_form.html

Usuarios

1. creamos bundle (annotation)

2. creamos una base datos(si no está creada)

php bin/console doctrine:database:create

3. creamos una nueva entidad "User" (annotation)

php bin/console doctrine:generate:entity

Quedaría de la siguiente forma

<?php

namespace UsuariosBundle\Entity;

use Doctrine\ORM\Mapping as ORM;

/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="UsuariosBundle\Repository\UserRepository")
 */
class User
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="username", type="string", length=255)
     */
    private $username;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="password", type="string", length=64)
     */
    private $password;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set username
     *
     * @param string $username
     *
     * @return User
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * Get username
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Set email
     *
     * @param string $email
     *
     * @return User
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set password
     *
     * @param string $password
     *
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Get password
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }
}


4. Añadimos los atributos métodos que nos faltan y que ponen en la documentación(y alguno más) en la entidad

Y actualizamos nuestra base de datos

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

<?php

namespace UsuariosBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\Security\Core\User\UserInterface;

/**
 * User
 *
 * @ORM\Table(name="user")
 * @ORM\Entity(repositoryClass="UsuariosBundle\Repository\UserRepository")
 */
class User implements UserInterface
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="username", type="string", length=255)
     */
    private $username;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="password", type="string", length=64)
     */
    private $password;


    /**
     * @Assert\NotBlank()
     * @Assert\Length(max=4096)
     */
    private $plainPassword;


    /**
     * Get id
     *
     * @return int
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set username
     *
     * @param string $username
     *
     * @return User
     */
    public function setUsername($username)
    {
        $this->username = $username;

        return $this;
    }

    /**
     * Get username
     *
     * @return string
     */
    public function getUsername()
    {
        return $this->username;
    }

    /**
     * Set email
     *
     * @param string $email
     *
     * @return User
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

    /**
     * Get email
     *
     * @return string
     */
    public function getEmail()
    {
        return $this->email;
    }

    /**
     * Set password
     *
     * @param string $password
     *
     * @return User
     */
    public function setPassword($password)
    {
        $this->password = $password;

        return $this;
    }

    /**
     * Get password
     *
     * @return string
     */
    public function getPassword()
    {
        return $this->password;
    }


    public function getPlainPassword()
    {
        return $this->plainPassword;
    }

    public function setPlainPassword($password)
    {
        $this->plainPassword = $password;
    }


    public function getSalt()
    {
        // The bcrypt algorithm doesn't require a separate salt.
        // You *may* need a real salt if you choose a different encoder.
        return null;
    }

    public function getRoles()
    {
        return array('ROLE_USER');
    }

    public function eraseCredentials()
    {
        // TODO: Implement eraseCredentials() method.
    }
}

5.creamos Formulario Registro

php bin/console doctrine:generate:form UsuariosBundle:User

<?php

namespace UsuariosBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\EmailType;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;

class UserType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('username')
            ->add('email',EmailType::class)
            ->add('plainPassword', RepeatedType::class, array(
                'type' => PasswordType::class,
                'first_options'  => array('label' => 'Password'),
                'second_options' => array('label' => 'Repeat Password'),
            ))
            ->add('salvar',SubmitType::class)
            ->add('borrar',ResetType::class)
        ;
    }
    
    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'UsuariosBundle\Entity\User'
        ));
    }
}

6. Ahora tenemos que recuperar en nuestro controlador el formulario de registro.

use Symfony\Component\HttpFoundation\Request;

......

public function registerAction(Request $request)
    {
        // 1) build the form
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        // 2) handle the submit (will only happen on POST)
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            // 3) Encode the password (you could also do this via Doctrine listener)
            $password = $this->get('security.password_encoder')->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);

            // 4) save the User!
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            // ... do any other work - like sending them an email, etc
            // maybe set a "flash" success message for the user

            //return $this->redirectToRoute('replace_with_some_route');
            return new Response("Usuario Registrado");
        }

        return $this->render('UsuariosBundle:Default:register.html.twig', array('form' => $form->createView())
        );
    }

7. Añadimos la VISTA

{{ form_start(form) }}
    {{ form_row(form.username) }}
    {{ form_row(form.email) }}
    {{ form_row(form.plainPassword.first) }}
    {{ form_row(form.plainPassword.second) }}
    {{ form_row(form.salvar) }}
    {{ form_row(form.borrar) }}
{{ form_end(form) }}

8. Falta poner la encriptacion en app/config/security.yml

security:
    encoders:
       UsuariosBundle\Entity\User: bcrypt

FIN DE FORMULARIO DE REGISTRO



INICIO DE LOGIN USUARIOS


-> En la versión 2 se recomendaba crear diferentes bundles y redirigir si es un usuario admin a AdminBundle y si es un usuario normal a otro Bundle.


Ahora vamos a crear otro Bundle admin, para separar el login y el acceso entre usuarios y administrador.

    php bin/console generate:bundle --namespace=Admin --format=yml


Una vez instalado el Bundle, tenemos que irnos a app->config->routing para separar las diferentes rutas de nuestros bundles

admin:
    resource: "@AdminBundle/Controller/"
    type:     annotation
    prefix:   /admin

usuarios:
    resource: "@UsuariosBundle/Controller/"
    type:     annotation
    prefix:   /

app:
    resource: "@AppBundle/Controller/"
    type:     annotation

Para realizar el logout, hay que añadir en el routing principal, además de lo añadido en security

admin:
    resource: "@AdminBundle/Controller/"
    type:     annotation
    prefix:   /admin

usuarios:
    resource: "@UsuariosBundle/Controller/"
    type:     annotation
    prefix:   /

app:
    resource: "@AppBundle/Controller/"
    type:     annotation

logout:
    path: /usuarios/logout


-> En la versión 3 se recomienda como buenas prácticas en caso de tener un usuario admin que vaya a un controlador y los usuarios normales a otro o al Default.

Crear un nuevo Controller enlace

php bin/console generate:controller --route-format=annotation


Como ejemplo cómo redirigir a otro Controlador.

mi_homepage:
    path:     /
    defaults: { _controller: MiBundle:Default:index }

mi_Admin:
    path:     /admin
    defaults: { _controller: MiBundle:Admin:index }



LOGIN BÁSICO USUARIOS EN MEMORIA

Modificamos el FIREWALL

https://symfony.com/doc/3.2/security.html

https://symfony.com/doc/3.2/reference/configuration/security.html

security:
    encoders:
       UsuariosBundle\Entity\User: bcrypt   #seguridad encriptación para el usuarios
       Symfony\Component\Security\Core\User\User: plaintext   #seguridad encriptación para el admin
  
    providers:
            in_memory:
                memory:   #usuarios en memoria
                    users:
                        admin:
                            password: 1234
                            roles: 'ROLE_ADMIN'

    firewalls:
        
        dev:  #deshabilitamos la seguridad para el modo desarrollador
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        admin:
           # anonymous: ~    #deshabilitamos para que no pueda accedir ninguno y nos aparece el login básico
            pattern: ^/admin  
            http_basic: ~

        main:
            anonymous: ~

LOGIN BÁSICO USUARIOS EN BBDD

$2y$13$D3esHHJkhPQnFsKzAlDW/.RExL5/GtWbWJmzUUsRmUXrxfiFiLhte

Logearnos con http_basic(login cutre ), nos vamos a security y añadimos todo el contenido de a continuación con eso podremos entrar en la seccion admin si ya estas en la base de datos.

https://symfony.com/doc/3.0/security/entity_provider.html#configure-security-to-load-from-your-entity

security:
    encoders:
       UsuariosBundle\Entity\User: bcrypt

    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    providers:
        nuestros_usuarios:
            entity:
                class: UsuariosBundle:User
                property: username

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false
        admin:
            pattern: ^/admin
            http_basic: ~
            provider: nuestros_usuarios

        main:
            anonymous: ~

LOGIN CON FORMULARIO.

https://symfony.com/doc/3.0/security/form_login_setup.html

Debemos modificar los parámetros de nuestro security, como queremos tener una ruta de admin y otra usuarios, en el cual alojaremos un login que podrá acceder cualquier persona y para el resto de contenido solo las personas autorizadas.

security:
    encoders:
       MiBundle\Entity\User: bcrypt
      #cost: 12

    # http://symfony.com/doc/current/book/security.html#where-do-users-come-from-user-providers
    providers:
        our_db_provider:
            entity:
                class: MiBundle:User
                property: username

    firewalls:
        # disables authentication for assets and the profiler, adapt it according to your needs
        dev:
            pattern: ^/(_(profiler|wdt)|css|images|js)/
            security: false

        admin:
            pattern: ^/admin
            http_basic: ~
            provider: our_db_provider
        usuarios:
            pattern: ^/usuarios   
            anonymous: ~
            form_login:
                  login_path: /usuarios/login
                  check_path: /usuarios/login
            logout:
                  path: /usuarios/logout
                  target: /usuarios

    access_control:
         - { path: ^/usuarios/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
         - { path: ^/usuarios, roles: ROLE_USER }

Añadimos un controlador de LOGIN y modificamos todos los controladores para que empiecen en /usuarios

/**
     * @Route("/usuarios")
     */
    public function indexAction()
    {
        return $this->render('UsuariosBundle:Default:index.html.twig');
    }


    /**
     * @Route("/usuarios/login", name="login")
     */
    public function loginAction(Request $request)
    {

        $authenticationUtils = $this->get('security.authentication_utils');

        // get the login error if there is one
        $error = $authenticationUtils->getLastAuthenticationError();

        // last username entered by the user
        $lastUsername = $authenticationUtils->getLastUsername();

        return $this->render(
            'UsuariosBundle:Default:login.html.twig',
            array(
                // last username entered by the user
                'last_username' => $lastUsername,
                'error'         => $error,
            )
        );
    }



    /**
     * @Route("/usuarios/register", name="user_registration")
     */
    public function registerAction(Request $request)
    {
        // 1) build the form
        $user = new User();
        $form = $this->createForm(UserType::class, $user);

        // 2) handle the submit (will only happen on POST)
        $form->handleRequest($request);
        if ($form->isSubmitted() && $form->isValid()) {

            // 3) Encode the password (you could also do this via Doctrine listener)
            $password = $this->get('security.password_encoder')
                ->encodePassword($user, $user->getPlainPassword());
            $user->setPassword($password);

            // 4) save the User!
            $em = $this->getDoctrine()->getManager();
            $em->persist($user);
            $em->flush();

            // ... do any other work - like sending them an email, etc
            // maybe set a "flash" success message for the user

            //return $this->redirectToRoute('replace_with_some_route');
            return new Response("Usuario Registrado");
        }

        return $this->render(
            'UsuariosBundle:Default:register.html.twig',
            array('form' => $form->createView())
        );
    }

Y la vista que irá nuestro login

{# app/Resources/views/security/login.html.twig #}
{# ... you will probably extends your base template, like base.html.twig #}
<body>
{% if error %}
    <div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}

<form action="{{ path('login') }}" method="post">
    <label for="username">Username:</label>
    <input type="text" id="username" name="_username" value="{{ last_username }}" />

    <label for="password">Password:</label>
    <input type="password" id="password" name="_password" />

    {#
        If you want to control the URL the user
        is redirected to on success (more details below)
        <input type="hidden" name="_target_path" value="/account" />
    #}

    <button type="submit">login</button>
</form>
</body>

Para realizar el logout, hay que añadir en el routing principal, además de lo añadido en security

mi:
    resource: "@MiBundle/Resources/config/routing.yml"
    prefix:   /

app:
    resource: "@AppBundle/Controller/"
    type:     annotation

logout:
    path: /usuarios/logout

y en la vista

<body>Hello World User!

<a href="{{path("logout")}}">salir</a>
</body>

si queremos ver una serie de parámetros de nuestros usuarios

//desde la vista
{% if is_granted('ROLE_USER')  %}{% endif %}
{% if is_granted('ROLE_ADMIN') %} <a href="...">Borrar</a>{% endif %}

{{ dump(app.user) }}


{% if app.user.username=='admin' %}{% endif %}


// desde el controlador
if($this->getUser()){}

if($this->getUser()->getUsername()=="conserjeria") {}


//Denegar el acceso desde el controlador
   $this->denyAccessUnlessGranted('ROLE_ADMIN', null, 'Unable to access this page!');

Crear Usuarios Sin Login desde el controlador

https://stackoverflow.com/questions/5886713/automatic-post-registration-user-authentication/19578674#19578674

$user = new User();
$user->setUserName("julio");
$user->setEmail("julio@julio.com");
$user->setPassword("1234");

        $token = new UsernamePasswordToken($user, null, 'main', $user->getRoles());
        $this->get('security.token_storage')->setToken($token);
        $this->get('session')->set('_security_main', serialize($token));


SUBIR FICHEROS

https://symfony.com/doc/current/controller/upload_file.html


Entity

namespace MiBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Symfony\Component\Validator\Constraints as Assert;

class Product
{
    // ...

    /**
     * @ORM\Column(type="string")
     *
     * @Assert\NotBlank(message="Please, upload the product brochure as a PDF file.")
     * @Assert\File(mimeTypes={ "application/zip" })
     */
    private $brochure;

    public function getBrochure()
    {
        return $this->brochure;
    }

    public function setBrochure($brochure)
    {
        $this->brochure = $brochure;

        return $this;
    }
}

ProductType

// src/Form/ProductType.php
namespace MiBundle\Form;

use MiBundle\Entity\Product;
use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\FileType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\Form\Extension\Core\Type\ResetType;

class ProductType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            // ...
            ->add('brochure', FileType::class, array('label' => 'Brochure (PDF file)'))
            ->add('salvar',SubmitType::class)
            // ...
        ;
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => Product::class,
        ));
    }
}


CONTROLADOR

/**
     * @Route("/new", name="app_product_new")
     */
    public function newAction(Request $request)
    {
        $product = new Product();
        $form = $this->createForm(ProductType::class, $product);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {
            // $file stores the uploaded PDF file
            /** @var Symfony\Component\HttpFoundation\File\UploadedFile $file */
            $file = $product->getBrochure();

            $fileName = $this->generateUniqueFileName().'.'.$file->guessExtension();  //genera un nombre único para la extensión guardada de antes.

            // mueve el fichero al directorio donde las "brochures" son almacenadas.
            $file->move(
                $this->getParameter('brochures_directory'),
                $fileName
            );

            // actualiza la'brochure' propiedad para almacenar el fichero PDF
            // instead of its contents
                $product->setBrochure($fileName);

            // ... persist the $product variable or any other work
           
            return $this->redirect($this->generateUrl('app_product_list'));  //redirige al sitio indicado.
        }

        return $this->render('MiBundle:Default:vista.html.twig', array(
            'form' => $form->createView(),
        ));
    }

 /**
     * @return string
     */
    private function generateUniqueFileName()
    {
        // md5() reduces the similarity of the file names generated by
        // uniqid(), which is based on timestamps
        return md5(uniqid());
    }



VISTA

<body>
<h1>Añadir nuevo producto</h1>
{{ form_start(form) }}
{# ... #}

{{ form_row(form.brochure) }}
{{ form_end(form) }}

</body>

app->config->config.yml

parameters:
    locale: en
    brochures_directory: '%kernel.root_dir%/../web/imagenes'

CKEditor

https://symfony.com/doc/master/bundles/IvoryCKEditorBundle/index.html

1.- Instalamos el

$ composer require egeloen/ckeditor-bundle

$ php bin/console assets:install web

2.- Añadir el bundle en app/AppKernel.php,

class AppKernel extends Kernel
{
    public function registerBundles()
    {
        $bundles = array(
            new Ivory\CKEditorBundle\IvoryCKEditorBundle(),
            // ...
        );

        // ...
    }
}

3. Creamos la entidad Ejemplo, que tenga dos atributos(textArea/editorcke), para que veamos la diferencia.

4. Creamos el formulario (EjemploType), descripción de tipo CKEditorType y top de tipo TextareaType.

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Ivory\CKEditorBundle\Form\Type\CKEditorType;
use Symfony\Component\Form\Extension\Core\Type\SubmitType;

class EjemploType extends AbstractType
{
    /**
     * @param FormBuilderInterface $builder
     * @param array $options
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('descripcion',CKEditorType::class)
            ->add('top',TextareaType::class)
            ->add('salvar',SubmitType::class)
        ;
    }
    
    /**
     * @param OptionsResolver $resolver
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'MiBundle\Entity\Ejemplo'
        ));
    }
}

5. Añadimos en el controlador.

/**
     * @Route("/editor", name="nuevo_editpr")
     */
    public function editorAction(Request $request)
    {
        $Ejemplo = new Ejemplo();
        $form = $this->createForm(EjemploType::class, $Ejemplo);
        $form->handleRequest($request);

        if ($form->isSubmitted() && $form->isValid()) {

            $em = $this->getDoctrine()->getManager();
            //Con el método persist() le decimos que el objeto pasado por argumento sea guardado para ser insertado
            // decirle a Doctrine que desea (eventualmente) guardar el Producto (no hay consultas aún)
            $em->persist($Ejemplo);
            // realmente ejecuta las consultas (es decir, la consulta INSERT)
            //y la inserción en sí se realizará cuando ejecutemos el método flush(). Con esta orden Doctrine generará la sentencia INSERT necesaria
            $em->flush();
        
            die();
            // return $this->render('MiBundle:Default:listar.html.twig');
           
        }

        return $this->render('MiBundle:Default:vista.html.twig', array('form' => $form->createView(), ));
    }

5. Vista

<body>

{{ form_start(form) }}
{{ form_widget(form) }}
{{ form_end(form) }}
</body>

AJAX

VISTA

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
....
    setInterval(ajaxCall, 1000);

function ajaxCall(){
 $.ajax({
                url:'{{ (path('listadotareas')) }}',
                type: "POST",
                dataType: "json",
                data: {
                    "valor": "vacio"
                },
                async: true,
                success: function (data)
                {
                    console.log(data)
                    $('div#ajax-results').html(data.output);

                }
            });

            };

ROUTING

listadotareas:
    path:     /listadotareas
    defaults: { _controller: MiBundle:Default:listadotareas }

CONTROLADOR

use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\JsonResponse
.........
public function listadotareasAction(Request $request)
    {


  if($request->request->get('valor')){
       
        $datos = ['info' => $request->request->get('valor')];
        return new JsonResponse($datos);
    }

    return $this->render('MiBundle:Default:inicio.html.twig');
}

EJERCICIO

Instalar uno de los mejores Bundle de Symfony, por ejemplo, EASYADMINBUNDLE

https://symfony.com/blog/the-30-most-useful-symfony-bundles-and-making-them-even-better