Archives de catégorie : Symfony

Symfony2 – Doctrine Sortable Behaviour

Inclure le bundle sf2 de stof dans son composer

Commencer par ajouter cette petite ligne dans votre composer :

"require": {
...
"stof/doctrine-extensions-bundle": "dev-master",
...

Activation du bundle dans le fichier AppKernel

Composer update , puis rajouter ce nouveau bundle dans l’AppKernel :

<?php

use Symfony\Component\HttpKernel\Kernel;
use Symfony\Component\Config\Loader\LoaderInterface;

class AppKernel extends Kernel
{
  public function registerBundles()
  {
    $bundles = array(
      ...,
      new Stof\DoctrineExtensionsBundle\StofDoctrineExtensionsBundle(),
      ...,
    );
    // ...

    return $bundles;
  }
}
&#91;/sourcecode&#93;

<h2>Ajout des annotations</h2>

Ensuite, on ajoute les annotations qui vont bien :


<?php
namespace Entity;

use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Table(name="items")
 * @ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
 */
class Item
{
    /** @ORM\Id @ORM\GeneratedValue @ORM\Column(type="integer") */
    private $id;

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

    /**
     * @Gedmo\SortablePosition
     * @ORM\Column(name="position", type="integer")
     */
    private $position;

    /**
     * @Gedmo\SortableGroup
     * @ORM\Column(name="category", type="string", length=128)
     */
    private $category;

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setPosition($position)
    {
        $this->position = $position;
    }

    public function getPosition()
    {
        return $this->position;
    }

    public function setCategory($category)
    {
        $this->category = $category;
    }

    public function getCategory()
    {
        return $this->category;
    }
    
    // ...
}

Ajout du listener

Et pour que tout cela fonctionne correctement, ne pas oublier d’ajouter l’event subscriber à notre event manager dans la méthode Boot de notre bundle.
Sinon, la mise à jour du champ position n’est jamais faite…
Si vous avez oublié de surcharger la méthode boot de votre bundle et que vous avez tenté d’utiliser la classe SortableRepository, une belle exception devrait vous avoir mis la puce à l’oreille :

This repository can be attached only to ORM sortable listener

<?php

namespace Acme\AcmeBundle;

use Symfony\Component\HttpKernel\Bundle\Bundle;

class AcmeAcmeBundle extends Bundle
{
  public function boot()
  {
    // get the doctrine 2 entity manager
    $em = $this->container->get('doctrine.orm.default_entity_manager');

    // get the event manager
    $evm = $em->getEventManager();
    $evm->addEventSubscriber(new \Gedmo\Sortable\SortableListener);
  }
}

Pour des exemples d’utilisation, RDV ici : http://gediminasm.org/article/sortable-behavior-extension-for-doctrine2

Symfony2 – Génération dynamique de formulaire en fonction de l’entitée liée

La génération dynamique de formulaires est assez peu documentée sur le site de symfony2.
Une entrée dans le cookbook nous permet quand même de se mettre sur la bonne voie. Je vous invite à la lire à cette adresse.

Attention, la toute prochaine version 2.1 de symfony introduit des changements notables dans le framework de formulaire. Une rétrocompatibilité temporaire est assurée avec la version 2.0 mais plusieurs méthodes sont aujourd’hui marquées deprecated. Par exemple, la méthode getDefaultsOptions est désormais obsolète et on doit surcharger setDefaultOptions qui prend en paramètre un objet de type OptionsResolverInterface.

J’utilise personnellement la version 2.1 du master et les extraits de codes ne sont pas tous exploitables tel quel sur une version 2.0 stable.

Le besoin

L’objectif est de pouvoir construire un formulaire et d’y ajouter ou non un champ en fonction de l’entitée qui est passée au moment de l’instanciation du formulaire.

Par exemple, si on construit un formulaire d’édition d’une entité catégorie, on veut pouvoir ajouter un champ entity pour nous permettant de lister les catégories parentes sélectionnables pour la catégorie en cours d’édition. Par contre, cette liste ne doit pas être affichée si notre entitée n’a pas de catégorie parente (si elle est à la racine de notre arbo par exemple).

On veut également pouvoir filtrer les catégories qui s’affichent dans notre liste, de manière à n’afficher que les catégories qui ont un niveau hiérarchiques supérieur à la catégorie en cours d’édition, histoire de ne pas affecter à notre catégorie un parent lui même enfant de notre catégorie.

Implémentation

La solution à ce type de problématique est de passer par l’ajout d’un event listener au sein de la méthode buildForm(...). C’est ce listener qui va nous permettre de récupérer l’entitée injectée dans notre formulaire via le controller.

// src/Acme/AcmeBundle/Form/Type/CategoryType.php
$builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($builder) {
  /** @var Category $category */
  $category = $event->getData();
  // Si on n'a pas accès à l'entitée liée ou si notre entitée n'a pas de catégorie parente, on n'ajoute pas d'autre champ.
  if (!$category instanceof Category || !$category->getParent()) {
      return;
  }
  // On ajoute un nouveau champ de type entity pour sélectionner la catégorie parente.
  $event->getForm()->add($builder->getFormFactory()->createNamed('parent', 'entity', null, array(
    'class' => 'AcmeAcmeBundleBundle:Category',
    'required' => false,
    'query_builder' => function(EntityRepository $er) use ($category) {
      return $er->createQueryBuilder('c')
        ->where('c.level <= :level') // On ne veut récupérer que les catégories qui sont au dessus de celle en cours d'édition.
        ->setParameter('level', $category->getParent()->getLevel());
    }
  )));
});

Ci-dessous, le code source complet de ma classe de gestion du formulaire d’édition de catégorie :

// src/Acme/AcmeBundle/Form/Type/CategoryType.php
namespace Acme\AcmeBundle\Form\Type;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormEvents;
use Symfony\Component\Form\FormEvent;
use Symfony\Component\Form\FormBuilderInterface;
use Doctrine\ORM\EntityRepository;
use Symfony\Component\OptionsResolver\OptionsResolverInterface;

use Acme\AcmeBundle\Entity\Category;

class CategoryType extends AbstractType
{
  /**
   * @param \Symfony\Component\OptionsResolver\OptionsResolverInterface $resolver
   */
  public function setDefaultOptions(OptionsResolverInterface $resolver)
  {
    $resolver->setDefaults(array(
      'data_class' => 'Acme\AcmeBundle\Entity\Category',
    ));
  }

  /**
   * @param \Symfony\Component\Form\FormBuilderInterface $builder
   * @param array $options
   */
  public function buildForm(FormBuilderInterface $builder, array $options)
  {
    $builder->add('title', 'text');
    $builder->addEventListener(FormEvents::PRE_SET_DATA, function(FormEvent $event) use ($builder) {
      /** @var Category $category */
      $category = $event->getData();
      // Si on n'a pas accès à l'entitée liée ou si notre entitée n'a pas de catégorie parente, on n'ajoute pas d'autre champ.
      if (!$category instanceof Category || !$category->getParent()) {
          return;
      }
      // On ajoute un nouveau champ de type entity pour sélectionner la catégorie parente.
      $event->getForm()->add($builder->getFormFactory()->createNamed('parent', 'entity', null, array(
        'class' => 'AcmeAcmeBundleBundle:Category',
        'required' => false,
        'query_builder' => function(EntityRepository $er) use ($category) {
          return $er->createQueryBuilder('c')
            ->where('c.level <= :level') // On ne veut récupérer que les catégories qui sont au dessus de celle en cours d'édition.
            ->setParameter('level', $category->getParent()->getLevel());
        }
      )));
    });
  }

  /**
   * @return string
   */
  public function getName()
  {
    return 'category';
  }
}

Attention, la fonction anonyme utilisée au sein de la méthode addEventListener prend en entrée un FormEvent et non pas un DataEvent comme dans la version 2.0 de Symfony.

En espérant que cela puisse être utile à certains…

sfForms et valeurs par défaut. Pourquoi est-ce que getDefault me renvoie null ?

Les formulaires de symfony ont quelquefois des comportements surprenants pour le développeur. Tout le monde pense immédiatement aux embed forms mais ce n’est pas de ces derniers dont je vais parler ici.

Je suis tombé sur le problème suivant : pourquoi après avoir passé une valeur par défaut à un des widgets de mon formulaire, je ne parviens pas à la récupérer dans mon action ou dans mon template ? L’appel à la méthode getDefault du sfForm me renvoie null, tout simplement.

Cas typique :


// Dans le code de mon formulaire :
$this->setWidgets(array(
  ...
  'my_field' => new sfMyWidget(array('default' => 'my_value')),
));

// Dans le code de mon action :
if ($my_form->getDefault('my_field') == 'some_value')
{
  ...
}

Dans ce cas, la méthode getDefault() appelée sur mon formulaire me renvoie null alors que j’ai explicitement donné une valeur par défaut à mon widget. Pourtant, en lisant la doc de symfony sur le sujet je retiens ceci :

The setDefault(), getDefault(), setDefaults(), and getDefaults() methods manages the default values for the embedded widgets. They are proxy methods for the getDefault() and setDefault() widget methods.

Les méthodes setDefault() et getDefault() au niveau de la classe sfForm sont censées êtres des raccourcis pour les méthodes setDefault() et getDefault() de la classe sfWidgetForm().

En fait il s’avère que ce n’est absolument pas le cas et que la méthode getDefault() du formulaire ne fait jamais appel à la méthode getDefault() du widget associé.

L’appel à $my_form->getDefault(‘my_field’) n’est donc pas équivalent à $my_form[‘my_field’]->getWidget()->getDefault().

Par conséquent, il est important de bien comprendre quelle méthode appeler et dans quel cas.

A savoir, $my_form->getDefault(‘my_field’) quand le setDefault a été utilisé au niveau du formulaire. Exemple :


// Valeurs passées dans mon formulaire :
$this->setWidgets(array(
  ...
  'my_field' => new sfMyWidget(),
));
$this->setDefault('my_field', 'my_value');

// Ou alors valeurs passées dans mon action :
$my_form = new myFormClass(array(
  ...
  'my_field' => 'my_value',
));

Et, $my_form[‘my_field’]->getWidget()->getDefault() quand le setDefault() a été utilisé au niveau du widget. Exemple :


// Dans le code de mon formulaire :
$this->setWidgets(array(
  ...
  'my_field' => new sfMyWidget(array('default' => 'my_value')),
));

Voilà, désormais vous êtes prévenus…

Les ressources utiles du mois de Juillet

C’est une pratique assez récurrente chez les développeurs Web ou chez les web designers qui bloguent. Alors moi aussi je vais régulièrement (chaque mois) poster un article regroupant les ressources utiles sur lesquelles je suis tombé. Deux intérêts à cette démarche :

  • D’une part pouvoir les retrouver le jour où j’en aurai besoin / re-besoin
  • D’autre part, les partager avec vous amis lecteurs

Je crée donc pour l’occasion une nouvelle catégorie d’articles sur OvernetCity intitulée sobrement : « Ressources Utiles ».

Pour ce mois de Juillet, un peu de jQuery, un peu de symfony et un peu de développement iPhone sont au programme. D’ailleurs, cela risque d’être souvent le cas…

Interactive Photo Desk with jQuery and CSS3

Les démonstrations concernant CSS 3 et HTML 5 ont le vent en poupe depuis quelques mois. Il ne se passe pas une journée sans qu’un tuto complet soit posté sur un des nombreux blogs que je suis, montrant à quel point l’arrivée de ces deux langages et leur support par l’ensemble des navigateurs modernes va changer notre façon d’interagir avec une application Web.

Ici, c’est l’alliance du CSS 3 et de jQuery qui permet de réaliser un chouette bureau sur lequel s’entassent quelques photos. L’utilisateur peut les déplacer avec un résultat d’une fluidité étonnante.

A consulter avec un navigateur moderne évidemment.

http://tympanus.net/codrops/2010/07/01/interactive-photo-desk/

Minimalistic Slideshow Gallery with jQuery

Encore un tuto expliquant comment réaliser une mini gallerie pour afficher quelques photos. Celui-ci a le mérite d’être très clair et d’avoir un rendu vraiment pro.

Très facile à intégrer au sein d’une page existante !

http://tympanus.net/codrops/2010/07/05/minimalistic-slideshow-gallery/

Symfony 1.4 – Tri sur les colonnes étrangères dans l’admin generator

L’admin generator de symfony est un outil merveilleusement pratique qui permet de créer en quelques minutes un backend parfaitement fonctionnel et adapté aux besoins du client.

Néanmoins, je me suis souvent demandé pourquoi il est impossible sans mettre les mains dans le cambouis de faire un tri sur les colonnes persos dans la liste générée par le Framework. Chaque colonne est par défaut cliquable ce qui entraine un tri ascendant ou descendant de la liste en fonction de cette colonne mais ce comportement est indisponible dès que la colonne en question n’est pas une colonne native en base et fait, par exemple, référence à l’attribut name d’un objet récupéré via une relation.

Les développeurs experts symfony de l’agence Elao nous montrent comment modifier le comportement de l’admin generator pour que, justement, ce tri soit possible sur les colonnes non natives. Très utile !

http://www.elao.org/symfony/symfony-1-4-admin-generator-tris-sur-les-colonnes-etrangeres.html

Introduction aux Métadonnées RDFa, Microdata et Microformats

Je connais la technique depuis un moment mais n’ai pas encore eu l’occasion de la mettre en pratique au sein d’un projet. Ces métadonnées à destination des moteurs de recherche permettent de stocker de modifier la présentation d’une page au sein d’un moteur de recherche pour apporter de la valeur ajoutée à l’utilisateur. Les sites référençant des restaurants, par exemple, peuvent profiter de ces métadonnées pour afficher, directement au sein des résultats d’un moteur de recherche, la note associée à un restaurant sous forme d’étoiles.

Ces données sont de plus en plus utilisées et de mieux en mieux exploitées. Elles méritent donc qu’on s’y attarde un tout petit peu de manière à optimiser le référencement naturel d’une application web.

Cet article présente quelques exemples concrets d’utilisation de ces métadonnées et référence quelques liens utiles pour le développeur.

http://spyrestudios.com/real-world-microformats-rdfa-microformats-and-microdata-practical-examples/

Tutoriel complet sur les Microdata de HTML5

Quand on pense à l’HTML 5, on ne pense pas forcément immédiatement à Microdata. Et pourtant c’est également une des grandes nouveauté que ce nouveau langage nous apporte et à laquelle tout bon développeur se doit de s’intéresser. Ce tutoriel très complet permet d’aller plus loin que l’article précédent en se basant sur un exemple concret.

http://net.tutsplus.com/tutorials/html-css-techniques/html5-microdata-welcome-to-the-machine/

Comment intégrer Google Analytics dans son application iPhone en 7 minutes ?

Intégrer un script de tracking des visiteurs dans une application Web est aujourd’hui presque un réflexe pour le développeur. On peut faire la même chose pour une application lourde sur iPhone puisque l’API de Google est compatible avec plusieurs terminaux mobiles dont ce dernier.

Quel est l’intérêt ? Comment est-ce que ça marche ? Quels sont les pré-requis ? Autant de questions qui trouveront leurs réponses dans ce tutoriel très complet de iCodeBlog.

http://icodeblog.com/2010/04/22/how-to-integrate-google-analytics-tracking-into-your-apps-in-7-minutes/

Premiers pas avec iPhone Core Data

Ce tutoriel est une belle introduction au Framework Core Data de l’iPhone OS qui n’est pas très bien couvert par les livres que j’ai pu lire lors de ma formation au développement iPhone.

A parcourir pour comprendre quelles sont les possibilités offertes par ce Framework et surtout comment les mettre en oeuvre.

http://mobile.tutsplus.com/tutorials/iphone/iphone-core-data/

Afficher un flux RSS en temps réel avec XMPP

Comment fait Google avec son GTalk pour afficher les messages envoyés en temps réel sur sa page web, sans sacrifier les ressources serveurs avec des requêtes Ajax envoyée chaque seconde ?

C’est une question que je me suis souvent posé et à laquelle j’ai trouvé une réponse aujourd’hui grâce à un tutoriel très complet posté sur le site d’IBM. GMail, Gtalk, Google Wave font tous usage du langage XMPP dédié à la notification en quasi temps réel pour les applications web. A lire d’urgence si le sujet vous intéresse…

http://www.ibm.com/developerworks/xml/tutorials/x-realtimeXMPPtut/index.html