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 PHPSymfony2.
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 :
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));
}
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 de la recette.
Dans mon article sur Cufón, 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 :
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 !
La création et la gestion de formulaires HTML fait bien évidemment partie des nombreux atouts de FuelPHP : le module en charge de ce travail a d'ailleurs l'intelligence d'appliquer l'attribut HTML5required aux champs signalés comme obligatoires en ce qui concerne la validation côté serveur. En revanche, celle-ci comporte d'autres règles (maxlength, pattern avec une expression régulière...) possédant des équivalents côté client qui, pour leur part, ne sont pas répliqués. Voyons comment changer cela !
Le code concerné se trouve dans la méthode add_rule de la classe Field, située dans le fichier fuel/core/classes/fieldset/field.php, il suffit donc d'étendre cette classe côté application en réécrivant ladite méthode. Pour ce faire, créez le fichier fuel/app/classes/fieldset/field.php et éditez-le ainsi :
<?php
class Fieldset_Field extends Fuel\Core\Fieldset_Field
{
public function add_rule($callback)
{
$args = array_slice(func_get_args(), 1);
$this->rules[] = array($callback, $args);
switch ($callback) {
case 'required':
$this->set_attribute('required', 'required');
break;
case 'max_length':
$this->set_attribute('maxlength', $args[0]);
break;
case 'match_pattern':
$this->set_attribute('pattern', $args[0])->set_attribute('title', $args[0]);
break;
case 'min_length':
if ($this->get_attribute('pattern') === null) {
$this->set_attribute('pattern', '.{'.$args[0].',}');
}
break;
}
return $this;
}
}
Nous venons donc de remplacer le simple test sur l'attribut required présent à l'origine par une structure switch testant les différentes règles de validation gérées par FuelPHP que nous voulons implémenter côté client.
Nous allons également en profiter pour modifier la méthode utilisée par la règle match_pattern : les expressions régulières devant être entourées d'un délimiteur en PHP mais pas dans le code HTML, ajoutons ce délimiteur directement avec ladite méthode, qui se trouve dans la classe Validation du fichier fuel/core/classes/validation.php (à étendre tout comme la précédente) :
<?php
class Validation extends Fuel\Core\Validation
{
public function _validation_match_pattern($val, $pattern)
{
return $this->_empty($val) || preg_match('~'.$pattern.'~', $val) > 0;
}
}
Pour terminer, il suffit de déclarer l'extension à FuelPHP via le fichier fuel/app/bootstrap.php : je vous invite à vous référer au précédent article, dont la fin décrit une démarche identique.
Vous obtenez ainsi une validation plus riche côté client ! D'autres règles de validation de FuelPHP sont évidemment implémentables de la même façon, n'hésitez pas à partager votre code le cas échéant.
En utilisant FuelPHP, je me suis rendu compte qu'une URL ne dépendant pas d'une règle de routage personnalisée (c'est-à-dire traduite directement en noms de contrôleurs et de méthodes) n'était pas sensible à la casse. Les maniaques comme moi y verront sans nul doute un risque potentiel de duplicate content... Heureusement, la flexibilité du framework fait que l'on peut modifier ce comportement très simplement !
Primo, créons le fichier fuel/app/classes/router.php, et éditons-le comme suit :
Les deux lignes importantes (les seules ajoutées à la fonction d'origine) sont celle où l'on déclare la variable $case_sensitive (pour récupérer la valeur du paramètre de configuration éponyme), et celle où on l'utilise pour vérifier le cas échéant la valeur de chaque segment de l'URL courante. En retournant false si l'un d'entre eux comporte des majuscules, on déclenchera automatiquement une erreur 404 bienvenue.
Il ne nous reste qu'à informer FuelPHP de l'existence de cette extension de classe, en modifiant le fichier fuel/app/bootstrap.php :
Autoloader::add_classes(array(
// Add classes you want to override here
// Example: 'View' => APPPATH.'classes/view.php',
'Router' => APPPATH.'classes/router.php'
));
Notez bien qu'en toute logique, ceci s'appliquera également aux segments de l'URL correspondant aux éventuelles variables GET ; si ce comportement est gênant dans votre cas, il suffira d'affiner un peu la vérification effectuée.
À compter de sa version 1.1, le framework PHPFuelPHP dispose du package Parser, qui lui permet d'utiliser un moteur de templates externe. Compatible avec un certain nombre d'entre eux, il l'est notamment avec celui qui nous intéresse aujourd'hui : Twig, le moteur de templates de Symfony2.
Si l'installation de ce dernier au sein du framework est plutôt évidente, même sans passer par Composer (télécharger Twig, extraire le sous-répertoire lib/Twig de l'archive obtenue, le placer tel quel dans fuel/app/vendor, ajouter si nécessaire Parser à l'autoload, sabrer le champagne), le moyen d'utiliser des extensions de Twig peut le paraître nettement moins, alors qu'il n'en est rien ! Voyez plutôt :
Commençons par écrire notre extension ; voici l'exemple (bidon) que j'ai utilisé pour mes tests :
<?php
class MyExtension extends \Twig_Extension
{
public function getName()
{
return 'my_extension';
}
public function getFilters()
{
return array(
'my_filter' => new \Twig_Filter_Function('my_function')
);
}
}
function my_function($s)
{
$salt = 'FuelPHP rules';
return md5($salt.$s);
}
Par souci de simplicité, j'ai placé ce fichier directement dans fuel/app/classes/myextension.php. Copions ensuite le fichier de configuration fuel/packages/parser/config/parser.php dans fuel/app/config/parser.php, et éditons ce dernier pour y déclarer notre extension :