Améliorer la validation des formulaires côté client avec FuelPHP

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 HTML5 required 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é clients 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 , 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.

Rendre les URLs par défaut sensibles à la casse avec FuelPHP

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 :

<?php

class Router extends Fuel\Core\Router
{
protected static function parse_segments($segments, $namespace = '', $module = false)
{
$temp_segments = $segments;
$case_sensitive = \Config::get('routing.case_sensitive', true);

foreach (array_reverse($segments, true) as $key => $segment) {
if (($case_sensitive) && ($segment != mb_strtolower($segment))) {
return false;
}

$class = $namespace.'Controller_'.\Inflector::words_to_upper(implode('_', $temp_segments));
array_pop($temp_segments);

if (class_exists($class)) {
return array(
'controller' => $class,
'action' => isset($segments[$key + 1]) ? $segments[$key + 1] : null,
'method_params' => array_slice($segments, $key + 2),
);
}
}

if ($module) {
$class = $namespace.'Controller_'.ucfirst($module);

if (class_exists($class)) {
return array(
'controller' => $class,
'action' => isset($segments[0]) ? $segments[0] : null,
'method_params' => array_slice($segments, 1),
);
}
}
return false;
}
}

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.

Créer une extension Twig pour FuelPHP

À compter de sa version 1.1, le framework PHP FuelPHP 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 :

'View_Twig' => array(
// ...
'extensions' => array(
'Twig_Fuel_Extension',
'MyExtension'
),
),

Et c'est tout ! Vous pouvez désormais utiliser le filtre my_filter directement dans vos templates Twig sous FuelPHP.

Masquer les points dans la numérotation d'une liste ordonnée sur IE 7

Si vous souhaitez masquer les points qui suivent les numéros dans une liste ordonnée (et pourquoi pas les remplacer par d'autres signes de ponctuations), la meilleure solution consiste à désactiver l'affichage de ces numéros, et à prendre en charge vous-même la numérotation grâce à CSS et au pseudo-élément :before :

ol { list-style-type: none; }
ol li { counter-increment: mycounter; }
ol li:before { content: counter(mycounter) ' '; }
ol.custom:first-child { counter-reset: mycounter; }

C'est bien beau, mais Internet Explorer 7 vient une nouvelle fois gâcher la fête en ne supportant pas :before.

Heureusement, en trichant un peu, il est possible de masquer "physiquement" les points en utilisant un positionnement similaire à celui-ci (qui nécessite tout de même d'insérer une balise supplémentaire, afin de pouvoir contrôler le contenu des li comme des éléments de type bloc) :

ol {
list-style-position: inside;
width: 72px;
}

ol li span {
float: right; /* les noeuds de texte se comportent comme des blocs flottant à droite */
margin-left: -7px; /* on vient superposer ces derniers avec les points */
padding-left: 7px; /* on rétablit un peu d'espace */
position: relative;
z-index: 1; /* on les fait passer au-dessus des points */
background-color: white; /* on leur donne la même couleur de fond que le conteneur */
}

Les valeurs en pixels devront évidemment être adaptées à vos propres styles ! Cette astuce est plus pratique dans le cas d'une liste de liens : les li contenant alors déjà des a, il est inutile de passer par des span superflus d'un point de vue sémantique.

Gérer les variantes d'une police avec Cufón

Cufón est une librairie JavaScript ayant pour objet la gestion de polices non-websafe, en les redessinant de manière vectorielle sur des canvas. Elle se présente comme une alternative intéressante aux règles CSS3 @font-face pour les polices vraiment exotiques, dont le rendu natif par les navigateurs n'est pas optimal. En revanche, sa lourdeur ainsi que le fait que le texte ainsi rendu ne soit pas correctement sélectionnable font qu'il vaut mieux réserver son usage aux titres et autres éléments spécifiques.

Le générateur en ligne permet d'obtenir facilement un fichier JS embarquant une ou plusieurs variantes d'une police, réparties entre les classiques normal, gras, italique et gras-italique. Malheureusement, ce dernier gère plutôt mal la correspondance entre lesdites variantes et leurs équivalents CSS, il vaut donc mieux s'occuper nous-même de cet aspect.

Voici un exemple de déclaration de police dans un fichier généré par Cufón :

Cufon.registerFont({"w":187,"face":{"font-family":"Avenir","font-weight":600,"font-style":"oblique", // ...

Il suffit de modifier à la main ces déclarations, en spécifiant une valeur de graisse (pour information, 400 correspond à normal, et 700 à bold), et en ajoutant la règle font-style pour les déclinaisons en italique. Cela vous permettra également d'outrepasser la limite intrinsèque de 4 déclarations par fichier si nécessaire, en regroupant plusieurs fichiers en un seul, d'où économie de nombre de requêtes HTTP et donc de temps de chargement de votre page !