Damien ALEXANDRE

Aller au contenu | Aller au menu | Aller à la recherche

mercredi, août 18 2010

Désactiver temporairement Timestampable avec Doctrine

Timestampable est un behavior Doctrine fort pratique qui permet de mettre en place des champs "created_at" et "updated_at" sur nos modèles sans avoir à s'en soucier.

Il ont pleins d'options très pratiques, et sont entièrement automatique : à chaque save(), le updated_at est mis à jour.

Seulement il peux arriver d'avoir besoin, occasionnellement, de désactiver ces automatismes (import en masse en souhaitant garder les anciennes dates, modification d'un contenu par un bot... chacun ces problèmes hein). Voici la marche à suivre.

// Récupérer le Listener (c'est pas forcement 0, ça dépend de l'ordre dans votre Table->setUp())
$timestampable = $this->getListener()->get(0);
// Changer les options voulu, et donc désactiver tel ou tel propriété du Behavior
$timestampable->setOption(array('updated' => array('disabled' => true)));

Ce code se place au niveau de votre objet, dans l'idée vous pouvez vous faire une méthode, appelée occasionnellement donc :

public function disableTimestampableBehavior()
{
$timestampable = $this->getListener()->get(0);
$timestampable->setOption(array('updated' => array('disabled' => true)));
}

De rien ! (ah et bien sûr c'est applicable aux autres Listeners !)

lundi, juillet 12 2010

Exécuter des tests dans une base de donnée éphémère avec symfony / Doctrine

Vous cherchez comment faire pour que vos test unitaire (phpunit où autres...) s'exécutent sur une base de donnée neuve (MySql, SQLite... peu importe) à chaque fois (avec chargement des fixtures) ? Simple. Suivez l'exemple.

Dans votre fichier databases.yml, vous pouvez donner une configuration particulière à l'environnement de test :

test:
  poney:
    param:
      #dsn: "sqlite::memory:"
      #dsn: mysql:host=127.0.0.1;dbname=poney
      dsn: sqlite:///%SF_DATA_DIR%/poney.db?mode=0666

Tous l'intérêt de cette petite note technique réside dans le bout de code qui suit, qui va réinitialiser la base de donnée de test à chaque fois que vous lancerez vos tests :

define('SF_APP_NAME', 'backend');
define('SF_ENV', 'test');
define('SF_CONN', 'poney');
if ( SF_APP_NAME != '' )
{
  require_once(dirname(__FILE__).'/../../../../config/ProjectConfiguration.class.php');
  myTestClass::$configuration = ProjectConfiguration::getApplicationConfiguration( SF_APP_NAME , SF_ENV, true);
  sfContext::createInstance(myTestClass::$configuration);
}
class myTestClass
{
  public static $configuration = null;
  public static $databaseManager = null;
  public static $connection = null;
  protected static function setup()
  {
    if (self::$configuration)
    {
      self::$databaseManager = new sfDatabaseManager(self::$configuration);
      $doctrine = new sfDoctrineBuildTask(self::$configuration->getEventDispatcher(), new sfAnsiColorFormatter());
      $doctrine->setConfiguration(self::$configuration);
      $doctrine->run(array(), array("--db --and-load --no-confirmation --env=test"));
      if (SF_CONN) self::$connection = self::$databaseManager->getDatabase(SF_CONN);
    }
    else
    {
      throw new Exception("Unable to load the Database");
    }
  }
... ETC

On lance en fait la task doctrine:build directement via le PHP, avec la très importante option "--env=test" qui évite d'écraser votre base de donnée de travail :-)

A vous de vous inspirer de ce code pour vos propres tests, il y a plein de possibilités (voir les lignes commentées dans le databases.yml) :

  • On peux utiliser une base de donnée éphémère en RAM
  • Utiliser une base MySQL classique, ou oracle, ou n'importe quoi qui est supporté par Doctrine
  • On peux choisir de charger des fixtures spécifique, ou pas...

Bref vous êtes les maitres du monde. Ce code est utilisé ici avec symfony 1.4 et phpunit.

vendredi, juillet 2 2010

Utiliser des fontes SVG avec Raphael.js

Raphael.js est une librairie javascript permettant de dessiner du SVG très simplement, et ce sur tous les navigateurs actuels, IE6 compris (en passant par VML).

Bref ça rox du poulet et je suis actuellement en train de jouer avec. Les limitations imposé par cette compatibilité sont assumées : il n'y a pas de méthode permettant d'utiliser tous les filters SVG comme feGaussianBlur ou feOffset (qui servent à dessiner une ombre portée) par exemple - tout simplement parce que ce n'est pas reproductible sous IE.

Heureusement il est possible d'étendre Raphael pour y ajouter nos propres méthodes et ainsi exploiter pleinement l'intégralité des spécifications SVG (à condition de faire fi de la compatibilité IE, mais on est là pour s'amuser non ?).

Mon besoin était de mettre en place une police personnalisée. Il y a plusieurs solutions :

  • utiliser des police "web safe" (dont le verdana, arial, helvetica...)
  • utiliser @font-face pour déclarer une nouvelle police en CSS
  • utiliser une fonte définie en SVG via la balise <font>

C'est ce dernier cas qui me paraissait le plus fun. J'ai donc trouvé une police libre, et j'ai convertie les glyphes qui m'intéressait en SVG grâce à l'excellent http://www.fontsquirrel.com/ !

 Par contre, l'API de Raphael ne propose pas de méthode pour embedder une fonte externe. Voici ma solution, j'espère qu'elle vous sera utile ;-)

Raphael.fn.registerSvgFont = function (name, path)
{
var fontface = document.createElementNS("http://www.w3.org/2000/svg", "font-face");
fontface.setAttribute('font-family', name);
var fontfacesrc = document.createElementNS("http://www.w3.org/2000/svg", "font-face-src");
var fontfaceuri = document.createElementNS("http://www.w3.org/2000/svg", "font-face-uri");
fontfaceuri.setAttributeNS('http://www.w3.org/1999/xlink', 'href', path);
var fontfaceformat = document.createElementNS("http://www.w3.org/2000/svg", "font-face-format");
fontfaceformat.setAttribute('string', 'svg');
fontfaceuri.appendChild(fontfaceformat)
fontfacesrc.appendChild(fontfaceuri);
fontface.appendChild(fontfacesrc);
return this.defs.appendChild(fontface);
};

A utiliser de la façon suivante dans votre code :

paper.registerSvgFont('pixelsplitter', '../fonts/pixelsplitter-bold-webfont.svg');

createElementNS n'est pas compatible IE6 attention.

mercredi, juin 16 2010

Installer XHProf sous Ubuntu 9.10 et PHP5.3 (dotdeb)

Si comme moi vous utilisez les dépôts dotdeb sur votre Ubuntu / Debian, l'installation de l'extension XHProf peut s'avérer fastidieuse...

Voici la procédure normale :

wget http://pecl.php.net/get/xhprof-0.9.2.tgz
tar xvfz xhprof-0.9.2.tgz
cd xhprof-0.9.2/extension
phpize
./configure
make
sudo make install
sudo make test

On télécharge, on compile, on installe et on test. Si vous n'avez aucun soucis jusque là, GOOD FOR YOU. Vous pouvez modifier votre php.ini et jouer avec XHProf :

[xhprof]
extension=xhprof.so
;
; directory used by default implementation of the iXHProfRuns
; interface (namely, the XHProfRuns_Default class) for storing
; XHProf runs.
;
xhprof.output_dir=(A REMPLIR)

Maintenant si vous êtes comme moi, vous obtiendrez surement une erreur du style :

PHP Warning:  PHP Startup: xhprof: Unable to initialize module
Module compiled with module API=20060613
PHP    compiled with module API=20090626
These options need to match

Normal ! Vous venez de compiler le module avec la mauvaise version de php5-dev (et donc de phpize). Pour vérifier votre version, faite un phpize -v, si vous êtes en PHP 5.3 ça devrait être 20090626.
Un petit tour dans apt-get nous apprend que php5-dev ne peux pas être mis à jour :

libtool (>= 2.2) mais 2.2.6a-4 devra être installé

Cette erreur ne veux pas dire grand chose, je ne l'ai toujours pas compris, mais il existe une solution ! Downgrader à la main libtool et réinstaller le tout (car php5-dev est bien disponible chez dotdeb dans la version qui va bien) :

cd /tmp
wget http://fr.archive.ubuntu.com/ubuntu/pool/main/libt/libtool/libtool_1.5.26-1ubuntu1_i386.deb
sudo apt-get remove libtool
sudo dpkg -i libtool_1.5.26-1ubuntu1_i386.deb
sudo apt-get install php5-dev

Maintenant que vous avez la bonne version de php5-dev tout devrait rouler (vous pouvez recommencer depuis le début de ce post :P), et là j'ai eu le problème avec XHProf mais j'aurais pu avoir le même sur n'importe quel compilation de module.

lundi, mai 31 2010

Petit test de Doctrine 2 beta 1

Tien, mais c'est vrai que j'ai un blog moi... Voici le copier / coller d'un billet écrit sur le blog de Clever Age par mes petits doigts potelés. Bisou.

Il y a environ 1 mois de cela, la première version bêta de Doctrine 2 a été rendue publique, une petite prise en main rapide s’imposait !

Je ne vais pas parler ici de l’intégration de Doctrine dans le framework MVC symfony, le plugin sfDoctrine2Plugin n’étant à l’heure actuelle pas testable en profondeur. Cet article porte donc sur Doctrine 2, l’ORM, uniquement.

Le schéma de description du modèle

Le classique schema.yml pour commencer, apporte son lot de nouveautés côté syntaxe. Il y a trois méthodes : XML, Yaml et DocBlock (sous forme d’annotations PHP). À vous de configurer le bon Driver (setMetadataDriverImpl(), dans votre configuration Doctrine), et donc de décrire votre modèle selon votre humeur :

La méthode recommandée par Doctrine est celle des annotations DocBlock. Leur écriture n’est pas très sexy, mais elles peuvent être générée depuis les autres formats :

<?php
namespace Entities;

/** @Entity @Table(name="users") */
class User
{
   /**
    * @Id @Column(type="integer")
    * @GeneratedValue(strategy="AUTO")
    */
   private $id;
   /** @Column(type="string", length=50) */
   private $name;
   /**
    * @OneToOne(targetEntity="Address")
    * @JoinColumn(name="address_id", referencedColumnName="id")
    */
   private $address;
}
?>

La notation XML (si vous utilisez un IDE XML-friendly, vous aurez l’auto-complétion pour toutes les balises et attributs) :

<?xml version="1.0" encoding="UTF-8"?>
<doctrine-mapping xmlns="http://doctrine-project.org/schemas/orm/doctrine-mapping"
     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
     xsi:schemaLocation="http://doctrine-project.org/schemas/orm/doctrine-mapping
                   http://doctrine-project.org/schemas/orm/doctrine-mapping.xsd">

   <entity name="Entities\User" table="users">
       <id name="id" type="integer">
           <generator strategy="AUTO"/>
       </id>
       <field name="name" type="string" length="50"/>
       <one-to-one field="address" target-entity="Address">
           <join-column name="address_id" referenced-column-name="id"/>
       </one-to-one>
   </entity>

</doctrine-mapping>

et, à l’ancienne, en Yaml :

Entities\User:
 type: entity
 table: users
 id:
   id:
     type: integer
     generator:
       strategy: AUTO
 fields:
   name:
     type: string
     length: 50
 oneToOne:
   address:
     targetEntity: Address
     joinColumn:
       name: address_id
       referencedColumnName: id

Vous noterez que la syntaxe est légèrement différente sur le format Yaml, mais pas de panique, la migration de vos projet de Doctrine 1 à Doctrine 2 est prévue, et on a donc une task ./doctrine orm:convert-d1-schema qui se charge de la conversion.

Les Entities et le EntityManager

Comme vous pouvez le voir dans la première méthode, la classe User que j’ai définie est une Entity ! C’est cette classe que je vais manipuler, et elle est vide. Pas de getter, pas de setter, pas de save(), elle n’étend rien...

C’est un des intérêts de Doctrine 2, tuer la magie (et ainsi améliorer les performances), c’est à vous d’écrire ce dont vous avez besoin, de façon explicite et ainsi plus simple à débugger.

À savoir que la majorité des IDE permettent de générer les getters/ setters de vos propriétés automatiquement. 
Sachez qu’il existe aussi une classe nommée ActiveEntityque vous trouverez dans le namespace DoctrineExtensions, qui implémente sur les Entities qui l’étendent les getters / setters magique, le save(), le ArrayAccess, le toArray()... mais il n’est pas recommandé de l’utiliser (ce n’est pas "la façon de faire" en Doctrine 2).

Pour chaque connexion à votre base de donnée, vous avez un EntityManager.

$em = EntityManager::create($connectionOptions, $config);

C’est cette classe qui permet de récupérer et de sauver nos Entities. Ainsi pour créer un User dans ma base, je procède de cette façon :

$user = new \Entities\User();
$user->setName('Damien');
$em->persist($user);
$em->flush();

On donne des Entities à notre EntityManager (via la méthode persist()), et quand on souhaite mettre à jour la base de donnée, on flush(). Doctrine abandonne donc le pattern Active record au profit d’une approche de Data Mapping (qui profite de Reflection, introduit en PHP 5.3) :

because we think it hurts testability, project maintainability and is not a suitable abstraction (80/20) for models that exceed the complexity of a blog or otherwise simple web application

Côté requête, la magie des findBy* est toujours de la partie :

$user = $em->getRepository('Entities\User')->findOneByName('romanb');

(À noter que le Repository est commun à toutes les Entities par défaut, il est ensuite possible d’en créer des spécifique pour chaque Entity, et ainsi d’avoir des classes de type "UserTable" comme dans Doctrine 1).

La construction d’une requête plus complexe (avec des jointures par exemple) demandera de faire appel au DQL. C’est la méthode la plus puissante et la plus flexible pour récupérer des objets depuis la base de données. Il permet d’interroger la base dans un langage objet, avec nos noms de classes, nos champs, nos héritages et associations sans nous soucier du nom des champs effectivement en base. Il est très similaire au SQL, mais ce n’est PAS du SQL :

$em->createQuery('SELECT COUNT(a.id) FROM Entities\User u LEFT JOIN u.articles a WHERE u.username = ?1 GROUP BY u.id');

Le DQL de Doctrine 2 est très proche de ce qu’on faisait déjà avec Doctrine 1, avec quelques améliorations, comme par exemple la possibilité d’étendre le langage avec des fonctions non native.

Essayer Doctrine 2

À l’heure où j’écris ces lignes, la sandbox de Doctrine 2 beta 1 ne fonctionne pas très bien. La meilleure solution pour jouer avec Doctrine 2 est de télécharger les sources depuis le dépôt GitHub et de résoudre les dépendances comme expliqué ici. Un package entièrement prêt à l’emploi pour la sandbox est en cours de préparation. Il y a aussi un petit quick-start dans la documentation officielle.

Si vous souhaiter utiliser Mysql, modifiez les options comme ci-dessous dans index.php et cli-config.php :

$connectionOptions = array(
   'driver' => 'pdo_mysql',
   'user' => 'login',
   'password' => 'pass',
   'dbname' => 'dbname'
);

La documentation est très bien avancée, elle couvre déjà de nombreux domaines et c’est, pour l’instant, une des seules ressource viable.

Pour finir, en l’état le plugin sfDoctrine2Plugin (qui permet d’utiliser Doctrine 2 dans symfony 1, avec admin-generator, etc) n’est pas fonctionnel actuellement et il faudra encore attendre un peu avant de pouvoir l’utiliser entièrement.

dimanche, novembre 1 2009

Convertir une fonte TrueType ou OpenType en EOT Embedded OpenType sous Ubuntu Linux

Un titre à ralonge pour un petit tuto rapide qui va vous permettre de convertir vos polices de caractères OpenType (.otf) ou TrueType (.ttf) en Embedded OpenType (.eot), un format propre à Microsoft, utilisé dans Internet Explorer depuis Ubuntu Linux (je dis Ubuntu mais ça devrait fonctionner sur n'importe quel unix).

EOT est le seul format de police accepté par Internet Explorer, il va donc falloir convertir les polices que je souhaite utiliser avec @font-face pour assurer la compatibilité. Microsoft propose un outil pour faire ça (WEFT), mais il est mal fichu et ne tourne pas sous Wine (testé avec Wine 1.0.1 sous Ubuntu 9.10). On va donc se débrouiller autrement !

Let me introduce ttf2eot !

Il s'agit simplement d'un utilitaire en ligne de commande pour OpenTypeUtilities.cpp (une classe qui à la base vient de Chromium, le projet Open Source dont Google Chrome est issu). On télécharge donc les sources, et on fait un petit "make" dans le répertoire :

~/Bureau/ttf2eot-0.0.2-2$ make

Si tout se passe bien le binaire ttf2eot à été compilé (si ça foire, posez vous la question : est-ce que g++ est installé sur mon système ?).

Comme son nom l'indique, on a plus qu'a lui passer un ttf pour qu'il nous génére le eot correspondant :

~/Bureau/ttf2eot-0.0.2-2$ ./ttf2eot < ../delicious-123/Delicious-Roman.ttf > ../delicious-123/Delicious-Roman.eot

Deux paramètre : le fichier d'entrée, et de destination :
$ ./ttf2eot < input.ttf > output.eot
That's all !

Convertir un fichier OpenType

C'est bien beau mais si notre police de caractère n'est pas en TrueType, ttf2eot ne sert à rien. Il n'existe pas d'équivalent pour OpenType, mais il est possible de convertir une fonte OpenType en TrueType. Voici la marche à suivre. On va avoir besoin de fontforge :

$ sudo apt-get install fontforge

Ensuite on écrit un petit script shell qui nous évitera de passer par l'affreuse interface de fontforge (à sauver en otf2ttf.sh par exemple) :

#!/usr/local/bin/fontforge
# Quick and dirty hack: converts a font to truetype (.ttf)
Print("Opening "+$1);
Open($1);
Print("Saving "+$1:r+".ttf");
Generate($1:r+".ttf");
Quit(0);

(Petit script récupéré ici, merci :-))

On s'en sert donc pour convertir notre fichier otf en ttf (et ensuite, on utilise ttf2eot pour enfin obtenir le fichier eot !) :

$ fontforge -script otf2ttf.sh ../delicious-123/Delicious-Roman.otf

Utilisation de @font-face

Si vous vous intéressez à eot c'est que vous allez utiliser @font-face (grillé !), mais savez-vous comment l'utiliser proprement ?

@font-face {
font-family:'Delicious';
src: url('Delicious-Roman.eot');
src: local('Delicious Roman'), local('Delicious'),
url('Delicious-Roman.otf') format('opentype');
}

Pour les explications, veuillez vous référer au billet de Paul Irish, qui est à l'origine de cette solution (qui en gros permet en une seule fois de déclarer une police pour tous les navigateur, sans requête HTTP superflue).

Quelques liens :

  • compatibilité de @font-face (on y voit que Google Chrome n'active pas Web Font par défaut)
  • la police pour laquelle je me suis penché sur le sujet, Delicious (ces conditions d'utilisations autorise @font-face)

- page 1 de 44