Yohan Giarelli

Concepteur / Développeur PHP5 / Symfony.
Mangeur de bières.
Technophile.

  1. Introducing CalendR

    There is some boring and recurrent tasks in computer sciences. Date manipulations are. There is sometimes nice and interessant problematics, but usually, no.

    That’s why I developped CalendR.

    CalendR is a calendar management library based on PHP5.3+ date objects.

    Ok. By what can it do ?

    Well… In fact, very little things. But it’s enough to deal with usual needs.

    For the big picture, CalendR allows you to create time periods, an to iterate on it. That periods are classics calendar periods (day, week, month, year).

    So… Nothing that sounds like a revolution, but it gives you the ability to generate a calendar with this simple code :

    <?php
    $factory = new CalendR\Calendar;
    $month = $factory->getMonth(2012, 06);
    
    <table>
        <?php foreach ($month as $week): ?>
            <tr>
                <?php foreach ($week as $day): ?>
                    <td<?php $month->contains($day) or print ' class="out-of-month' ?>>
                        <?php echo $day->getBegin()->format('d') ?>
                    </td>
                <?php endforeach ?>
            </tr>
        <?php endforeach ?>
    </table>
    

    Could you find anything simplier ?

    That’s nice, but what can i do with this calendar without events ?

    Nothing. That’s why CalendR has a (simple) event management system.

    Now that our calendar is generated and displayed, let’s add some events to it. One more time, it’s really simple.

    $factory = new CalendR\Calendar;
    $month = $factory->getMonth(2012, 06);
    $events = $factory->getEvents($month);
    
    <table>
        <?php foreach ($month as $week): ?>
            <tr>
                <?php foreach ($week as $day): ?>
                    <td<?php $month->contains($day) or print ' class="out-of-month' ?>>
                        <?php echo $day->getBegin()->format('d') ?>
                        <?php if ($events->has($day)): ?>
                            <ul>
                                <?php foreach ($events->find($day) as $event): ?>
                                    <li><?php echo $event->getName() ?></li>
                                <?php endforeach ?>
                            </ul>
                        <?php endif ?>
                    </td>
                <?php endforeach ?>
            </tr>
        <?php endforeach ?>
    </table>
    

    How it works

    All the event stuff is based on 2 Interfaces

    • CalendR\Event\EventInterface
    • CalendR\Event\Provider\ProviderInterface

    Events

    In fact, you have to implement EventInterface on your event class(es). The methods to implements are :

    • getUid() : Returns an unique event identifier
    • getBegin() : Returns the event begin \DateTime instance
    • getEnd() : Returns the event end \DateTime instance
    • contains(\DateTime $date) : Returns true if $date is during the event
    • containsPeriod(PeriodInterface $period) : Returns if $period is during the event
    • isDuring(PeriodInterface $period) : Returns if $period contains the event

    But that’s a lot… So CalendR comes with a CalendR\Event\AbstractEvent class, that implement most of theeses methods.

    Providers

    Well, nice, we have defined our events. But how CalendR is supposed to retrieve them ? With a provider. The provider is a simple object providing events contained in a time period. It can be a Plain Old PHP Object, a Doctrine Repository, a Propel ActiveQuery or, why not, an object retrieving events over WebDAV.

    This time, that’s easy. The ProviderInterface needs only one method to be implemented :

    • getEvents(\DateTime $begin, \DateTime $end, array $options = array())

    This method must returns the array of event instances that are between $begin and $end, and related to the $options options ($options can be anything).

    Events returned by this method will be checked by CalendR and his EventManager, so you can make it simple. For a very small Provider working with a small set of events, you can return all your events. I will decrease performance for a big amount of events BTW.

    Next, you juste have to declare your Provider by adding it to the EventManager :

        $factory = new CalendR\Calendar;
        $factory->getEventManager()->addProvider(new AwesomeEventProvider);
    

    Ok, it can be usefuk someday. That’s all ?

    No. For Symfony2 fanboys, “There is a Bundle for that”. CalendR is fully integrated to Symfony2 via (FrequenceWebCalendRBundle)[https://github.com/frequence-web/FrequenceWebCalendRBundle], wich allows you to declare your providers as service and add them to CalendR via a simple calendr.event_provider tag.

    Some Twig functions are provided too, they’re useful for simple calendar rendering.

    About the library installation, CalendR is available on Packagist, thanks to composer. Package name is ‘yohang/calendr’.

    Final thought

    CalendR is still in developement. There is some things that I don’t like, like the event retrieving. So, if you’re interested in this project, don’t wait anymore, just send me a Pull Request, it’ll make me happy :).

    Repository : https://github.com/yohang/CalendR

  2. CalendR : Présentation

    S'il y a bien quelque chose de chiant, lourd et répetitif en développement, c'est les manipulation de dates. On peut parfois tomber sur des problèmatiques très spécifiques et/ou intéressantes, mais globalement, non.

    C'est suite à ce constat que j'ai développé CalendR.

    CalendR est une bibliothèque de gestion de calendriers basée sur les objets de dates de PHP5.3+.

    Mais ça fait quoi, au juste ?

    Concrètement, pas grand chose. Mais assez, bien assez, pour les problèmatiques classiques.

    En gros, CalendR vous permet de créer des instances de periodes de temps, et d'iterer dessus. Ces periodes sont des periodes calendaires classiques (Année/Mois/Semaine/Jour).

    Soit, rien de révolutionnaire, mais la possibilité de génerer un calendrier du mois simplement avec ce code :

    $factory = new CalendR\Calendar;
    $month = $factory->getMonth(2012, 06);
    
    <table>
        <?php foreach ($month as $week): ?>
            <tr>
                <?php foreach ($week as $day): ?>
                    <td<?php $month->contains($day) or print ' class="out-of-month' ?>>
                        <?php echo $day->getBegin()->format('d') ?>
                    </td>
                <?php endforeach ?>
            </tr>
        <?php endforeach ?>
    </table>
    

    Ce qui simplifie grandement la tâche, non ?

    Un calendrier sans évènements, c'est pas terrible…

    …c'est pourquoi CalendR intègre également une gestion (simplifiée) d'événements.

    Maintenant que notre calendrier est géneré, affiché, et certainement mis en forme de manière très raffinée (je vous fait confiance !), nous voulons y ajouter les évenements associés à nos periodes de temps.

    C'est assez simple :

    $factory = new CalendR\Calendar;
    $month = $factory->getMonth(2012, 06);
    $events = $factory->getEvents($month);
    
    <table>
        <?php foreach ($month as $week): ?>
            <tr>
                <?php foreach ($week as $day): ?>
                    <td<?php $month->contains($day) or print ' class="out-of-month' ?>>
                        <?php echo $day->getBegin()->format('d') ?>
                        <?php if ($events->has($day)): ?>
                            <ul>
                                <?php foreach ($events->find($day) as $event): ?>
                                    <li><?php echo $event->getName() ?></li>
                                <?php endforeach ?>
                            </ul>
                        <?php endif ?>
                    </td>
                <?php endforeach ?>
            </tr>
        <?php endforeach ?>
    </table>
    

    Comment ça marche ?

    Au niveau évènements, tout est basé sur 2 interfaces :

    • CalendR\Event\EventInterface
    • CalendR\Event\Provider\ProviderInterface

    Events

    En gros, vous devez implémenter EventInterface sur la (les) classes représentant vos évènements. Les methodes à implementer sont :

    • getUid() : Retourne un identifiant unique
    • getBegin() : Retourne le \DateTime de début
    • getEnd() : Retourne le \DateTime de fin
    • contains(\DateTime $date) : Retourne si $date est pendant l'évènement
    • containsPeriod(PeriodInterface $period) : Retourne si $period est pendant l'évenement
    • isDuring(PeriodInterface $period) : Retourne si $period contient l'évènement

    Mais ça fait beaucoup… CalendR met donc également à votre disposition une classe CalendR\Event\AbstractEvent qui implémente un bonne partie de ces méthodes.

    Providers

    Ok, nos évènements sont définis, mais comment diable CalendR peut-il les récuperer ? Grâce à un provider. Le provider est un simple objet fournissant les évènements compris dans une période. Ça peut être un repository Doctrine, une ActiveQuery Propel, ou même une classe récuperant du iCal en WebDAV.

    Cette fois c'est plus simple, l'interface ProviderInterface ne demande d'implémenter qu'une méthode :

    • getEvents(\DateTime $begin, \DateTime $end, array $options = array())

    Cette methodes doit renvoyer les instances d'évènements situés entre $begin et $end, et correspondant aux options $options.

    Notez que vous pouvez simplifier cette methodes, puisque les évènements fournis pas cette methodes seront vérifiés par CalendR. Cela signifie que pour un Provider très simple, travaillant sur de petits volumes de données, vous pouvez renvoyer tous les évènements systèmatiquement. C'est bien sur fortement déconseillé pour un nombre d'évènements important.

    Ne reste plus ensuite qu'a déclarer notre Provider en l'ajoutant à l'EventManager :

    $factory = new CalendR\Calendar;
    $factory->getEventManager()->addProvider(new AwesomeEventProvider);
    

    Ok, ça peut servir… D'autres infos ?

    Ouaip. Pour reprendre une citation qui commence à être connue dans la communauté Symfony, “There is a Bundle for that”. CalendR est complètement integré à Symfony2 via FrequenceWebCalendRBundle, ce qui vous permet de déclarer vos providers comme service et de les ajouter à CalendR via un simple tag calendr.event_provider.

    Des fonctions Twig sont égalements fournis, afin de simplifier le process.

    Concernant l'installation, CalendR est bien évidemment disponible sur Packagist, grâce au merveilleux composer, le nom du paquet est ‘yohang/calendr’.

    Et pour finir, CalendR est toujours en développement. Il y a encore des choses qui ne me plaisent pas (que je n'arrive pas à faire correctement en fait), comme la récuperation des évenements. Donc si le projet vous intéresse, et que vous avez plus de talent que moi (ce qui est certainement le cas ! ), je vous invite à m'envoyer autant de pull request que vous voudrez !

    Le dépôt : https://github.com/yohang/CalendR

  3. Snippet : Mysql RAND() pour Doctrine2

    Intégrer la fonction MySQL RAND() à Doctrine2 (Dans ce cas, Random() en DQL) :

    use Doctrine\ORM\Query\AST\Functions\FunctionNode,
        Doctrine\ORM\Query\SqlWalker,
        Doctrine\ORM\Query\Parser,
        Doctrine\ORM\Query\Lexer;
    
    class Random extends FunctionNode
    {
        public function getSql(SqlWalker $sqlWalker)
        {
            return 'RAND()';
        }
    
        public function parse(Parser $parser)
        {
            $parser->match(Lexer::T_IDENTIFIER);
            $parser->match(Lexer::T_OPEN_PARENTHESIS);
            $parser->match(Lexer::T_CLOSE_PARENTHESIS);
        }
    }
    
  4. Declarer un EntityRepository comme service

    Un article ? Ça faisait longtemps !

    Problème bête, dans Symfony2, j'ai besoin d'utiliser un Repository comme service. Pourquoi ? Pour l'injecter comme Provider dans une classe de gestion de calendrier dans mon cas.

    La solution est très simple, et se généralise à la création d'une entité via une méthode de factory.

    Donc, dans notre config.yml, on a :

    # config.yml
    services:
        my_repository:
            class: My\MainBundle\Entity\EventRepository
            factory_service: doctrine.orm.default_entity_manager
            factory_method: getRepository
            arguments: ['MyMainBundle:Event']
    

    C'était pas sorcier, non ?

  5. Twig, the flexible, fast, and secure template lang: Twig 1.2.0 RC1 released »

    twig-project:

    I have just published the first Twig 1.2 release candidate. This version is fully compatible with previous versions of Twig (just don’t forget to clear the cache after upgrading).

    In this post, I want to highlight some of the changes we have made to Twig to make it even better and more flexible…

  6. IE ne m'a même pas tué

    Intégration firefox terminée, c'est ok. Bon allez, on teste.

    Chrome ? rien à faire cool.

    Safari ? idem, génial.

    IE ? Ah oui mais, comment je teste ça moi, j'ai pas de pc windows sous la main… Un virtualbox en local ? Mouai, pourquoi pas.

    Ou alors… Un virtualbox sur mon serveur de développement avec remote desktop !

    C'est la petite solution toute simple que j'ai trouvée à mes tests IE, toujours très fastidieux…

    Pour ceux que ça interesse, ça m'a pris environ 2h pour installer un virtualbox 4.1 sur mon serveur (attention, sous debian ne pas utiliser le paquet virtualbox-ose fourni, vraiment vieux, un .deb tout neuf est dispo sur le site de virtualbox).

    Et du coup je me retrouve avec 4 machines virtuelles sur mon serveur (XP-IE6, XP-IE7, XP-IE8, 7-IE9), accessibles via rdesktop… Simple, efficace, et très pratique !

    Pensez-y ;)

  7. D'où l'importance de la casse (bonbon)

    Dans le cadre d'un projet symfony 1.4 / Doctrine 1.2, avec un back-office constitué essentiellement d'admin generator, avec des tables i18n et tout ce qui va avec, je me sui frotté à un problème… assez sympa :

    Avec ce schema :

    Category:
      actAs:
        NestedSet:
          hasManyRoots: true
          rootColumnName: thematic_id
        Timestampable: ~
        I18n:
          fields: [ name ]
      columns:
        id: { primary: true, autoincrement: true, type: integer }
        thematic_id: { type: integer, notnull: true }
        name: { type: string, length: 255, notnull: true }
      relations:
        Thematic:
          onDelete: CASCADE
    

    L'admin generator me sortait ce genre de requete :

    SELECT c.id AS c__id, c.thematic_id AS c__thematic_id, c.name AS c__name, c.lft AS c__lft, c.rgt AS c__rgt, c.level AS c__level, c.created_at AS c__created_at, c.updated_at AS c__updated_at FROM category c
    

    Sauf que du coup, le champ c.name, il pouvait toutjours le chercher dans la table de base !

    Après de longues heures de recherches, voici la solution (ridicule) à ce problème, située dans mon generator.yml :

    generator:
      class: sfDoctrineGenerator
      param:
        model_class:           category
        theme:                 admin
        non_verbose_templates: true
        with_show:             false
        singular:              ~
        plural:                ~
        route_prefix:          category
        with_doctrine_route:   true
        actions_base_class:    sfActions
    ...
    

    et plus précisément, ici :

    model_class:           category
    

    Mon generator.yml a été généré avec un c minuscule à category, ce qui générait ces problèmes de requêtes à la première exécution, maintenant on le saura !

  8. Utilisation du pattern Flyweight (poids mouche) avec symfony 1.4 et Doctrine

    Dans le cadre de manipulation de grandes quantités de données avec doctrine, il arrive souvent que l'hydratation des objets pose problème, dans le sens ou l'occupation mémoire devient vite énorme.

    Exemple :

    avec ce schema :

    Category:
      columns:
        name: { type: string(255) }

    Product:
      columns:
        category_id: { type: integer }
        name:        { type: string(255) }
      relations:
        Category: { onDelete: CASCADE, local: category_id, foreign: id, foreignAlias: Products }

    et ces fixtures :

    Category:
      <?php for ($i = 0 ; $i < 100 ; $i++): ?>

        Category_<?php echo $i ?>:
          name: 'Catégorie <?php echo $i ?>'

      <?php endfor ?>

    Product:
      <?php for ($i = 0 ; $i < 100 ; $i++): ?>
        <?php for ($j = 0 ; $j < 100 ; $j++): ?>

          Product_<?php echo $i ?>_<?php echo $j ?>:
            Category: Category_<?php echo $i.PHP_EOL ?>
            name: 'Produit <?php echo $i ?>_<?php echo $j ?>'

        <?php endfor ?>
      <?php endfor ?>

    Soit 100 catégories contenant chacune 100 produits, ce code :

    $q = Doctrine_Query::create()
             ->from('Product p'); foreach ($q->execute() as $product)
    {
    $name = $product->getName();
    }

    à une empreinte mémoire de plus de 30Mo. Pas vraiment problématique, mais imaginez la même chose avec des “vrais” objets, contenant autre chose qu'un simple nom…

    Deux solutions à ce problème :

    1er cas : Ne pas Hydrater les objets

    Si vous n'utilisez pas de méthodes métier particulières, et que vous n'avez pas besoin de récuperer d'objets liés à vos produits, vous pouvez vous contenter de ne pas hydrater vos objets doctrine. Ce code :

    $q = Doctrine_Query::create()
            ->from('Product p');

    foreach ($q->execute(array(), Doctrine_Core::HYDRATE_ARRAY) as $row)
    {
    $name = $row['name'];
    }

    a une empreinte mémoire de seulement 1.9 Mo, 15 fois moins.

    2ème cas : Vous avez besoin des méthode de votre objet, utilisation du poids mouche.

    L'autre solution, consiste à instancier un seul objet Product, que l'on hydratera à chaque itération de la boucle, avec de nouvelles données, remplaçant les anciennes.

    Ce code :

    $q = Doctrine_Query::create()
            ->from('Product p');
       
    $product = new Product;
    foreach ($q->execute(array(), Doctrine_Core::HYDRATE_ARRAY) as $row)
    {
    $product->hydrate($row);
    $name = $product->getName();
    }

    a une empreinte mémoire de 2.1Mo, c'est un peu plus que la méthode précédente, mais on garde l'accès à nos méthodes métier, et à nos relations à conditions de prévoir les jointures dans le requête.

    A noter que l'execution du script est également beaucoup plus rapide

  9. An easy way to debug filename typos in Sf2 »
  10. Multiple interface Inheritance in PHP »