Materiales del curso de Symfony2

4,197 views
3,678 views

Published on

Materiales de apoyo utilizados para los cursos de Symfony2 impartidos en 2012 y 2013

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
4,197
On SlideShare
0
From Embeds
0
Number of Embeds
51
Actions
Shares
0
Downloads
39
Comments
0
Likes
3
Embeds 0
No embeds

No notes for slide

Materiales del curso de Symfony2

  1. 1. Materiales del curso Raúl Fraile Beneyto
  2. 2. Temario • Tema 1: Prerequisitos • • • • • PHP 5.3 YAML MVC PSR-0 (Autoloading) PSR-1, PSR-2 (Coding Standards)
  3. 3. Temario • Tema 2: Introducción a symfony2 • • • • • • • Historia Arquitectura Gestión de dependencias con Composer Inyección de dependencias Entornos de ejecución Instalación y configuración Documentación
  4. 4. Temario • Tema 3: Primeras páginas • • • Bundles Routing Controladores
  5. 5. Temario • Tema 4: Lenguaje de plantillas Twig • • • • Sintaxis básica Herencia Macros Twig extensions
  6. 6. Temario • Tema 5: Bases de datos con Doctrine2 • • • • • ORM DBAL Entidades y relaciones Configuración Fixtures
  7. 7. Temario • Tema 6: Frontend • • • Gestión de assets Tratamiento de imágenes Formatos alternativos
  8. 8. Temario • Tema 7: Backend • Formularios • • • • Conceptos básicos Generación automática de formularios Formularios avanzados Validación
  9. 9. Temario • Tema 8: Seguridad • • • • Autenticación VS Autorización Roles Configuración Login
  10. 10. Temario • Tema 9: Servicios y eventos • Contenedor de inyección de dependencias • Eventos
  11. 11. Temario • Tema 10: Extendiendo symfony2 • • Comandos de consola Extensiones propias de Twig
  12. 12. Temario • Tema 11: Optimización y rendimiento • • • Optimización de assets APC ESI
  13. 13. Temario • Proyecto: tienda “social” de libros • Usuarios se registran y compran libros. Tienen un perfil público con los libros comprados. • • • Los libros se agrupan por tecnologías (N:M). Backend para gestionar usuarios, libros y tecnologías. Multiidioma, optimización de assets, redimensión imágenes, canales RSS, etc.
  14. 14. Prerequisitos
  15. 15. PHP 5.3
  16. 16. Namespaces
  17. 17. Prerequisitos. PHP 5.3 / Namespaces <?php require_once(‘./libs/libA/foo.class.php’); require_once(‘./libs/libB/foo.class.php’); $foo = new Foo();
  18. 18. Prerequisitos. PHP 5.3 / Namespaces // ./libs/libA/Foo.php <?php namespace LibA; class Foo {} // ./libs/libB/Foo.php <?php namespace LibB; class Foo {}
  19. 19. Prerequisitos. PHP 5.3 / Namespaces // ./index.php <?php namespace MyApp; use LibAFoo; $foo = new Foo();
  20. 20. Prerequisitos. PHP 5.3 / Namespaces // ./index.php <?php namespace MyApp; use LibAFoo as FooA; use LibBFoo as FooB; $fooA = new FooA(); $fooB = new FooB();
  21. 21. Prerequisitos. PHP 5.3 / Namespaces // ./index.php <?php namespace MyApp; $created = new DateTime(); $len = strlen(‘hola’); echo $len . PHP_EOL;
  22. 22. Closures
  23. 23. Prerequisitos. PHP 5.3 / Closures <?php echo preg_replace_callback('/[a-z]/',     function ($match) {         return strtoupper($match[0]);     },     'Hola'); // HOLA
  24. 24. Annotations
  25. 25. Prerequisitos. PHP 5.3 / Annotations <?php /** * @ORMColumn(type="string") * @AssertNotBlank() */ protected $name;
  26. 26. Prerequisitos. PHP 5.3 / Annotations <?php /** * @Route("/signup") * @Method({"POST"}) */ public function signupAction() {     ... }
  27. 27. OOP
  28. 28. Prerequisitos. PHP 5.3 / OOP public $foo; private $foo; protected $foo;
  29. 29. Prerequisitos. PHP 5.3 / OOP <?php class Foo {     private $a = 1;     protected $b = 2;     public $c = 3; } $foo = new Foo(); echo $foo->a;
  30. 30. Prerequisitos. PHP 5.3 / OOP <?php class Foo {     private $a = 1;     protected $b = 2;     public $c = 3; } $foo = new Foo(); echo $foo->b;
  31. 31. Prerequisitos. PHP 5.3 / OOP <?php class Foo {     private $a = 1;     protected $b = 2;     public $c = 3; } class Bar extends Foo {     public function getA()     {         return $this->b;     } } $foo = new Foo(); echo $foo->getA();
  32. 32. Prerequisitos. PHP 5.3 / OOP ¿Diferencia entre una interface y una clase abstracta?
  33. 33. Prerequisitos. PHP 5.3 / OOP // JsonInterface.php interface JsonInterface {     const MAX_DEPTH = 100;     public function exportJson(); } // User.php class User implements {     public function exportJson()     {         return array(             'username' => $this->username;         );     } }
  34. 34. Prerequisitos. PHP 5.3 / OOP // BaseController.php abstract class BaseController {     public function getCurrentUser()     {         ...     } } // UserController.php class UserController extends BaseController {     ... }
  35. 35. Prerequisitos. PHP 5.3 / OOP ¿Puede haber herencia entre interfaces?
  36. 36. Prerequisitos. PHP 5.3 / OOP <?php namespace DemoBundleEntity; class User {     public function setBirthday(DateTime $date)     {         $this->birthday = $date;     } {
  37. 37. YAML
  38. 38. Prerequisitos. YAML “YAML is a human friendly data serialization standard for all programming languages. YAML is a great format for your configuration files. YAML files are as expressive as XML files and as readable as INI files.”
  39. 39. Prerequisitos. YAML parameters:     database_driver: pdo_mysql     database_host: localhost     database_port: ~     database_name: db_test     database_user: db_user_test     database_password: db_pass_test
  40. 40. Prerequisitos. YAML parameters:     emails: [ ‘e1@gmail.com’, ‘e2@gmail.com’ ]     webs:         - ‘web1.com’         - ‘web2.com’     technologies: { PHP: 5.3, MySQL: 5.1 }
  41. 41. MVC
  42. 42. Prerequisitos. MVC
  43. 43. PSR-0 (Autoloading)
  44. 44. Prerequisitos. PSR-0 (Autoloading) <?php require_once(‘classes/page.php’); require_once(‘classes/chapter.php’); require_once(‘classes/book.php’); $book = new Book(‘my book’); $chapter = new Chapter(‘chapter 1’); $book->addChapter($chapter);
  45. 45. Prerequisitos. PSR-0 (Autoloading) <?php require_once(‘autoload.php’); $book = new Book(‘my book’); $chapter = new Chapter(‘chapter 1’); $book->addChapter($chapter);
  46. 46. Prerequisitos. PSR-0 (Autoloading) FQN (Fully Qualified Name) Ruta SymfonyComponentFilesystem Filesystem [path]/Symfony/Component/ Filesystem/Filesystem.php Twig_Function [path]/Twig/Function.php
  47. 47. Prerequisitos. PSR-0 (Autoloading) ¿Impacto en el rendimiento de la aplicación?
  48. 48. PSR-1, PSR-2 (Coding Standards)
  49. 49. Prerequisitos. PSR-1, PSR-2 (Coding Standards) <?php function a($b) {     if ($b > 1) {         return $b;     }     return 0; } function a($b) {     if ($b > 1) return $b;     return 0; }
  50. 50. Prerequisitos. PSR-1, PSR-2 (Coding Standards) https://github.com/php-fig/fig-standards/blob/master/ accepted/PSR-1-basic-coding-standard.md https://github.com/php-fig/fig-standards/blob/master/ accepted/PSR-2-coding-style-guide.md
  51. 51. Introducción a SF2
  52. 52. Historia
  53. 53. Introducción a Symfony2. Historia Fabien Potencier
  54. 54. Introducción a Symfony2. Historia Versión Fecha PHP 1.0 Enero 2007 >= 5.0 1.1 Junio 2008 >= 5.1 1.2 Diciembre 2008 >= 5.2 1.3 Noviembre 2009 >= 5.2.4 1.4 Noviembre 2009 >= 5.2.4 2.0 Julio 2011 >= 5.3.2 2.1 Septiembre 2012 >= 5.3.3
  55. 55. Arquitectura
  56. 56. Introducción a Symfony2. Arquitectura BrowserKit EventDispatcher Routing ClassLoader Finder Security Config Form Serializer Console HttpFoundation Templating CssSelector HttpKernel Translation DependencyInjection Locale Validator DomCrawler Yaml Process
  57. 57. Introducción a Symfony2. Arquitectura Componentes + Bundles + Librerías externas Full-stack framework
  58. 58. Distribuciones
  59. 59. Gestión de dependencias con Composer
  60. 60. Introducción a Symfony2. Composer
  61. 61. Introducción a Symfony2. Composer “Composer is a tool for dependency management in PHP. It allows you to declare the dependent libraries your project needs and it will install them in your project for you”
  62. 62. Introducción a Symfony2. Composer $ curl -s https://getcomposer.org/installer | php $ sudo mv composer.phar /usr/local/bin/composer
  63. 63. Introducción a Symfony2. Composer Opción Descripción list Lista de opciones self-update Actualizar composer create-project Crea proyecto a partir dependencia init Crear composer.json básico validate Valida el archivo composer.json install Instala dependencias (.lock, .json) update Actualiza dependencias + .lock
  64. 64. Introducción a Symfony2. Composer # composer.json { "name": "raulfraile/demo_composer", "description": "Demo composer", "require": { "symfony/console": "2.1.*" } }
  65. 65. Introducción a Symfony2. Composer # composer.json { "name": "raulfraile/demo_composer", "description": "Demo composer", "require": { "php": ">=5.3.3", "symfony/console": "2.1.*" } }
  66. 66. Introducción a Symfony2. Composer # composer.json { "name": "raulfraile/demo_composer", "description": "Demo composer", "require": { "php": ">=5.3.3", "symfony/console": "2.1.*", "doctrine/orm": ">=2.2.3,<2.4-dev", } }
  67. 67. Demo: Composer + Console component
  68. 68. Introducción a Symfony2. Composer • Ejercicio. Crear un proyecto de consola que a partir de unos archivos en YAML con información sobre facturas, proporcione los siguientes comandos: summary (year): Muestra el total del año introducido (por defecto el actual). add date total: Añade un nuevo importe al archivo YAML correspondiente. Utilizar los componentes Console,Yaml y Finder. Para hacer debug os puede ser de utilidad el paquete raulfraile/ladybug.
  69. 69. Inyección de dependencias
  70. 70. Introducción a Symfony2. Inyección de dependencias "Dependency Injection is where components are given their dependencies through their constructors, methods, or directly into fields."
  71. 71. Introducción a Symfony2. Inyección de dependencias class UserController {     private $em;     public function __construct()     {         $this->em = new EntityManager();     } } $userController = new UserController();
  72. 72. Introducción a Symfony2. Inyección de dependencias class UserController {     private $em;     public function __construct()     {         $this->em = new EntityManager(‘conn2’);     } } $userController = new UserController();
  73. 73. Introducción a Symfony2. Inyección de dependencias class UserController {     private $em;     public function __construct(EntityManager $em)     {         $this->em = $em;     } } $em = new EntityManager(); $userController = new UserController($em);
  74. 74. DIC: Contenedor de Inyección de Dependencias
  75. 75. Introducción a Symfony2. Inyección de dependencias use SymfonyComponentDependencyInjection; use SymfonyComponentDependencyInjectionReference;   $sc = new DependencyInjectionContainerBuilder(); $sc->register('mailer', 'MyMailer'); $sc->register('em.main', 'MyEntityM')     ->setArguments(array('conn1')); $sc->register('logger', 'MyLogger')     ->setArguments(array(new Reference('em.main'))); $sc->get('logger');
  76. 76. Entornos de ejecución
  77. 77. Introducción a Symfony2. Entornos de ejecución Mismo código Diferentes configuraciones
  78. 78. Introducción a Symfony2. Entornos de ejecución Entorno ¿Mostrar errores? Cachear consultas Base de datos dev Sí No bd_dev prod No Sí bd_prod
  79. 79. Instalación y configuración
  80. 80. Introducción a Symfony2. Instalación y configuración $ composer create-project symfony/framework-standard-edition path/ 2.1.3
  81. 81. Introducción a Symfony2. Instalación y configuración
  82. 82. Documentación
  83. 83. Introducción a Symfony2. Documentación
  84. 84. Primeras páginas
  85. 85. Bundles
  86. 86. Primeras páginas. Bundles Todo el contenido de nuestra aplicación estará dentro de uno o más bundles
  87. 87. Primeras páginas. Bundles $ php app/console generate:bundle
  88. 88. Primeras páginas. Bundles // app/AppKernel.php class AppKernel extends Kernel {     public function registerBundles()     {         $bundles = array(             new SymfonyBundleFrameworkBundleFrameworkBundle(),             new SymfonyBundleSecurityBundleSecurityBundle(),             new SymfonyBundleTwigBundleTwigBundle(),             new SymfonyBundleMonologBundleMonologBundle(),             new SymfonyBundleSwiftmailerBundleSwiftmailerBundle(),             new SymfonyBundleAsseticBundleAsseticBundle(),             new DoctrineBundleDoctrineBundleDoctrineBundle(), ...         );         if (in_array($this->getEnvironment(), array('dev', 'test'))) {             $bundles[] = new SymfonyBundleWebProfilerBundleWebProfilerBundle();         }         return $bundles;     } }
  89. 89. Primeras páginas. Bundles
  90. 90. Routing
  91. 91. Primeras páginas. Routing /user/profile/raulfraile Controlador: UserController Acción: showProfile($username)
  92. 92. Primeras páginas. Routing # routing.yml user_profile:     pattern: /user/profile/{username}     defaults: { _controller: DemoBundle:User:profile }
  93. 93. Primeras páginas. Routing # routing.yml users_list:     pattern: /user/list/{page}     defaults: { _controller: DemoBundle:User:list, page: 1 }
  94. 94. Primeras páginas. Routing # routing.yml users_list:     pattern: /user/list/{page}     defaults: { _controller: DemoBundle:User:list, page: 1 } requirements: page: d+
  95. 95. Primeras páginas. Routing # routing.yml users_list:     pattern: /user/list/{page}{subpage}     defaults: { _controller: DemoBundle:User:list, page: 1 } requirements: page: d+ _method: GET
  96. 96. Primeras páginas. Routing # user.yml user_profile:     pattern: /profile/{username}     defaults: { _controller: DemoBundle:User:profile } # routing.yml user_routing: resource: "@DemoBundle/Resources/config/user.yml" prefix: /user
  97. 97. Primeras páginas. Routing $ php app/console router:debug [route_name]
  98. 98. Primeras páginas. Routing $ php app/console router:match path
  99. 99. Controladores
  100. 100. Primeras páginas. Controladores Request Response
  101. 101. Primeras páginas. Controladores use SymfonyComponentHttpFoundationResponse; class UserController {     public function helloAction()     {         return new Response('Hello world!');     } }
  102. 102. Primeras páginas. Controladores use SymfonyComponentHttpFoundationResponse; class UserController {     public function helloAction($name)     {         return new Response('Hello '.$name.'!');     } }
  103. 103. Primeras páginas. Controladores use SymfonyComponentHttpFoundationResponse; use SymfonyBundleFrameworkBundleController Controller; class UserController extends Controller {     public function helloAction($name)     {         return new Response('Hello '.$name.'!');     } }
  104. 104. Primeras páginas. Controladores public function indexAction() {     return $this->redirect($this->generateUrl('home')); }
  105. 105. Primeras páginas. Controladores public function indexAction() {     throw $this->createNotFoundException('Not found'); }
  106. 106. Demo: Controladores + objeto Request
  107. 107. Primeras páginas. Controladores • Ejercicio. Crear todas las rutas del proyecto, con sus respectivos controladores: Ruta Controller /register AccountController /book/{slug} BookController /technology/{slug} TechnologyController /author/{slug} AuthorController /book/buy/{slug} BookController /book/buy_confirm/{slug} BookController /user/{username} UserController /account/profile AccountController /account/password AccountController /search?q={consulta}&offset={offset}&limit={limit} SearchController /api/book/latest ApiController /api/book/featured ApiController
  108. 108. Primeras páginas. Controladores • Ejercicio. En la página de ‘search’, mostrar los valores de los 3 parámetros (query, offset y limit), además de: • IP del usuario • Idiomas del usuario (e idioma preferido) • Nombre de la ruta que está cogiendo
  109. 109. Primeras páginas. Controladores • Ejercicio. En las páginas de la API generar un JSON con los datos solicitados (de momento de prueba) y devolviendo las cabeceras correctas. Content-Type: application/json
  110. 110. Primeras páginas. Controladores • Ejercicio. En la página de ‘technology’, cuando se pidan libros de tecnología ‘java’, se debe redirigir al usuario para mostrar los libros de tecnología ‘php’ y además mostrar un mensaje diciendo ‘PHP es mejor’. Ayuda: flash messages • $session->getFlashBag()->add('notice', 'Profile updated'); • $session->getFlashBag()->get('notice', null);
  111. 111. Twig
  112. 112. Sintaxis básica
  113. 113. Twig. Sintaxis básica {{ }} {% %} {# #}
  114. 114. Twig. Sintaxis básica - Variables {{ name }} {{ user.name }} {{ user[‘name’] }}
  115. 115. Twig. Sintaxis básica - Variables {% set foo = 'foo' %} {% set foo = [1, 2] %} {% set foo = {'foo': 'bar'} %}
  116. 116. Twig. Sintaxis básica - Filtros {{ name|upper }} {{ name|striptags|nlbr }}
  117. 117. Twig. Sintaxis básica - Filtros abs capitalize convert_encoding date date_modify default escape format join json_encode keys length lower merge nl2br number_format raw replace reverse slice sort split striptags title trim upper url_encode
  118. 118. Twig. Sintaxis básica - Funciones {{ random(['rojo', 'azul', 'verde']) }} {{ random('ABC') }} {{ random() }} {{ random(5) }}
  119. 119. Twig. Sintaxis básica - Funciones {{ path('home') }} <a href=”{{ path(‘user_profile’, {‘username’: user.username }) }}”>
  120. 120. Twig. Sintaxis básica - Funciones {{ url('home') }} {{ url(‘user_profile’, {‘username’: user.username }) }}
  121. 121. Twig. Sintaxis básica - Control de flujo {% for user in users %} <li>{{ user.username }}</li> {% endfor %}
  122. 122. Twig. Sintaxis básica - Control de flujo {% for user in users %} <li>{{ user.username }}</li> {% else %} <li>No hay usuarios</li> {% endfor %}
  123. 123. Twig. Sintaxis básica - Control de flujo {% for user in users %} <li>{{ loop.index }} {{ user.username }}</li> {% endfor %}
  124. 124. Twig. Sintaxis básica - Control de flujo {% for user in users if user.active %} <li>{{ user.username }}</li> {% endfor %}
  125. 125. Twig. Sintaxis básica - Control de flujo {% for key, user in users %} <li>{{ user.username }}</li> {% endfor %}
  126. 126. Twig. Sintaxis básica - Control de flujo {% if user.active %} <p>Usuario activo</p> {% endif %}
  127. 127. Twig. Sintaxis básica - Control de flujo {% if user.status == 0 %} <p>Inactivo</p> {% elseif user.status == 1 %} <p>Activo</p> {% else %} <p>Deshabilitado</p> {% endif %}
  128. 128. Twig. Sintaxis básica • Ejercicio. Crear la vista search.html.twig, para que muestre exactamente los mismo que antes, pero utilizando Twig. Ayuda: $this->render(‘DemoBundle::search.html.twig’, array());
  129. 129. Twig. Sintaxis básica • Ejercicio. En la página del autor, simular que se devuelven libros de la base de datos con un array y crear una tabla/ lista con Twig de los títulos de los libros. Los títulos deberían ser un enlace a la página de cada uno de los libros.
  130. 130. Herencia
  131. 131. Twig. Herencia {% include %} {% extends %} {% render %}
  132. 132. Twig. Herencia {# _footer.html.twig #} <footer>&copy; {{ “now”|date(‘Y’) }}</footer> {# home.html.twig #} <html> <head> <title>Ejemplo include</title> </head> <body> ... {% include ‘_footer.html.twig’ %} </body> </html>
  133. 133. Twig. Herencia {# _footer.html.twig #} <footer>&copy; {{ “now”|date(‘Y’) }}</footer> {# home.html.twig #} <html> <head> <title>Ejemplo include</title> </head> <body> ... {% include ‘DemoBundle:web:_footer.html.twig’ %} </body> </html>
  134. 134. Twig. Herencia {# layout.html.twig #} <html> <head> <title>{% block title %}{% endblock %}</title> </head> <body> {% block content %}{% endblock %} </body> </html> {# user_profile.html.twig #} {% extends ‘layout.html.twig’ %} {% block title %}Perfil de usuario{% endblock %} {% block content %} <h1>{{ user.username }}</h1> {% endblock %}
  135. 135. Twig. Herencia {# layout.html.twig #} <html> <head> <title>{% block title %}{% endblock %}</title> </head> <body> {% block content %}{% endblock %} </body> </html> {# user_profile.html.twig #} {% extends ‘DemoBundle::layout.html.twig’ %} {% block title %}Perfil de usuario{% endblock %} {% block content %} <h1>{{ user.username }}</h1> {% endblock %}
  136. 136. Twig. Herencia {# layout.html.twig #} <html> <head> <title>{% block title %}Título{% endblock %}</title> </head> <body> {% block content %}{% endblock %} </body> </html> {# user_profile.html.twig #} {% extends ‘DemoBundle::layout.html.twig’ %} {% block title %}Perfil de usuario{% endblock %} {% block content %} <h1>{{ user.username }}</h1> {% endblock %}
  137. 137. Twig. Herencia {# index.html.twig #} <html> <head> <title>Título</title> </head> <body> ... {% render "DemoBundle:Notifications:show" %} </body> </html>
  138. 138. Twig. Herencia • Ejercicio. Crear un layout.html.twig con el código común a todas las páginas y que incluya bloques para: título de la página, css, javascript y contenido. Utilizar el layout en la página de search.
  139. 139. Twig. Herencia • Ejercicio. Suponed que queremos tener el código de Google Analytics en todas las páginas, y lo cremos en un archivo _analytics.html.twig, ¿dónde se añadiría? Además, sólo queremos que salga el código cuando estemos en el entorno de producción. (Ayuda, variable global ‘app’ de Twig). {{ app|ld }} {{ dump(app) }}
  140. 140. Macros
  141. 141. Twig. Macros {# macros.html.twig #} {% macro m_user_box(user) %} <div class=”user_box”> <p>{{ user.username }}</p> </div> {% endmacro %} {# index.html.twig #} {% import "macros.html.twig" as modules %} {{ modules.m_user_box(user) }}
  142. 142. Twig. Macros {# macros.html.twig #} {% macro m_user_box(user) %} <div class=”user_box”> <p>{{ user.username }}</p> </div> {% endmacro %} {# index.html.twig #} {% import "DemoBundle::macros.html.twig" as modules %} {{ modules.m_user_box(user) }}
  143. 143. Twig. Macros • Ejercicio. Crear 2 macros, que nos servirán para mostrar un libro (p.ej. en cualquier listado de libros: search, technologies, author...) y para mostrar un usuario (p.ej. para la lista de usuarios que han comprado ese libro). Ambas macros deben recibir la información del libro/ usuario y enlazar con la página de cada uno de ellos. Utilizar las 2 macros en las páginas de author y book, simulando que obtenemos los datos de la bd. {# macros.html.twig #} {% macro m_user_box(user) %} <div class=”user_box”> <p>{{ user.username }}</p> </div> {% endmacro %} {# index.html.twig #} {% import "DemoBundle::macros.html.twig" as modules %} {{ modules.m_user_box(user) }}
  144. 144. Extensiones
  145. 145. Twig. Extensiones Oficiales: Twig Extensions Repository Comunidad Propias
  146. 146. Doctrine 2
  147. 147. ORM
  148. 148. Doctrine 2. ORM ORM: Object Relational Mapper
  149. 149. Doctrine 2. ORM
  150. 150. Doctrine 2. ORM ORM DBAL PDO SQL Server MySQL Oracle ...
  151. 151. DBAL
  152. 152. Doctrine 2. DBAL class UserController extends Controller {     public function indexAction()     {         $conn = $this->get('database_connection');         $users = $conn->fetchAll('SELECT * FROM users');     } }
  153. 153. Entidades y relaciones
  154. 154. Doctrine 2. Entidades y relaciones namespace DemoDemoBundleEntity; use DoctrineORMMapping as ORM; /** * @ORMTable(name="category") * @ORMEntity */ class Category {     /** * @ORMColumn(name="id", type="integer") * @ORMId * @ORMGeneratedValue(strategy="AUTO") */     protected $id;     /** * @var string $name * @ORMColumn(name="name", type="string", length=255) */     protected $name; }
  155. 155. Doctrine 2. Entidades y relaciones /** * @ORMColumn(name="twitter", type="string", length=15, nullable=true) */ protected $twitter; /** * @ORMColumn(name="bio", type="text") */ protected $bio; /** * @ORMColumn(name="created_at", type="datetime") */ protected $createdAt; /** * @ORMColumn(name="languages", type="array") */ private $languages;
  156. 156. Doctrine 2. Entidades y relaciones // Relación 1:N unidireccional // User.php /** * @var City $city * * @ORMManyToOne(targetEntity="City") * @ORMJoinColumns({ * @ORMJoinColumn(name="city_id", referencedColumnName="id") * }) */ private $city;
  157. 157. Doctrine 2. Entidades y relaciones // Relación 1:N unidireccional // User.php /** * @var City $city * * @ORMManyToOne(targetEntity="City") * @ORMJoinColumns({ * @ORMJoinColumn(name="city_id", referencedColumnName="id") * }) */ private $city;
  158. 158. Doctrine 2. Entidades y relaciones // Relación 1:N bidireccional // Post.php /** * @var User $user * * @ORMManyToOne(targetEntity="User", inversedBy="posts") * @ORMJoinColumns({ * @ORMJoinColumn(name="user_id", referencedColumnName="id") * }) */ protected $user; // User.php /** * @var $posts PersistentCollection * * @ORMOneToMany(targetEntity="Post", mappedBy="user", cascade={"all"}) */ protected $posts;
  159. 159. Doctrine 2. Entidades y relaciones // Relación N:M bidireccional // Post.php /** * @var $categories PersistentCollection * * @ORMManyToMany(targetEntity="Category", inversedBy="posts") * @ORMJoinTable(name="post_category") */ protected $categories; // Category.php /** * @var $secrets PersistentCollection * * @ORMManyToMany(targetEntity="Post", mappedBy="categories") */ protected $posts;
  160. 160. Doctrine 2. Entidades y relaciones $ php app/console doctrine:generate:entity
  161. 161. Doctrine 2. Entidades y relaciones • Ejercicio. Crear todas las entidades necesarias para la siguiente base de datos:
  162. 162. Configuración
  163. 163. Doctrine 2. Configuración # parameters.yml parameters:     database_driver:     database_host:     database_port:     database_name:     database_user:     database_password: pdo_mysql localhost ~ db_name db_user db_pass
  164. 164. Doctrine 2. Configuración $ php app/console doctrine:schema:update --dump-sql --force
  165. 165. Doctrine 2. Fixtures • Ejercicio. Crear la base de datos del proyecto y el usuario que tendrá acceso. Configurar doctrine y generar el schema definido en las entities. Recordad que la base de datos debe estar en UTF-8: CREATE DATABASE books DEFAULT CHARACTER SET utf8 COLLATE utf8_general_ci;
  166. 166. Fixtures
  167. 167. Doctrine 2. Fixtures {     "require": {         "doctrine/doctrine-fixtures-bundle": "dev-master"     } } // app/AppKernel.php public function registerBundles() {     $bundles = array(         // ...         new DoctrineBundleFixturesBundle DoctrineFixturesBundle()     ); }
  168. 168. Doctrine 2. Fixtures // Demo/DemoBundle/DataFixtures/ORM/LoadUserData.php namespace DemoDemoBundleDataFixturesORM; use DoctrineCommonDataFixturesFixtureInterface; use DoctrineCommonPersistenceObjectManager; use DemoDemoBundleEntityUser; class LoadUserData implements FixtureInterface {     public function load(ObjectManager $manager)     {         $user = new User();         $user->setUsername('raulfraile');         $manager->persist($user);         $manager->flush();     } }
  169. 169. Doctrine 2. Fixtures // Demo/DemoBundle/DataFixtures/ORM/LoadUserData.php namespace DemoDemoBundleDataFixturesORM; use use use use DoctrineCommonDataFixturesAbstractFixture; DoctrineCommonDataFixturesOrderedFixtureInterface; DoctrineCommonPersistenceObjectManager; DemoDemoBundleEntityUser; class LoadUserData extends AbstractFixture implements OrderedFixtureInterface {     public function getOrder()     {         return 1;     }     public function load(ObjectManager $manager)     {         // ...         $this->addReference('user_' . $user->getUsername(), $user);     } }
  170. 170. Doctrine 2. Fixtures // Demo/DemoBundle/DataFixtures/ORM/LoadBookData.php namespace DemoDemoBundleDataFixturesORM; use use use use DoctrineCommonDataFixturesAbstractFixture; DoctrineCommonDataFixturesOrderedFixtureInterface; DoctrineCommonPersistenceObjectManager; DemoDemoBundleEntityBook; class LoadBookData extends AbstractFixture implements OrderedFixtureInterface {     public function getOrder()     {         return 2;     }     public function load(ObjectManager $manager)     {         // ...         $book->setUser($this->getReference('user_'.$data[‘slug’]));     } }
  171. 171. Doctrine 2. Fixtures $ php app/console doctrine:fixtures:load --purge-with-truncate
  172. 172. Buenas prácticas
  173. 173. Doctrine 2. Fixtures CONST FIXTURE_REF = ‘user’ users.yml LoadUserData.php CONST FIXTURE_REF = ‘books’ books.yml LoadBookData.php CONST FIXTURE_REF = ‘author’ authors.yml LoadAuthorData.php CONST FIXTURE_REF = ‘technology’ technologies.yml LoadTechnologyData.php
  174. 174. Doctrine 2. Fixtures • Ejercicio. Crear datos de prueba (incluidas imágenes) en archivos yml, además de los correspondientes scripts para cargar los fixtures.
  175. 175. nelmio/alice
  176. 176. Doctrine 2. Fixtures • Ejercicio. Rehacer los fixtures para utilizar nelmio/alice y además generar usuarios de prueba utilizando fzaninotto/ faker.
  177. 177. Repositorios
  178. 178. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/User.php /** * @ORMEntity(repositoryClass="DemoDemoBundle EntityUserRepository") */ class User {     //... }
  179. 179. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/UserRepository.php use DoctrineORMEntityRepository; class UserRepository extends EntityRepository {     public function findAllActive()     {         return $this->getEntityManager()             ->createQuery('SELECT u FROM DemoBundle:User u WHERE u.active = TRUE')             ->getResult();     } }
  180. 180. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/UserRepository.php use DoctrineORMEntityRepository; class UserRepository extends EntityRepository {     public function findAllFromCountry($country)     {         return $this->getEntityManager()             ->createQuery('SELECT u FROM DemoBundle:User u WHERE u.country = :country') ->setParameter('country', $country);             ->getResult();     } }
  181. 181. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/UserRepository.php   use DoctrineORMEntityRepository;   class UserRepository extends EntityRepository { public function findAllFromSpain($limit = null, $offset = null) { $query = $this->getEntityManager() ->createQuery('SELECT u FROM DemoBundle:User u WHERE u.country = :country') ->setParameter('country', 'es'); if (!is_null($limit)) { $query->setMaxResults($limit); } if (!is_null($limit)) { $query->setFirstResult($offset); } return $query->getResult(); } }
  182. 182. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/UserRepository.php use DoctrineORMEntityRepository; class UserRepository extends EntityRepository {     public function countUsersFromSpain() { $dql = 'SELECT COUNT(u.id) FROM DemoBundle:User u WHERE u.country = :country';   $query = $this->getEntityManager()->createQuery($dql) ->setParameter('country', 'es') ;   try { return $query->getSingleScalarResult(); } catch (DoctrineORMNoResultException $e) { return 0; } } }
  183. 183. Doctrine 2. Repositorios // Demo/DemoBundle/Entity/UserRepository.php   use DoctrineORMEntityRepository;   class UserRepository extends EntityRepository { public function findAllFromSpain() { $qb = $this->getEntityManager()->createQueryBuilder() ->select('u') ->from('DemoBundle:User', 'u') ->where('u.country = :country') ->orderBy('u.id', 'desc') ;   $query = $qb->getQuery(); $query->setParameter('country', 'es'); try { return $query->getResult(); } catch (DoctrineORMNoResultException $e) { return false; } } }
  184. 184. Doctrine 2. Repositorios // Demo/DemoBundle/Controller/UserController.php class UserController extends Controller {     public function listActiveAction()     {         $em = $this->getEntityManager();         $userRepository = $em->getRepository('DemoBundle:User');         $all = $userRepository->findAll();         $userId1 = $userRepository->find(1);         $userRaul = $userRepository->findOneByUsername('raul');         $usersValencia = $userRepository->findOneBy(array(             'active' => true,             'city' => 'Valencia'         ));         $usersActive = $userRepository->findAllActive(); // custom query     } }
  185. 185. Doctrine 2. Repositorios • Ejercicio. Una vez tenemos datos de prueba y sabemos como acceder a los datos, crear las siguientes páginas: - Listado de autores - Perfil público de un usuario
  186. 186. Frontend
  187. 187. Gestión de assets
  188. 188. Frontend. Gestión de assets {{ asset('/bundles/books/images/logo.png') }}
  189. 189. Frontend. Gestión de assets framework:     ...     templating:         engines: ['twig']         assets_version: v0.2
  190. 190. Frontend. Gestión de assets http://ejemplo.com/bundles/books/images/logo.png?v0.2
  191. 191. Frontend. Gestión de assets php app/console assets:install web/ • Ejercicio. Añadir en el layout (utilizando la función ‘asset’ de Twig): - Logo de la web - Dos archivos CSS con algunos estilos básicos Una vez añadidos, utilizar la opción para versionar assets y comprobar como la URL cambia en cada cambio de versión.
  192. 192. Assetic
  193. 193. Frontend. Assetic “Assetic is an asset management framework for PHP, based on the Python webassets library.”
  194. 194. Frontend. Assetic Assets Filtros reset.css bootstrap.css global.css styles.css
  195. 195. Frontend. Assetic LessphpFilter OptiPngFilter PackerFilter PngoutFilter CompassFilter GoogleClosureCompilerApiFilter SassSassFilter GoogleClosureCompilerJarFilter SassScssFilter HandlebarsFilter SprocketsFilter JpegoptimFilter StylusFilter JpegtranFilter YuiCssCompressorFilter LessFilter YuiJsCompressorFilter CoffeeScriptFilter CssEmbedFilter CssImportFilter CssMinFilter CssRewriteFilter
  196. 196. Frontend. Assetic {% javascripts '@BooksBundle/Resources/public/js/*'%}     <script type="text/javascript" src="{{ asset_url }}"> </script> {% endjavascripts %}
  197. 197. Frontend. Assetic {% javascripts '@BooksBundle/Resources/public/js/1.js' '@BooksBundle/Resources/public/js/2.js' '@BooksBundle/Resources/public/js/3.js'%}     <script type="text/javascript" src="{{ asset_url }}"> </script> {% endjavascripts %}
  198. 198. Frontend. Assetic {% javascripts '@BooksBundle/Resources/public/js/1.js' '@BooksBundle/Resources/public/js/2.js' '@BooksBundle/Resources/public/js/3.js' filter='yui_js' output='js/script.js'%}     <script type="text/javascript" src="{{ asset_url }}"> </script> {% endjavascripts %}
  199. 199. Frontend. Assetic {% javascripts '@BooksBundle/Resources/public/js/1.js' '@BooksBundle/Resources/public/js/2.js' '@BooksBundle/Resources/public/js/3.js' filter='?yui_js' output='js/script.js'%}     <script type="text/javascript" src="{{ asset_url }}"> </script> {% endjavascripts %}
  200. 200. Frontend. Assetic $ php app/console assetic:dump --env=prod --watch
  201. 201. Frontend. Assetic • Ejercicio. Utilizando Assetic, combinar por separado los 2 css y los 2 javascripts, y comprimiéndolos con YUI Compressor. Nota: ver entrada del cookbock para detalles de configuración: http://symfony.com/doc/current/cookbook/assetic/ yuicompressor.html
  202. 202. Tratamiento de imágenes
  203. 203. Frontend. Tratamiento de imágenes AvalancheImagineBundle GD2 Imagine Imagick Gmagick
  204. 204. Frontend. Tratamiento de imágenes // GD resize $width = 80 $height = 80 $src = imagecreatefrompng('image.png'); $dest = imagecreatetruecolor($width, $height); imagealphablending($dest, false); imagesavealpha($dest, true); imagecopyresampled($dest, $src, 0, 0, 0, 0, $width, $height, imagesx($src), imagesy($src)); imagepng($dest,'image_resized.png');
  205. 205. Frontend. Tratamiento de imágenes // Imagick resize $width = 80; $height = 80; $image = new Imagick('image.png'); $image->adaptiveResizeImage($width, $height); $image->writeImage('image_resized.png');
  206. 206. Frontend. Tratamiento de imágenes // Imagine resize $width = 80; $height = 80; $imagine = new ImagineGdImagine(); $imagine = new ImagineImagickImagine(); $imagine->open('image.png')     ->resize(new ImagineBox($width, $height))     ->save('image_resized.png');
  207. 207. Frontend. Tratamiento de imágenes http://www.slideshare.net/avalanche123/introductiontoimagine
  208. 208. Frontend. Tratamiento de imágenes ¿Cómo usarlo en Symfony para hacer thumbnails de imágenes subidas por los usuarios?
  209. 209. Frontend. Tratamiento de imágenes # app/config.yml avalanche_imagine:    filters:        user_small:            type:            options:        user_medium:            type:            options:        user_big:            type:            options: thumbnail { size: [30, 30], mode: outbound } thumbnail { size: [60, 60], mode: outbound } thumbnail { size: [90, 90], mode: outbound }
  210. 210. Frontend. Tratamiento de imágenes <img src="{{ user.webPath|apply_filter('user_small') }}" />
  211. 211. Demo: AvalancheImagineBundle + Upload Doctrine
  212. 212. Formatos alternativos
  213. 213. Frontend. Formatos alternativos # routing.yml api_books_latest:     pattern: /api/latest.{_format}     defaults: { _controller: BooksBundle:Api:latest }
  214. 214. Frontend. Formatos alternativos # routing.yml api_books_latest:     pattern: /api/latest.{_format}     defaults: { _controller: BooksBundle:Api:latest, _format: html }
  215. 215. Frontend. Formatos alternativos public function latestAction() {     $format = $this->getRequest()->getRequestFormat(); }
  216. 216. Frontend. Formatos alternativos • Ejercicio. Generar la API /api/books/latest para que permitan obtener el contenido en JSON y XML, dependiendo del formato pasado. Por ejemplo: /api/books/latest.xml: contenido en XML Por defecto, el formato será JSON. Nota: Algunos formatos son más propensos a ser generados a través de Twig que otros, tenedlo en cuenta.
  217. 217. Backend
  218. 218. Formularios
  219. 219. Backend. Formularios namespace BooksBooksBundleForm; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilderInterface; use SymfonyComponentOptionsResolverOptionsResolverInterface; class UserType extends AbstractType {     public function buildForm(FormBuilderInterface $builder, array $options)     {         $builder->add('username', 'text');     }     public function setDefaultOptions(OptionsResolverInterface $resolver)     {         $resolver->setDefaults(array(             'data_class' => 'BooksBooksBundleEntityUser'         ));     }     public function getName()     {         return 'form_user';     } }
  220. 220. Backend. Formularios public function createAction(Request $request) {     $user = new User();     $form = $this->createForm(new UserType(), $user);     $form->bind($request);     if ($form->isValid()) {         $em = $this->getEntityManager();         $em->persist($entity);         $em->flush();         return $this->redirect($this->generateUrl('user_created'));     }     return $this->render('BooksBundle:Users:new.html.twig', array(         'entity' => $user,         'form' => $form->createView(),     )); }
  221. 221. Backend. Formularios <form action="{{ path('user_create') }}" method="post" {{ form_enctype(form) }}>     {{ form_errors(form) }}     {{ form_row(form) }}     {{ form_rest(form) }}     <input type="submit" /> </form>
  222. 222. Backend. Formularios <form action="{{ path('user_create') }}" method="post" {{ form_enctype(form) }}>     {{ form_errors(form) }}     {{ form_row(form.username) }}     {{ form_rest(form) }}     <input type="submit" /> </form>
  223. 223. Backend. Formularios namespace BooksBooksBundleForm; use SymfonyComponentFormAbstractType; use SymfonyComponentFormFormBuilderInterface; use SymfonyComponentOptionsResolverOptionsResolverInterface; class UserType extends AbstractType {     public function buildForm(FormBuilderInterface $builder, array $options)     {         $builder->add('username', 'text', array(             'required' => true,             'label' => 'Nombre de usuario'             'attr' => array(                 'class' => 'text_big'             )         ));         $builder->add('email', 'email');     }     ... }
  224. 224. Backend. Formularios text textarea email integer money number password percent search url choice entity country language locale timezone date datetime time birthday checkbox file radio collection repeated hidden csrf field form
  225. 225. Backend. Formularios http://symfony.com/doc/current/reference/forms/types.html
  226. 226. Backend. Formularios • Ejercicio. Generar tres nuevas rutas, que servirán para dar de alta un libro desde el backend: /admin/books/new /admin/books/create /admin/books/created Crear el formulario para poder introducir nuevos libros en la base de datos. Los campos deberán ser: título (text), descripción (textarea), isbn (text), price (money) y techonology (entity).
  227. 227. Backend. Formularios • Ejercicio. Realizar el mismo proceso para editar un libro desde el backend, utilizando el mismo formulario. /admin/books/edit/{id} /admin/books/update/{id} /admin/books/updated
  228. 228. Backend. Formularios • Ejercicio. Crear el listado de libros con enlaces para editarlos, y un link para añadir un nuevo libro en la parte superior: /admin/books/list
  229. 229. Backend. Formularios Generación de CRUDs automáticos Create Read Update Delete
  230. 230. Backend. Formularios
  231. 231. Backend. Formularios $ php app/console generate:doctrine:crud
  232. 232. Backend. Formularios • Ejercicio. Utilizando el generador de CRUD, crear las acciones necesarias para gestionar los libros, a partir de la ruta /admin/v2/books. Una vez que lo tengamos en funcionamiento, estudiar las acciones que ha creado y añadir lo que considereis necesario en las plantillas, namespaces, formulario... Finalmente, sustituir el CRUD que habíamos hecho a mano por éste, para que se muestre en /admin/books.
  233. 233. Validación
  234. 234. Backend. Validación /** * @var string $title * * @AssertNotBlank * @AssertMaxLength(250) * @ORMColumn(name="title", type="string", length=250) */ protected $title;
  235. 235. Backend. Validación public function createAction(Request $request) {     $user = new User();     $form = $this->createForm(new UserType(), $user);     $form->bind($request);     if ($form->isValid()) {         $em = $this->getEntityManager();         $em->persist($entity);         $em->flush();         return $this->redirect($this->generateUrl('user_created'));     }     return $this->render('BooksBundle:Users:new.html.twig', array(         'entity' => $user,         'form' => $form->createView(),     )); }
  236. 236. Backend. Validación <form action="{{ path('user_create') }}" method="post" {{ form_enctype(form) }}>     {{ form_errors(form) }}     {{ form_row(form.username) }}     {{ form_rest(form) }}     <input type="submit" /> </form>
  237. 237. Backend. Validación Constraints NotBlank Null Type MaxLength Regex Min DateTime Collection Language File All Blank TRUE Email Length Ip Range Time Count Locale Image UserPassword NotNull FALSE MinLength Url Max Date Choice UniqueEntity Country Callback Valid
  238. 238. Backend. Validación http://symfony.com/doc/current/reference/constraints.html
  239. 239. Backend. Validación • Ejercicio. Añadir las siguientes reglas de validación al modelo de datos: Author: - country: código de país válido - name: valor requerido Book: - isbn: isbn válido de 10 dígitos - price: no puede ser negativo User: - email: email válido - locale: locale válido - username: letras, números o caracter “_”, de una longitud mínima de 3 y máxima de 15
  240. 240. Seguridad
  241. 241. Autenticación VS Autorización
  242. 242. Seguridad. Autenticación VS Autorización Firewall Control de acceso
  243. 243. Seguridad. Autenticación VS Autorización
  244. 244. Seguridad. Autenticación VS Autorización
  245. 245. Seguridad. Autenticación VS Autorización
  246. 246. Seguridad. Autenticación VS Autorización
  247. 247. Roles
  248. 248. Seguridad. Roles ROLE_USER Usuario ROLE_ADMIN ROLE_TRANSLATOR
  249. 249. Seguridad. Roles ROLE_USER Usuario ROLE_ADMIN ROLE_TRANSLATOR
  250. 250. Configuración
  251. 251. Seguridad. Configuración security:     encoders:         SymfonyComponentSecurityCoreUserUser: plaintext     role_hierarchy:         ROLE_ADMIN: ROLE_USER         ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]     providers:         in_memory:             memory:                 users:                     user: { password: userpass, roles: [ 'ROLE_USER' ] }                     admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }     firewalls:         dev:             pattern: ^/(_(profiler|wdt)|css|images|js)/             security: false         login:             pattern: ^/demo/secured/login$             security: false         secured_area:             pattern: ^/demo/secured/             form_login:                 check_path: /demo/secured/login_check                 login_path: /demo/secured/login             logout:                 path: /demo/secured/logout                 target: /demo/             #anonymous: ~     access_control:         #- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }         #- { path: ^/_internal/secure, roles: IS_AUTHENTICATED_ANONYMOUSLY, ip: 127.0.0.1 }
  252. 252. Seguridad. Configuración security:     encoders:         EmpresaBooksBundleEntityUser: sha512       role_hierarchy:         ROLE_ADMIN: ROLE_USER         ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]       providers: SymfonyComponentSecurityCoreUserUserInterface         main: entity: { class: EmpresaBooksBundleEntityUser, property: username }   getRoles()   getPassword()     firewalls: getSalt()         dev: getUsername()             pattern: ^/(_(profiler|wdt)|css|images|js)/ eraseCredentials()             security: false           main:             pattern: ^/ form_login: check_path: /login_check                 login_path: /login logout: path: /logout target: / anonymous: true       access_control:         - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }         - { path: ^/admin/, role: ROLE_ADMIN }
  253. 253. Login
  254. 254. Seguridad. Login # app/config/routing.yml login: pattern: /login defaults: { _controller: EmpresaBooksBundle:Security:login } login_check: pattern: logout: pattern: /login_check /logout
  255. 255. Seguridad. Login // src/Empresa/BooksBundle/Controller/SecurityController.php; namespace EmpresaBooksBundleController;   use SymfonyBundleFrameworkBundleControllerController; use SymfonyComponentSecurityCoreSecurityContext;   class SecurityController extends Controller { public function loginAction() { $request = $this->getRequest(); $session = $request->getSession();   // get the login error if there is one if ($request->attributes->has(SecurityContext::AUTHENTICATION_ERROR)) { $error = $request->attributes->get( SecurityContext::AUTHENTICATION_ERROR ); } else { $error = $session->get(SecurityContext::AUTHENTICATION_ERROR); $session->remove(SecurityContext::AUTHENTICATION_ERROR); }   return $this->render( 'EmpresaBooksBundle:Security:login.html.twig', array( // last username entered by the user 'last_username' => $session->get(SecurityContext::LAST_USERNAME), 'error' => $error, ) ); } }
  256. 256. Seguridad. Login {# src/Empresa/BooksBundle/Resources/views/Security/ login.html.twig #} {% if error %} <div>{{ error.message }}</div> {% endif %}   <form action="{{ path('login_check') }}" 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" />   <button type="submit">login</button> </form>
  257. 257. Servicios y eventos
  258. 258. Servicios
  259. 259. Demo: Servicios
  260. 260. Eventos
  261. 261. Demo: Eventos
  262. 262. Extendiendo SF2
  263. 263. Comandos personalizados
  264. 264. Extendiendo Symfony2. Comandos personalizados // src/Empresa/BooksBundle/Command/BooksListCommand.php class BooksListCommand extends ContainerAwareCommand {     protected function configure()     {         $this             ->setName('books:list')             ->setDescription('Books list')         ;     }     protected function execute(InputInterface $input, OutputInterface $output)     {         $em = $this->getContainer()->get('doctrine.orm.entity_manager');         $books = $em->getRepository('BooksBundle:Book')->findAll();         foreach ($books as $book) {             $output->writeln($book->getTitle());         }              } }
  265. 265. Backend. Validación • Ejercicio. Crear el comando books:author, que muestre por consola los libros de un determinado autor, introducido por parámetro: php app/console books:author author_id
  266. 266. Extensiones de Twig
  267. 267. Extendiendo Symfony2. Extensiones de Twig // src/Empresa/BooksBundle/Twig/HtmlExtension.php class HtmlExtension extends Twig_Extension {     public function getFunctions()     {         return array(             'strong' => new Twig_Function_Method($this, 'getStrong', array('is_safe' => array('html')))         );     }     public function getStrong($text)     {         return '<strong>' . $text . '</strong>';     }     public function getName()     {         return 'html';     } }
  268. 268. Extendiendo Symfony2. Extensiones de Twig # services.yml books.twig.html_extension:     class: EmpresaBooksBundleTwigHtmlExtension     tags:         - { name: twig.extension }
  269. 269. Backend. Validación • Ejercicio. Crear una función de Twig que reciba un objeto de tipo libro y devuelva una cadena de texto con el título del libro y el nombre del autor entre paréntesis.
  270. 270. Rendimiento
  271. 271. Optimización y rendimiento Ejecutar controlador frontal (app[_dev].php) Procesar archivos de configuración Cargar bundles Cargar rutas y decidir la ruta solicitada Ejecutar controlador interno Parsear plantilla Twig Generar respuesta (contenido + headers)
  272. 272. Optimización y rendimiento Cache
  273. 273. Optimización y rendimiento
  274. 274. APC
  275. 275. Optimización y rendimiento. APC Byte Code Cache
  276. 276. Optimización y rendimiento. APC GET /index.php HTTP/1.1 Lee el archivo index.php y lo introduce en memoria El analizador léxico (lexer) lee el código fuente y genera una serie de “tokens” El analizador sintáctico (parser) parsea los tokens y genera una serie de “opcodes”, los cuales son ejecutados directamente por el motor de PHP Se ejecutan los “opcodes”
  277. 277. Optimización y rendimiento. APC GET /index.php HTTP/1.1 (con APC) La primera vez se realizan los mismos pasos (lexer + parser) para generar los “opcodes”. Una vez generados se guardan en memoria. Las siguientes veces utiliza los “opcodes” guardados en memoria y los ejecuta directamente
  278. 278. Optimización y rendimiento. APC System/User cache
  279. 279. Optimización y rendimiento. APC // app.php $loader = require_once __DIR__.'/../app/ bootstrap.php.cache'; $loader = new ApcClassLoader('books', $loader); $loader->register(true);
  280. 280. Optimización y rendimiento. APC # app/config/config_prod.yml doctrine:     orm:         metadata_cache_driver: apc         result_cache_driver: apc         query_cache_driver: apc
  281. 281. Composer
  282. 282. Optimización y rendimiento. Composer $ composer dump-autoload --optimize
  283. 283. Extra
  284. 284. Seguridad
  285. 285. Extra. Seguridad • Ejercicio. Configurar la seguridad de Symfony2 para utilizar la entity User como proveedor de usuarios, codificando los passwords con sha512 y definiendo roles. Crear la página de login, y cuando el usuario esté logueado mostrar su nombre y un enlace para hacer logout.
  286. 286. Extra. Seguridad • Ejercicio. Crear la página /myprofile, que mostrará los datos del usuario actual, y solo se podrá acceder si está logueado y tiene el rol ROLE_USER.
  287. 287. Extra. Seguridad • Ejercicio. Crear la página /myadmin, a la que solo se podrá acceder si el usuario logueado tiene el rol ROLE_ADMIN.
  288. 288. Doctrine 2. Repositorios
  289. 289. Extra. Doctrine2 Repositorios • Ejercicio. Crear la página /search, que recibirá dos parámetros por GET: type y query: /search?type=book&query=programming Dependiendo de ‘type’ hará una búsqueda fulltext de libros (por título, descripción e isbn), autores (por nombre), tecnologías (por nombre) o usuarios (por username).
  290. 290. Extra. Doctrine2 Repositorios • Ejercicio. Crear la página /sales, que mostrará los 5 libros más baratos.
  291. 291. Extra. Doctrine2 Repositorios • Ejercicio. Añadir un campo llamado ‘active’ a las entidades ‘Book’ y ‘Author’, de tipo boolean. Cambiar los fixtures para tener libros/autores activos e inactivos. Crear la página /stats que muestre: - Número de libros activos - Número de libros inactivos - Número de autores activos - Número de autores inactivos - Media de autores por libro
  292. 292. Formularios
  293. 293. Extra. Formularios • Ejercicio. Añadir tres campos a la entity User: - birthday - country - bio Crear la página /myprofile/edit para editar los datos del perfil del usuario actual y /myprofile/password para cambiar el password (solicitando el password actual y pidiéndolo por duplicado). Crear el método hasLegalAgeForDrink() en la entity User, que devolverá TRUE si el usuario es mayor de edad, dependiendo del país: (es: 18, us: 21, ir: ilegal) http://en.wikipedia.org/wiki/Legal_drinking_age

×