Utiliser les modèles de RedBean avec un namespace PHP

RedBean est un ORM PHP dont le principal atout est la concision de la syntaxe ; en effet, l'usage générique ne requiert que l'appel à des méthodes de la classe frontale R, ce qui permet d'effectuer toutes sortes d'opérations en base de données en un minimum de caractères. Ceci à un point tel que l'utilisation de modèles pour représenter de manière plus concrète les objets manipulés est un point qui arrive étonnamment tard dans la documentation de l'outil.

De son propre aveu, l'auteur n'est pas un fan des namespaces introduits dans PHP 5.3 ; en toute objectivité, cette vision peut devenir un frein considérable à l'apogée de Composer et de PSR-0, à l'image du petit challenge que je me propose de vous aider à résoudre aujourd'hui.

En effet, RedBean propose une liaison automatique entre les "beans" (les objets de base obtenus lors des requêtes en lecture) et les éventuels modèles plus complexes que vous pouvez être amené à créer selon vos besoins, le tout reposant sur FUSE. Là où le bât blesse, c'est que ce dernier ne sera pas capable de trouver vos classes si celles-ci sont écrites sous un namespace donné. Il est heureusement possible de surcharger le comportement par défaut de l'ORM en la matière, mais le manque de documentation précise à ce sujet peut vous mener droit vers quelques heures de serrage de dents et d'arrachage de cheveux (c'est du vécu). Voici un exemple de solution !

Tout d'abord, nous allons surcharger la classe concernée de RedBean, et plus précisément sa méthode chargée de trouver le modèle correspondant à un "bean" donné :

<?php

namespace MyNamespace;

class MyBeanHelper extends \RedBean_BeanHelper_Facade
{
public function getModelForBean(\RedBean_OODBBean $bean)
{
$modelName = 'MyNamespace\\Model\\'.ucfirst($bean->getMeta('type'));

if (! class_exists($modelName)) {
return null;
}

$obj = \RedBean_ModelHelper::factory($modelName);
$obj->loadBean($bean);
return $obj;
}
}

Notez que pour l'exemple, je me contente d'un copier-coller de la fonction d'origine où j'ajoute mon namespace "en dur" devant le nom de modèle généré par défaut (tout en optimisant un peu l'ensemble). Que cela ne vous empêche pas de mettre à profit le fait de devoir créer une classe dans l'unique but de faire entendre raison à RedBean, afin d'en faire quelque chose d'un peu plus maniable (par exemple, en générant le namespace selon le type de "bean" auquel vous êtes confronté... tout dépend de la structure de votre code).

Achevons le travail en remplaçant l'instance de la classe d'origine par une de la nôtre :

// Faisons en sorte de conserver le petit nom de la classe façade de l'ORM
use \RedBean_Facade as R;

R::$toolbox->getRedBean()->setBeanHelper(new MyNamespace\MyBeanHelper());

C'est tout ! Je n'ai pas trouvé de solution vraiment universelle pour pallier à ce type de situation ; si vous vous en sortez mieux, n'hésitez surtout pas...

Empêcher le commit d'un fichier déjà versionné sur SVN

Posons le décor : vous rejoignez un projet utilisant le gestionnaire de versions SVN. Vous clonez le dépôt, et dans 99% des cas, vous adaptez certains fichiers (de configuration par exemple) à votre environnement. Le hic, c'est que ces modifications seront prises en compte lors du prochain commit, et l'utilisation de la propriété svn:ignore ne vous sera d'aucun secours puisqu'elle ne concerne que les fichiers non versionnés. Un svn rm --keep-local est également hors de question puisqu'il supprimerait le fichier du dépôt, voire chez les autres utilisateurs (sans compter que cette option n'est disponible qu'à partir de la version 1.5.0). Voici comment passer outre cet état de fait :

cp config.file /some/other/folder # on copie le fichier hors du dépôt
svn rm config.file # on supprime le fichier à la fois aux yeux de SVN et "physiquement"
svn propset svn:ignore "config.file" . # le fichier n'existant plus, on peut dire à SVN de l'ignorer à l'avenir
mv /some/other/folder/config.file . # on ramène le fichier ; il n'est plus versionné, et ne sera plus pris en compte grâce à la commande précédente
svn revert config.file # on demande à SVN d'"oublier" la suppression programmée de ce fichier

Désormais, ce fichier ne sera plus committé, même avec un svn add --force. Pour appliquer le même traitement à un répertoire entier, il vous suffit d'ajouter l'option --recursive à la commande svn revert.

Modifier un modèle sur PrestaShop 1.5

La dernière version en date du CMS e-commerce PrestaShop en améliore pas mal d'aspects d'un point de vue technique, surtout concernant la surcharge des comportements par défaut. Ainsi, dans l'esprit de frameworks tels que FuelPHP, vous disposez à la racine du site d'un dossier override où vous pouvez facilement redéfinir tout ou partie de n'importe quelle classe du système. Ceci vous permettra de mettre PrestaShop à jour en conservant vos modifications.

Malgré tout, cela reste assez peu évident à mes yeux d'ajouter par exemple un champ aux produits (pour leur définir une caractéristique supplémentaire), les modèles de PrestaShop n'étant pas vraiment conçus en tant qu'entités. Voici la marche à suivre !

Tout d'abord, créons le champ correspondant dans la table qui va bien :

ALTER TABLE `ps_product` ADD `custom_field` INT NOT NULL;

Ensuite, il faut déclarer ce nouveau champ dans la classe Product en tant que membre, et le référencer dans un énorme tableau statique définissant les contraintes de chaque champ. Comme vu plus haut, nous allons surcharger la classe d'origine en créant le fichier override/classes/Product.php. Mais plutôt que de copier-coller tout le tableau, rusons un brin :

Product::$definition['fields']['custom_field'] = array('type' => ObjectModel::TYPE_STRING, 'validate' => 'isString');

class Product extends ProductCore
{
public $custom_field;
}

Jusqu'ici tout va bien ! En revanche, il serait quand même sympa que l'on puisse éditer la valeur de ce champ pour chaque produit directement en back-office, non ? Il faut pour cela ajouter, dans le template correspondant, un champ HTML portant le nom de votre propriété et étant hydraté avec sa valeur par Smarty (exactement comme les champs déjà présents). En l'occurence, ce fichier sera à choisir (selon l'endroit où le champ devra apparaître) dans le dossier admin/themes/myawesometheme/template/controllers/products.

Last but not least : si vous suivez les bonnes pratiques de développement sur PrestaShop, vous avez conservé le thème par défaut intact et l'avez dupliqué sous un autre nom afin de pouvoir travailler dessus. Idéalement, vous avez suivi le même cheminement en back-office, au cas où vous seriez justement amené à effectuer des modifications telles que celle décrite à l'instant. Dans ce cas, sachez qu'il faut indiquer à PrestaShop quel thème sera utilisé lors de l'accès au back-office, et ce, indépendamment du thème sélectionné pour la boutique, et individuellement pour chaque compte administrateur ! Gagnons donc du temps :

UPDATE `ps_employee` SET bo_theme='myawesometheme';

Automatiser l'usage de LESS via la gestion des environnements de votre framework PHP

LESS est un préprocesseur CSS étendant les possibilités natives du langage, proposant notamment l'utilisation de variables ou encore de mixins (sortes de fonctions) permettant au développeur feignant efficace d'appliquer au CSS le sacro-saint principe du don't repeat yourself. Seulement voilà, qui dit préprocesseur dit compilation, ce qui implique un passage systématique par le terminal. Comment éviter cela ?

Le but du jeu est d'intercepter les appels aux feuilles de styles émis par les navigateurs des visiteurs, et d'y répondre en accord avec l'environnement d'exécution actuel : en développement, on renverra un code non compressé et fraîchement compilé, afin de nous mâcher le travail ; en production en revanche, on créera un fichier CSS minifié et statique à l'occasion de de la première requête, que l'on renverra tel quel lors des suivantes afin d'éviter une charge serveur inutile. Pour changer un peu, nous allons voir comment mettre cela en place avec le framework PHP Symfony2.

Voici tout d'abord comment rerouter les requêtes concernées vers une méthode dédiée d'un contrôleur, dans votre fichier routing.yml :

less_to_css:
pattern: /css/{file}.css
defaults: { _controller: MyBundle:MyController:css }
requirements:
file: ([a-z0-9_-]+)

Ladite méthode embarque le code suivant :

public function cssAction($file)
{
if (! file_exists(CSS_PATH.$file.'.less')) {
throw $this->createNotFoundException('Cette feuille de style est introuvable.');
}

if ($this->container->get('kernel')->getEnvironment() === 'prod') {
if (! file_exists(CSS_PATH.$file.'.css')) {
system('lessc -x '.CSS_PATH.$file.'.less > '.CSS_PATH.$file.'.css');
}
$response = new Response(file_get_contents(CSS_PATH.$file.'.css'));
} else {
$output = array();
exec('lessc '.CSS_PATH.$file.'.less', $output);
$response = new Response(implode(PHP_EOL, $output));
}

$response->headers->set('Content-Type', 'text/css');
return $response;
}

Voilà donc un compromis intéressant entre automatisme et délégation de la compilation au serveur ! Évidemment, ce type de solution ne sera probablement pas adapté à un site à fort trafic, mais aura au moins le mérite de vous libérer l'esprit jusqu'à la fin du recettage.

Utiliser plusieurs polices au format SVG avec un seul fichier

Dans , je démontrais comment réaliser un gain de performances en combinant plusieurs polices dans un même fichier JavaScript. À l'heure de l'explosion des règles CSS3 @font-face, il peut être utile de savoir que des possibilités similaires s'offrent à nous concernant les fichiers de polices au format SVG. En effet, ceux-ci utilisent le format XML, et acceptent donc parfaitement la déclaration simultanée de plusieurs polices en faisant se suivre les balises font :

<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata></metadata>
<defs>
<font id="police1">
...
</font>
<font id="police2">
...
</font>
</defs>
</svg>

Il est donc très facile de combiner plusieurs fichiers SVG, obtenus par exemple via le générateur en ligne de FontSquirrel.

Le CSS correspondant prend alors tout son sens :

@font-face {
// ...
src: url('fonts.svg#police1') format('svg');
}

@font-face {
// ...
src: url('fonts.svg#police2') format('svg');
}

Les ancres étant de toute façon nécessaires pour s'assurer de la compatibilité cross-browser de cette déclaration (notamment chez Opera), n'utiliser qu'un seul fichier SVG est un pur bénéfice !