Symfony2

Introducción

By Fran Moreno / @franmomu

Fran Moreno

¿Qué es Symfony2?

Symfony2 is a reusable set of standalone, decoupled, and cohesive PHP components that solve common web development problems
Then, based on these components, Symfony2 is also a full-stack web framework

HTTP y MVC Framework

Tareas de un framework

  • Routing
  • Seguridad
  • Persistencia
  • Rendimiento
  • Logging
  • Formularios
  • Validación
  • ...

Filosofía

  • PHP5.3+
  • No reinventar la rueda
  • Buenas prácticas, inspirado en Django, Ruby on Rails, Spring
  • Proveer arquitectura de bajo nivel a otro proyectos
  • Rendimiento
  • Escalabilidad
  • HTTP caching, ESI
  • Documentación extensa

Symfony2 Application Flow

Comunidad

Proyectos desarrollados con Symfony2

ted

Lockerz

lockerz

Lockerz 19 millones de usuarios en Julio 2012

DragonCity

Lockerz

3 Millones de visitas al día - Imagen: dotmmo

YouPorn

Lockerz

200 millones de páginas vistas al día

Componentes

  • BrowserKit
  • ClassLoader
  • Config
  • Console
  • CssSelector
  • DependencyInjection
  • DomCrawler
  • EventDispatcher
  • Filesystem
  • Finder
  • Form
  • HttpFoundation
  • HttpKernel
  • Locale
  • OptionsResolver
  • Process
  • PropertyAccess
  • Routing
  • Security
  • Serializer
  • Stopwatch
  • Templating
  • Translation
  • Validator
  • Yaml

Finder

use Symfony\Component\Finder\Finder;

$finder = new Finder();

$iterator = $finder
  ->files()
  ->name('*.php')
  ->depth(0)
  ->size('>= 1K')
  ->in(__DIR__);

foreach ($iterator as $file) {
    print $file->getRealpath()."n";
}
$s3 = new Zend_Service_Amazon_S3($key, $secret);
$s3->registerStreamWrapper("s3");

$finder = new Finder();
$finder->name('photos*')->size('< 100K')->date('since 1 hour ago');
foreach ($finder->in('s3://bucket-name') as $file) {
    print $file->getFilename()."n";
}

Dependency Injection

  • Estandariza y centraliza la forma en la que se construyen los objetos en tu aplicación.
  • Reutilización de código
  • Lazy loading
  • Contenedor de servicios
<services>

  <service id="betabeers.entity.user_manager"
           class="Foo\BetaBeersBundle\Entity\UserManager">
    <argument type="service" id="doctrine" />
    <argument type="service" id="betabeers.mailer.manager" />
  </service>

</services>
$userManager = $container->get('betabeers.entity.user_manager');

Proyectos que usan componentes de Symfony2

Framework Symfony2

Bundles

A bundle is a directory that has a well-defined structure and can host anything from classes to controllers and web resources.

Ecosistema

  • Doctrine/Propel
  • Twig
  • Monolog
  • Assetic
  • SwiftMailer

Composer

# Descarga composer.phar
$ curl -s https://getcomposer.org/installer | php
{
    "require": {
        "symfony/framework-standard-edition": "2.1.7",
        "liip/imagine-bundle": "dev-master",
    }
}
$ php composer.phar install

Doctrine

  • Doctrine ORM
  • Entities - POPOs
  • Mapping (anotaciones, XML, Yaml)
  • DQL

Entities

use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="bb_user")
 * @ORM\Entity()
 */
class User
{
    /**
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @ORM\Column(name="first_name", type="string", length=50)
     */
    private $firstname;

    /**
     * @ORM\OneToMany(targetEntity="Foo\BarBundle\Entity\Address",
            mappedBy="user")
     */
    private $addresses;
}

Entity Manager

$em = $container->get('doctrine')->getManager();

for($i = 0; $i < 1000; $i++) {
  $user = new User();
  $user->setFirstname($names[$i]);
  //...
  $em->persist($user);
}

$em->flush();

DQL

$dql = "SELECT u
        FROM FooBarBundle:User u
        LEFT JOIN u.addresses a
        WHERE a.country = :country";
$query = $em->createQuery($dql);
$query->setParameter("country", "Spain");

$spanishUsers = $query->getResult();

Twig

  • Motor de plantillas
  • Fast: Compila a PHP
  • Flexible: Fácil crear filtros, funciones y tags
  • Fácil de aprender
  • Herencia
  • Fuerza buenas prácticas

Sintaxis orientada a plantillas

{% for user in users %}
    * {{ user.name }}
{% else %}
    No user have been found.
{% endfor %}

Conciso

<?php echo $var ?>
<?php echo htmlspecialchars($var, ENT_QUOTES, 'UTF-8') ?>
{{ var }}
{{ var|e }}
<?php echo $user->isEnabled() ?>
{{ user.enabled }}

Filtros

{# Filtros #}
{% set names = ["pepe", "juan", "maría"] %}

{{ names|join(", ") }}                {# output: pepe, juan, maría #}
{{ names|sort|join("-") }}            {# output: juan-maría-pepe #}

{{ post.publishedAt|date('d-m-Y') }}  {# output: 21-02-2013 #}

Tags

{% macro input(name, value, type, size) %}
    <input type="{{ type|default('text') }}" name="{{ name }}"
      value="{{ value|e }}" size="{{ size|default(20) }}" />
{% endmacro %}

<p>{{ forms.input('username') }}</p>
Documentación Twig

Assetic

  • Framework de gestión de recursos web
  • Filtros (LESS, SASS, CoffeeScript)
  • Minimizar y combinar JS y CSS
  • Optimización de imágenes
{% javascripts '@AcmeFooBundle/Resources/public/js/*' filter='yui_js' %}
    <script type="text/javascript" src="{{ asset_url }}"></script>
{% endjavascripts %}
{% image '@AcmeFooBundle/Resources/public/images/example.jpg'
    filter='jpegoptim' output='/images/example.jpg' %}
    <img src="{{ asset_url }}" alt="Example"/>
{% endimage %}

Crear un proyecto en Symfony2

$ php composer.phar create-project
            symfony/framework-standard-edition betabeers/ 2.1.7
# Permisos
$ sudo chmod +a "www-data allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs
$ sudo chmod +a "`whoami` allow delete,write,append,file_inherit,directory_inherit" app/cache app/logs

Hello World!

Profiler

Estructura de directorios

├── app
│   ├── Resources
│   ├── cache
│   ├── config
│   │   ├── routing.yml
│   │   ├── security.yml
│   │   ├── config.yml
│   │   ├── parameters.yml
│   ├── logs
│   ├── AppKernel.php
│   └── console
├── src
├── vendor
├── web
│   ├── app.php
│   ├── app_dev.php
│   └── config.php
├── composer.json
└── composer.lock

Crear un bundle

$ php app/console generate:bundle
Bundle namespace: FranMoreno/BetabeersBundle
Bundle name [FranMorenoBetabeersBundle]: BetabeersBundle
Target directory [/Users/franmoreno/projects/betabeers/src]:
Configuration format (yml, xml, php, or annotation) [annotation]:
Do you want to generate the whole directory structure [no]? yes
Do you confirm generation [yes]?
Generating the bundle code: OK
Checking that the bundle is autoloaded: OK
Confirm automatic update of your Kernel [yes]?
Enabling the bundle inside the Kernel: OK
Confirm automatic update of the Routing [yes]?
Importing the bundle routing resource: OK
src/FranMoreno/BetabeersBundle
├── Controller
│   └── DefaultController.php
├── DependencyInjection
├── Resources
│   ├── config
│   │   └── services.xml
│   ├── doc
│   ├── public
│   │   ├── css
│   │   ├── images
│   │   └── js
│   ├── translations
│   └── views
│       └── Default
│           └── index.html.twig
├── Tests
└── BetabeersBundle.php

Entity

class User
{
  /**
   * @ORM\Column(name="id", type="integer")
   * @ORM\Id
   * @ORM\GeneratedValue(strategy="AUTO")
   */
  private $id;

  /**
   * @ORM\Column(name="firstname", type="string", length=50)
   * @Assert\NotBlank()
   */
  private $firstname;

  /**
   * @ORM\ManyToOne(targetEntity="FranMoreno\BetabeersBundle\Entity\Country")
   * @ORM\JoinColumn(name="coutry_id", referencedColumnName="id")
   * @Assert\NotNull()
   */
  private $country;

  /**
   * @ORM\OneToOne(targetEntity="FranMoreno\BetabeersBundle\Entity\Profile", mappedBy="user")
   */
  private $profile;

  /**
   * @ORM\OneToMany(targetEntity="FranMoreno\BetabeersBundle\Entity\Address", mappedBy="user")
   */
  private $addresses;
}

Formularios

class UserType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder
            ->add('firstname')
            ->add('country', 'entity', array(
                'class' => 'FranMoreno\BetabeersBundle\Entity\Country'
            ))
            ->add('profile', new ProfileType())
            ->add('addresses', 'collection', array(
                'type' => new AddressType(),
                'allow_add' => true,
                'allow_delete' => true,
                'by_reference' => false
            ))
        ;
    }

    public function setDefaultOptions(OptionsResolverInterface $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'FranMoreno\BetabeersBundle\Entity\User'
        ));
    }

    public function getName()
    {
        return 'user';
    }
}

Controladores

/**
 * @Route("/user")
 */
class UserController extends Controller
{
    /**
     * @Route("/new", name="user_new")
     * @Template()
     */
    public function newAction(Request $request)
    {
        $entity  = new User();
        $form = $this->createForm(new UserType(), $entity);
        if ($request->isMethod("POST")) {

          $form->bind($request);

          if ($form->isValid()) {
              $em = $this->getDoctrine()->getManager();
              $em->persist($entity);
              $em->flush();

              return $this->redirect($this->generateUrl('user_show', array(
                  'id' => $entity->getId()
              )));
          }
        }

        return array(
            'entity' => $entity,
            'form'   => $form->createView(),
        );
    }

Vista

{% extends '::base.html.twig' %}

{% block javascripts %}
  {{ parent() }}
  <script src="{{ asset('bundles/betabeers/js/validation.js') }}"></script>
{% endblock %}

{% block content %}
  <h1>User creation</h1>

  <form action="{{ path('user_new') }}" method="post" {{ form_enctype(form) }}>
    {{ form_widget(form) }}
    <p>
        <button type="submit">Create</button>
    </p>
  </form>

  <a href="{{ path('homepage') }}">Back to the list</a>
{% endblock %}

Resultado

Crear un CRUD en base a Entity

php app/console doctrine:generate:crud
The Entity shortcut name: BetabeersBundle:User
Do you want to generate the "write" actions [no]? yes
Configuration format (yml, xml, php, or annotation) [annotation]:
Routes prefix [/user]:
Do you confirm generation [yes]?
Generating the CRUD code: OK
Generating the Form code: OK
src/FranMoreno/BetabeersBundle
├── Controller
│   └── UserController.php
├── Entity
│   └── User.php
├── Form
│   └── UserType.php
├── Resources
│   └── views
│       └── User
│           ├── edit.html.twig
│           ├── index.html.twig
│           ├── new.html.twig
│           └── show.html.twig
└── Tests
    └── Controller
        └── UserControllerTest.php

Ventajas

  • Desarrollo rápido de aplicaciones
  • Rápido
  • Flexible
  • Extensible
  • Estable y sostenible
  • Testeable

Desventajas

  • Curva de aprendizaje
  • API aún no estable

Aprender Symfony2

Referencias

¿Preguntas?