Raúl Fraile Beneyto
Materiales del curso
Temario
• Tema 1: Prerequisitos
• PHP 5.3
• YAML
• MVC
• PSR-0 (Autoloading)
• PSR-1, PSR-2 (Coding Standards)
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
Temario
• Tema 3: Primeras páginas
• Bundles
• Routing
• Controladores
Temario
• Tema 4: Lenguaje de plantillas Twig
• Sintaxis básica
• Herencia
• Macros
• Twig extensions
Temario
• Tema 5: Bases de datos con Doctrine2
• ORM
• DBAL
• Entidades y relaciones
• Configuración
• Fixtures
Temario
• Tema 6: Frontend
• Gestión de assets
• Tratamiento de imágenes
• Formatos alternativos
Temario
• Tema 7: Backend
• Formularios
• Conceptos básicos
• Generación automática de formularios
• Formularios avanzados
• Validación
Temario
• Tema 8: Seguridad
• Autenticación VS Autorización
• Roles
• Configuración
• Login
Temario
• Tema 9: Servicios y eventos
• Contenedor de inyección de dependencias
• Eventos
Temario
• Tema 10: Extendiendo symfony2
• Comandos de consola
• Extensiones propias de Twig
Temario
• Tema 11: Optimización y rendimiento
• Optimización de assets
• APC
• ESI
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.
Prerequisitos
PHP 5.3
Namespaces
<?phprequire_once(‘./libs/libA/foo.class.php’);require_once(‘./libs/libB/foo.class.php’);
$foo = new Foo();
Prerequisitos. PHP 5.3 / Namespaces
// ./libs/libA/Foo.php<?phpnamespace LibA;
class Foo {}
// ./libs/libB/Foo.php<?phpnamespace LibB;
class Foo {}
Prerequisitos. PHP 5.3 / Namespaces
// ./index.php<?phpnamespace MyApp;
use LibA\Foo;
$foo = new Foo();
Prerequisitos. PHP 5.3 / Namespaces
// ./index.php<?phpnamespace MyApp;
use LibA\Foo as FooA;use LibB\Foo as FooB;
$fooA = new FooA();$fooB = new FooB();
Prerequisitos. PHP 5.3 / Namespaces
// ./index.php<?phpnamespace MyApp;
$created = new \DateTime();$len = strlen(‘hola’);echo $len . PHP_EOL;
Prerequisitos. PHP 5.3 / Namespaces
Closures
<?php
echo preg_replace_callback('/[a-z]/', function ($match) { return strtoupper($match[0]); }, 'Hola'); // HOLA
Prerequisitos. PHP 5.3 / Closures
Annotations
<?php
/** * @ORM\Column(type="string") * @Assert\NotBlank() */protected $name;
Prerequisitos. PHP 5.3 / Annotations
<?php
/** * @Route("/signup") * @Method({"POST"}) */public function signupAction(){ ...}
Prerequisitos. PHP 5.3 / Annotations
OOP
public $foo;
private $foo;
protected $foo;
Prerequisitos. PHP 5.3 / OOP
<?php
class Foo{ private $a = 1; protected $b = 2; public $c = 3;}
$foo = new Foo();echo $foo->a;
Prerequisitos. PHP 5.3 / OOP
<?php
class Foo{ private $a = 1; protected $b = 2; public $c = 3;}
$foo = new Foo();echo $foo->b;
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();
Prerequisitos. PHP 5.3 / OOP
¿Diferencia entre una interface y una clase abstracta?
Prerequisitos. PHP 5.3 / OOP
Prerequisitos. PHP 5.3 / OOP// JsonInterface.phpinterface JsonInterface{ const MAX_DEPTH = 100;
public function exportJson();}
// User.phpclass User implements{ public function exportJson() { return array( 'username' => $this->username; ); }}
Prerequisitos. PHP 5.3 / OOP
// BaseController.phpabstract class BaseController{ public function getCurrentUser() { ... }}
// UserController.phpclass UserController extends BaseController{ ...}
¿Puede haber herencia entre interfaces?
Prerequisitos. PHP 5.3 / OOP
Prerequisitos. PHP 5.3 / OOP
<?php
namespace DemoBundle\Entity;
class User{ public function setBirthday(\DateTime $date) { $this->birthday = $date; }{
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.”
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
Prerequisitos. YAML
parameters: emails: [ ‘[email protected]’, ‘[email protected]’ ] webs: - ‘web1.com’ - ‘web2.com’ technologies: { PHP: 5.3, MySQL: 5.1 }
Prerequisitos. YAML
MVC
Prerequisitos. MVC
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);
Prerequisitos. PSR-0 (Autoloading)
<?php
require_once(‘autoload.php’);
$book = new Book(‘my book’);$chapter = new Chapter(‘chapter 1’);$book->addChapter($chapter);
Prerequisitos. PSR-0 (Autoloading)
FQN(Fully Qualified Name) Ruta
\Symfony\Component\Filesystem\Filesystem
[path]/Symfony/Component/Filesystem/Filesystem.php
\Twig_Function [path]/Twig/Function.php
Prerequisitos. PSR-0 (Autoloading)
¿Impacto en el rendimiento de la aplicación?
Prerequisitos. PSR-0 (Autoloading)
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;}
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
Prerequisitos. PSR-1, PSR-2 (Coding Standards)
Introducción a SF2
Historia
Fabien Potencier
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
Introducción a Symfony2. Historia
Arquitectura
BrowserKit
ClassLoader
Config
Console
CssSelector
DependencyInjection
DomCrawler
EventDispatcher
Finder
Form
HttpFoundation
Locale
Process
HttpKernel
Routing
Security
Serializer
Templating
Translation
Validator
Yaml
Introducción a Symfony2. Arquitectura
Introducción a Symfony2. Arquitectura
Componentes + Bundles + Librerías externas
Full-stack framework
Distribuciones
Gestión de dependencias con Composer
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”
Introducción a Symfony2. Composer
$ curl -s https://getcomposer.org/installer | php
$ sudo mv composer.phar /usr/local/bin/composer
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
Introducción a Symfony2. Composer
# composer.json
{ "name": "raulfraile/demo_composer", "description": "Demo composer", "require": { "symfony/console": "2.1.*" }}
Introducción a Symfony2. Composer
# composer.json
{ "name": "raulfraile/demo_composer", "description": "Demo composer", "require": { "php": ">=5.3.3", "symfony/console": "2.1.*" }}
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", }}
Introducción a Symfony2. Composer
Demo: Composer + Console component
• 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.
Introducción a Symfony2. Composer
Inyección de dependencias
"Dependency Injection is where components are given their dependencies through their constructors, methods, or directly
into fields."
Introducción a Symfony2. Inyección de dependencias
class UserController{ private $em;
public function __construct() { $this->em = new EntityManager(); }}
$userController = new UserController();
Introducción a Symfony2. Inyección de dependencias
class UserController{ private $em;
public function __construct() { $this->em = new EntityManager(‘conn2’); }}
$userController = new UserController();
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);
Introducción a Symfony2. Inyección de dependencias
DIC: Contenedor de Inyección de Dependencias
use Symfony\Component\DependencyInjection;use Symfony\Component\DependencyInjection\Reference; $sc = new DependencyInjection\ContainerBuilder();$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');
Introducción a Symfony2. Inyección de dependencias
Entornos de ejecución
Mismo código
Diferentes configuraciones
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
Introducción a Symfony2. Entornos de ejecución
Instalación y configuración
$ composer create-project symfony/framework-standard-edition path/ 2.1.3
Introducción a Symfony2. Instalación y configuración
Introducción a Symfony2. Instalación y configuración
Documentación
Introducción a Symfony2. Documentación
Primeras páginas
Bundles
Todo el contenido de nuestra aplicaciónestará dentro de uno o más bundles
Primeras páginas. Bundles
Primeras páginas. Bundles
$ php app/console generate:bundle
Primeras páginas. Bundles
// app/AppKernel.php
class AppKernel extends Kernel{ public function registerBundles() { $bundles = array( new Symfony\Bundle\FrameworkBundle\FrameworkBundle(), new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Symfony\Bundle\TwigBundle\TwigBundle(), new Symfony\Bundle\MonologBundle\MonologBundle(), new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(), new Symfony\Bundle\AsseticBundle\AsseticBundle(), new Doctrine\Bundle\DoctrineBundle\DoctrineBundle(), ... );
if (in_array($this->getEnvironment(), array('dev', 'test'))) { $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle(); }
return $bundles; }}
Primeras páginas. Bundles
Routing
Primeras páginas. Routing
/user/profile/raulfraile
Controlador: UserControllerAcción: showProfile($username)
Primeras páginas. Routing
# routing.ymluser_profile: pattern: /user/profile/{username} defaults: { _controller: DemoBundle:User:profile }
Primeras páginas. Routing
# routing.ymlusers_list: pattern: /user/list/{page} defaults: { _controller: DemoBundle:User:list, page: 1 }
Primeras páginas. Routing
# routing.ymlusers_list: pattern: /user/list/{page} defaults: { _controller: DemoBundle:User:list, page: 1 } requirements: page: \d+
Primeras páginas. Routing
# routing.ymlusers_list: pattern: /user/list/{page}{subpage} defaults: { _controller: DemoBundle:User:list, page: 1 } requirements: page: \d+ _method: GET
Primeras páginas. Routing
# user.ymluser_profile: pattern: /profile/{username} defaults: { _controller: DemoBundle:User:profile }
# routing.ymluser_routing: resource: "@DemoBundle/Resources/config/user.yml" prefix: /user
Primeras páginas. Routing
$ php app/console router:debug [route_name]
Primeras páginas. Routing
$ php app/console router:match path
Controladores
Request Response
Primeras páginas. Controladores
use Symfony\Component\HttpFoundation\Response;
class UserController{ public function helloAction() { return new Response('Hello world!'); }}
Primeras páginas. Controladores
use Symfony\Component\HttpFoundation\Response;
class UserController{ public function helloAction($name) { return new Response('Hello '.$name.'!'); }}
Primeras páginas. Controladores
use Symfony\Component\HttpFoundation\Response;use Symfony\Bundle\FrameworkBundle\Controller\Controller;
class UserController extends Controller{ public function helloAction($name) { return new Response('Hello '.$name.'!'); }}
Primeras páginas. Controladores
public function indexAction(){ return $this->redirect($this->generateUrl('home'));}
Primeras páginas. Controladores
public function indexAction(){ throw $this->createNotFoundException('Not found');}
Primeras páginas. Controladores
Demo: Controladores + objeto Request
• Ejercicio. Crear todas las rutas del proyecto, con sus respectivos controladores:
Primeras páginas. 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
• 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
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
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);
Primeras páginas. Controladores
Twig
Sintaxis básica
{{ }}
{% %}
{# #}
Twig. Sintaxis básica
{{ name }}
Twig. Sintaxis básica - Variables
{{ user.name }}
{{ user[‘name’] }}
{% set foo = 'foo' %}
{% set foo = [1, 2] %}
{% set foo = {'foo': 'bar'} %}
Twig. Sintaxis básica - Variables
{{ name|upper }}
{{ name|striptags|nlbr }}
Twig. Sintaxis básica - Filtros
abscapitalize
convert_encodingdate
date_modifydefaultescapeformat
joinjson_encode
keyslengthlowermerge
nl2brnumber_format
rawreplacereverse
slicesortsplit
striptagstitletrim
upperurl_encode
Twig. Sintaxis básica - Filtros
{{ random(['rojo', 'azul', 'verde']) }}
{{ random('ABC') }}
{{ random() }}
{{ random(5) }}
Twig. Sintaxis básica - Funciones
{{ path('home') }}
<a href=”{{ path(‘user_profile’, {‘username’: user.username }) }}”>
Twig. Sintaxis básica - Funciones
{{ url('home') }}
{{ url(‘user_profile’, {‘username’: user.username }) }}
Twig. Sintaxis básica - Funciones
{% for user in users %} <li>{{ user.username }}</li>{% endfor %}
Twig. Sintaxis básica - Control de flujo
{% for user in users %} <li>{{ user.username }}</li>{% else %} <li>No hay usuarios</li>{% endfor %}
Twig. Sintaxis básica - Control de flujo
{% for user in users %} <li>{{ loop.index }} {{ user.username }}</li>
{% endfor %}
Twig. Sintaxis básica - Control de flujo
{% for user in users if user.active %} <li>{{ user.username }}</li>{% endfor %}
Twig. Sintaxis básica - Control de flujo
{% for key, user in users %} <li>{{ user.username }}</li>{% endfor %}
Twig. Sintaxis básica - Control de flujo
{% if user.active %} <p>Usuario activo</p>{% endif %}
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 %}
Twig. Sintaxis básica - Control de flujo
• 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());
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.
Twig. Sintaxis básica
Herencia
{% include %}
{% extends %}
{% render %}
Twig. Herencia
{# _footer.html.twig #}<footer>© {{ “now”|date(‘Y’) }}</footer>
{# home.html.twig #}<html> <head> <title>Ejemplo include</title> </head> <body> ... {% include ‘_footer.html.twig’ %} </body></html>
Twig. Herencia
{# _footer.html.twig #}<footer>© {{ “now”|date(‘Y’) }}</footer>
{# home.html.twig #}<html> <head> <title>Ejemplo include</title> </head> <body> ... {% include ‘DemoBundle:web:_footer.html.twig’ %} </body></html>
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 %}
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 %}
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 %}
Twig. Herencia
{# index.html.twig #}<html> <head> <title>Título</title> </head> <body> ... {% render "DemoBundle:Notifications:show" %} </body></html>
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.
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) }}
Twig. Herencia
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) }}
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) }}
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) }}
Twig. Macros
Extensiones
Oficiales: Twig Extensions Repository
Comunidad
Propias
Twig. Extensiones
Doctrine 2
ORM
ORM: Object Relational Mapper
Doctrine 2. ORM
Doctrine 2. ORM
Doctrine 2. ORM
MySQL
PDO
DBAL
ORM
SQL Server Oracle ...
DBAL
class UserController extends Controller{ public function indexAction() { $conn = $this->get('database_connection'); $users = $conn->fetchAll('SELECT * FROM users'); }}
Doctrine 2. DBAL
Entidades y relaciones
namespace Demo\DemoBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/** * @ORM\Table(name="category") * @ORM\Entity */class Category{ /** * @ORM\Column(name="id", type="integer") * @ORM\Id * @ORM\GeneratedValue(strategy="AUTO") */ protected $id;
/** * @var string $name * @ORM\Column(name="name", type="string", length=255) */ protected $name;}
Doctrine 2. Entidades y relaciones
/** * @ORM\Column(name="twitter", type="string", length=15, nullable=true) */protected $twitter;
/** * @ORM\Column(name="bio", type="text") */protected $bio;
/** * @ORM\Column(name="created_at", type="datetime") */protected $createdAt;
/** * @ORM\Column(name="languages", type="array") */private $languages;
Doctrine 2. Entidades y relaciones
// Relación 1:N unidireccional
// User.php
/** * @var City $city * * @ORM\ManyToOne(targetEntity="City") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="city_id", referencedColumnName="id") * }) */private $city;
Doctrine 2. Entidades y relaciones
// Relación 1:N unidireccional
// User.php
/** * @var City $city * * @ORM\ManyToOne(targetEntity="City") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="city_id", referencedColumnName="id") * }) */private $city;
Doctrine 2. Entidades y relaciones
// Relación 1:N bidireccional
// Post.php
/** * @var User $user * * @ORM\ManyToOne(targetEntity="User", inversedBy="posts") * @ORM\JoinColumns({ * @ORM\JoinColumn(name="user_id", referencedColumnName="id") * }) */protected $user;
// User.php
/** * @var $posts PersistentCollection * * @ORM\OneToMany(targetEntity="Post", mappedBy="user", cascade={"all"}) */protected $posts;
Doctrine 2. Entidades y relaciones
// Relación N:M bidireccional
// Post.php/** * @var $categories PersistentCollection * * @ORM\ManyToMany(targetEntity="Category", inversedBy="posts") * @ORM\JoinTable(name="post_category") */protected $categories;
// Category.php/** * @var $secrets PersistentCollection * * @ORM\ManyToMany(targetEntity="Post", mappedBy="categories") */protected $posts;
Doctrine 2. Entidades y relaciones
Doctrine 2. Entidades y relaciones
$ php app/console doctrine:generate:entity
• Ejercicio. Crear todas las entidades necesarias para la siguiente base de datos:
Doctrine 2. Entidades y relaciones
Configuración
# parameters.yml
parameters: database_driver: pdo_mysql database_host: localhost database_port: ~ database_name: db_name database_user: db_user database_password: db_pass
Doctrine 2. Configuración
Doctrine 2. Configuración
$ php app/console doctrine:schema:update
--dump-sql--force
• 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;
Doctrine 2. Fixtures
Fixtures
{ "require": { "doctrine/doctrine-fixtures-bundle": "dev-master" }}
Doctrine 2. Fixtures
// app/AppKernel.phppublic function registerBundles(){ $bundles = array( // ... new Doctrine\Bundle\FixturesBundle\DoctrineFixturesBundle() );}
Doctrine 2. Fixtures
// Demo/DemoBundle/DataFixtures/ORM/LoadUserData.php
namespace Demo\DemoBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\FixtureInterface;use Doctrine\Common\Persistence\ObjectManager;use Demo\DemoBundle\Entity\User;
class LoadUserData implements FixtureInterface{ public function load(ObjectManager $manager) { $user = new User(); $user->setUsername('raulfraile');
$manager->persist($user); $manager->flush(); }}
Doctrine 2. Fixtures// Demo/DemoBundle/DataFixtures/ORM/LoadUserData.php
namespace Demo\DemoBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\DataFixtures\OrderedFixtureInterface;use Doctrine\Common\Persistence\ObjectManager;use Demo\DemoBundle\Entity\User;
class LoadUserData extends AbstractFixture implements OrderedFixtureInterface{ public function getOrder() { return 1; }
public function load(ObjectManager $manager) { // ... $this->addReference('user_' . $user->getUsername(), $user); }}
Doctrine 2. Fixtures// Demo/DemoBundle/DataFixtures/ORM/LoadBookData.php
namespace Demo\DemoBundle\DataFixtures\ORM;
use Doctrine\Common\DataFixtures\AbstractFixture;use Doctrine\Common\DataFixtures\OrderedFixtureInterface;use Doctrine\Common\Persistence\ObjectManager;use Demo\DemoBundle\Entity\Book;
class LoadBookData extends AbstractFixture implements OrderedFixtureInterface{ public function getOrder() { return 2; }
public function load(ObjectManager $manager) { // ... $book->setUser($this->getReference('user_'.$data[‘slug’])); }}
Doctrine 2. Fixtures
$ php app/console doctrine:fixtures:load
--purge-with-truncate
Buenas prácticas
users.yml
books.yml
authors.yml
technologies.yml
Doctrine 2. Fixtures
LoadUserData.php
LoadBookData.php
LoadAuthorData.php
LoadTechnologyData.php
CONST FIXTURE_REF = ‘user’
CONST FIXTURE_REF = ‘books’
CONST FIXTURE_REF = ‘author’
CONST FIXTURE_REF = ‘technology’
• Ejercicio. Crear datos de prueba (incluidas imágenes) en archivos yml, además de los correspondientes scripts para cargar los fixtures.
Doctrine 2. Fixtures
nelmio/alice
• Ejercicio. Rehacer los fixtures para utilizar nelmio/alice y además generar usuarios de prueba utilizando fzaninotto/faker.
Doctrine 2. Fixtures
Repositorios
Doctrine 2. Repositorios
// Demo/DemoBundle/Entity/User.php
/** * @ORM\Entity(repositoryClass="Demo\DemoBundle\Entity\UserRepository") */class User{ //...}
Doctrine 2. Repositorios
// Demo/DemoBundle/Entity/UserRepository.php
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository{ public function findAllActive() { return $this->getEntityManager() ->createQuery('SELECT u FROM DemoBundle:User u WHERE u.active = TRUE') ->getResult(); }}
Doctrine 2. Repositorios
// Demo/DemoBundle/Entity/UserRepository.php
use Doctrine\ORM\EntityRepository;
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(); }}
Doctrine 2. Repositorios
// Demo/DemoBundle/Entity/UserRepository.php use Doctrine\ORM\EntityRepository; 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(); }}
Doctrine 2. Repositorios// Demo/DemoBundle/Entity/UserRepository.php
use Doctrine\ORM\EntityRepository;
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 (\Doctrine\ORM\NoResultException $e) { return 0; }
}}
Doctrine 2. Repositorios// Demo/DemoBundle/Entity/UserRepository.php use Doctrine\ORM\EntityRepository; 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 (\Doctrine\ORM\NoResultException $e) { return false; } }}
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 }}
• 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
Doctrine 2. Repositorios
Frontend
Gestión de assets
{{ asset('/bundles/books/images/logo.png') }}
Frontend. Gestión de assets
Frontend. Gestión de assets
framework: ... templating: engines: ['twig'] assets_version: v0.2
Frontend. Gestión de assets
http://ejemplo.com/bundles/books/images/logo.png?v0.2
• 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.
Frontend. Gestión de assets
php app/console assets:install web/
Assetic
Frontend. Assetic
“Assetic is an asset management framework for PHP, based on the Python webassets library.”
Frontend. Assetic
reset.css
bootstrap.css
global.css
Assets Filtros
styles.css
Frontend. Assetic
CoffeeScriptFilterCssEmbedFilterCssImportFilterCssMinFilterCssRewriteFilterGoogleClosure\CompilerApiFilterGoogleClosure\CompilerJarFilterHandlebarsFilterJpegoptimFilterJpegtranFilterLessFilter
LessphpFilterOptiPngFilterPackerFilterPngoutFilterCompassFilterSass\SassFilterSass\ScssFilterSprocketsFilterStylusFilterYui\CssCompressorFilterYui\JsCompressorFilter
Frontend. Assetic
{% javascripts '@BooksBundle/Resources/public/js/*'%} <script type="text/javascript" src="{{ asset_url }}"> </script>{% endjavascripts %}
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 %}
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 %}
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 %}
Frontend. Assetic
$ php app/console assetic:dump --env=prod
--watch
• 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
Frontend. Assetic
Tratamiento de imágenes
Frontend. Tratamiento de imágenes
AvalancheImagineBundle
Imagine
GD2
Imagick
Gmagick
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');
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');
Frontend. Tratamiento de imágenes
// Imagine resize
$width = 80;$height = 80;
$imagine = new Imagine\Gd\Imagine();$imagine = new Imagine\Imagick\Imagine();
$imagine->open('image.png') ->resize(new Imagine\Box($width, $height)) ->save('image_resized.png');
Frontend. Tratamiento de imágenes
http://www.slideshare.net/avalanche123/introduction-toimagine
Frontend. Tratamiento de imágenes
¿Cómo usarlo en Symfony para hacer thumbnails de imágenes subidas por los usuarios?
Frontend. Tratamiento de imágenes
# app/config.yml
avalanche_imagine: filters: user_small: type: thumbnail options: { size: [30, 30], mode: outbound } user_medium: type: thumbnail options: { size: [60, 60], mode: outbound } user_big: type: thumbnail options: { size: [90, 90], mode: outbound }
Frontend. Tratamiento de imágenes
<img src="{{ user.webPath|apply_filter('user_small') }}" />
Demo: AvalancheImagineBundle + Upload Doctrine
Formatos alternativos
Frontend. Formatos alternativos
# routing.yml
api_books_latest: pattern: /api/latest.{_format} defaults: { _controller: BooksBundle:Api:latest }
Frontend. Formatos alternativos
# routing.yml
api_books_latest: pattern: /api/latest.{_format} defaults: { _controller: BooksBundle:Api:latest, _format: html }
Frontend. Formatos alternativos
public function latestAction(){ $format = $this->getRequest()->getRequestFormat();}
• 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.
Frontend. Formatos alternativos
Backend
Formularios
Backend. Formulariosnamespace Books\BooksBundle\Form;
use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilderInterface;use Symfony\Component\OptionsResolver\OptionsResolverInterface;
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' => 'Books\BooksBundle\Entity\User' )); }
public function getName() { return 'form_user'; }}
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(), ));}
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>
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>
Backend. Formularios
namespace Books\BooksBundle\Form;
use Symfony\Component\Form\AbstractType;use Symfony\Component\Form\FormBuilderInterface;use Symfony\Component\OptionsResolver\OptionsResolverInterface;
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'); }
...}
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
Backend. Formularios
http://symfony.com/doc/current/reference/forms/types.html
• 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).
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
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
Backend. Formularios
Backend. Formularios
Generación de CRUDs automáticos
Create Read Update Delete
Backend. Formularios
Backend. Formularios
$ php app/console generate:doctrine:crud
• 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.
Backend. Formularios
Validación
Backend. Validación
/** * @var string $title * * @Assert\NotBlank * @Assert\MaxLength(250) * @ORM\Column(name="title", type="string", length=250) */protected $title;
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(), ));}
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>
Backend. Validación
NotBlank Blank NotNullNull TRUE FALSEType Email MinLength
MaxLength Length UrlRegex Ip MaxMin Range Date
DateTime Time ChoiceCollection Count UniqueEntityLanguage Locale Country
File Image CallbackAll UserPassword Valid
Constraints
Backend. Validación
http://symfony.com/doc/current/reference/constraints.html
• 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
Backend. Validación
Seguridad
Autenticación VS Autorización
Seguridad. Autenticación VS Autorización
Firewall Control de acceso
Seguridad. Autenticación VS Autorización
Seguridad. Autenticación VS Autorización
Seguridad. Autenticación VS Autorización
Seguridad. Autenticación VS Autorización
Roles
Seguridad. Roles
Usuario
ROLE_USER
ROLE_ADMIN
ROLE_TRANSLATOR
Seguridad. Roles
Usuario
ROLE_USER
ROLE_ADMIN
ROLE_TRANSLATOR
Configuración
Seguridad. Configuraciónsecurity: encoders: Symfony\Component\Security\Core\User\User: 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 }
Seguridad. Configuración
security: encoders: Empresa\BooksBundle\Entity\User: sha512 role_hierarchy: ROLE_ADMIN: ROLE_USER ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH] providers: main: entity: { class: Empresa\BooksBundle\Entity\User, property: username } firewalls: dev: pattern: ^/(_(profiler|wdt)|css|images|js)/ 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 }
Symfony\Component\Security\Core\User\UserInterface
getRoles()getPassword()
getSalt()getUsername()
eraseCredentials()
Login
Seguridad. Login
# app/config/routing.ymllogin: pattern: /login defaults: { _controller: EmpresaBooksBundle:Security:login }
login_check: pattern: /login_checklogout: pattern: /logout
Seguridad. Login
// src/Empresa/BooksBundle/Controller/SecurityController.php;namespace Empresa\BooksBundle\Controller; use Symfony\Bundle\FrameworkBundle\Controller\Controller;use Symfony\Component\Security\Core\SecurityContext; 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, ) ); }}
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>
Servicios y eventos
Servicios
Demo: Servicios
Eventos
Demo: Eventos
Extendiendo SF2
Comandos personalizados
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()); } }}
• 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
Backend. Validación
Extensiones de Twig
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'; }}
Extendiendo Symfony2. Extensiones de Twig
# services.yml
books.twig.html_extension: class: Empresa\BooksBundle\Twig\HtmlExtension tags: - { name: twig.extension }
• 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.
Backend. Validación
Rendimiento
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)
Optimización y rendimiento
Cache
Optimización y rendimiento
APC
Optimización y rendimiento. APC
Byte Code Cache
Optimización y rendimiento. APC
Lee el archivo index.php y lo introduce en memoria
GET /index.php HTTP/1.1
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”
Optimización y rendimiento. APC
La primera vez se realizan los mismos pasos (lexer + parser) para generar los “opcodes”. Una vez generados
se guardan en memoria.
GET /index.php HTTP/1.1 (con APC)
Las siguientes veces utiliza los “opcodes” guardados en memoria y los ejecuta directamente
Optimización y rendimiento. APC
System/User cache
Optimización y rendimiento. APC
// app.php
$loader = require_once __DIR__.'/../app/bootstrap.php.cache';
$loader = new ApcClassLoader('books', $loader);$loader->register(true);
Optimización y rendimiento. APC
# app/config/config_prod.yml
doctrine: orm: metadata_cache_driver: apc result_cache_driver: apc query_cache_driver: apc
Composer
Optimización y rendimiento. Composer
$ composer dump-autoload --optimize
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.
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.
Extra. Seguridad
• Ejercicio. Crear la página /myadmin, a la que solo se podrá acceder si el usuario logueado tiene el rol ROLE_ADMIN.
Extra. Seguridad
Doctrine 2. 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).
Extra. Doctrine2 Repositorios
• Ejercicio. Crear la página /sales, que mostrará los 5 libros más baratos.
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
Extra. Doctrine2 Repositorios
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
Extra. Formularios