Concepteur / Développeur PHP5 / Symfony.
Mangeur de bières.
Technophile.
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.
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 ?
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>
All the event stuff is based on 2 Interfaces
In fact, you have to implement EventInterface
on your event class(es). The methods to implements are :
\DateTime
instance\DateTime
instance$date
is during the event$period
is during the event$period
contains the eventBut that’s a lot… So CalendR comes with a CalendR\Event\AbstractEvent
class, that implement most of theeses methods.
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 :
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);
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’.
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
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+.
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 ?
…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>
Au niveau évènements, tout est basé sur 2 interfaces :
En gros, vous devez implémenter EventInterface
sur la (les) classes représentant vos évènements. Les methodes à implementer sont :
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.
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 :
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);
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
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);
}
}
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 ?
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…
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 ;)
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 !
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