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
9. Temario
• Tema 8: Seguridad
•
•
•
•
Autenticación VS Autorización
Roles
Configuración
Login
10. Temario
• Tema 9: Servicios y eventos
• Contenedor de inyección de
dependencias
• Eventos
11. Temario
• Tema 10: Extendiendo symfony2
•
•
Comandos de consola
Extensiones propias de Twig
12. Temario
• Tema 11: Optimización y rendimiento
•
•
•
Optimización de assets
APC
ESI
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.
19. Prerequisitos. PHP 5.3 / Namespaces
// ./index.php
<?php
namespace MyApp;
use LibAFoo;
$foo = new Foo();
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();
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.”
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
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”
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
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.
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. Introducción a Symfony2. Inyección de dependencias
class UserController
{
private $em;
public function __construct()
{
$this->em = new EntityManager();
}
}
$userController = new UserController();
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. 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);
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');
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;
}
}
101. Primeras páginas. Controladores
use SymfonyComponentHttpFoundationResponse;
class UserController
{
public function helloAction()
{
return new Response('Hello world!');
}
}
102. Primeras páginas. Controladores
use SymfonyComponentHttpFoundationResponse;
class UserController
{
public function helloAction($name)
{
return new Response('Hello '.$name.'!');
}
}
103. Primeras páginas. Controladores
use SymfonyComponentHttpFoundationResponse;
use SymfonyBundleFrameworkBundleController
Controller;
class UserController extends Controller
{
public function helloAction($name)
{
return new Response('Hello '.$name.'!');
}
}
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. 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. 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. 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);
121. Twig. Sintaxis básica - Control de flujo
{% for user in users %}
<li>{{ user.username }}</li>
{% endfor %}
122. Twig. Sintaxis básica - Control de flujo
{% for user in users %}
<li>{{ user.username }}</li>
{% else %}
<li>No hay usuarios</li>
{% endfor %}
123. Twig. Sintaxis básica - Control de flujo
{% for user in users %}
<li>{{ loop.index }} {{ user.username }}</li>
{% endfor %}
124. Twig. Sintaxis básica - Control de flujo
{% for user in users if user.active %}
<li>{{ user.username }}</li>
{% endfor %}
125. Twig. Sintaxis básica - Control de flujo
{% for key, user in users %}
<li>{{ user.username }}</li>
{% endfor %}
126. Twig. Sintaxis básica - Control de flujo
{% if user.active %}
<p>Usuario activo</p>
{% endif %}
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. 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. 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.
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. 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) }}
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) }}
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;
167. Doctrine 2. Fixtures
{
"require": {
"doctrine/doctrine-fixtures-bundle": "dev-master"
}
}
// app/AppKernel.php
public function registerBundles()
{
$bundles = array(
// ...
new DoctrineBundleFixturesBundle
DoctrineFixturesBundle()
);
}
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. 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. 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’]));
}
}
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.
176. Doctrine 2. Fixtures
•
Ejercicio. Rehacer los fixtures para utilizar nelmio/alice y
además generar usuarios de prueba utilizando fzaninotto/
faker.
178. Doctrine 2. Repositorios
// Demo/DemoBundle/Entity/User.php
/**
* @ORMEntity(repositoryClass="DemoDemoBundle
EntityUserRepository")
*/
class User
{
//...
}
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. 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. 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. 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;
}
}
}
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
190. Frontend. Gestión de assets
http://ejemplo.com/bundles/books/images/logo.png?v0.2
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.
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
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.
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. 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(),
));
}
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. 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. 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
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.
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
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,
)
);
}
}
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
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.
278. 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”
279. 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
287. 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.
288. 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.
291. 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).
293. 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
295. 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