Zend Framework : champ "Pays" dans un formulaire

Le select qui permet de choisir un pays

Le Zend Framework est livré avec un ensemble de fichiers xml (dans Zend/Locale/Data/) qui contiennent des chaines localisées dans plusieurs langues. On peut par exemple récupérer une liste de pays traduite en français avec la méthode statique Zend_Locale::getCountryTranslationList. J'ai donc eu l'idée de m'en servir pour créer un select qui permet de choisir un pays.

C'est assez simple à réaliser, mais je me suis heurté à quelques petits problèmes.

Premièrement, la liste arrive dans le désordre et il faut donc la trier pour faciliter la sélection. La fonction PHP asort est là pour ça, mais par défaut, elle fonctionne avec des chaines encodées en ISO-8859-1, alors que la liste est encodée en UTF-8, ce qui fait que les différents noms d'îles se retrouve à la fin car elle commence par le caractère Î. Heureusement, en réglant la locale et en passant l'argument SORT_LOCALE_STRING à la fonction asort, on peut trier un tableau de chaînes UTF-8. Attention cependant, ça ne fonctionne que sous Windows.

<?php
// On sauvegarde la locale actuelle
$oldLocale setlocale(LC_COLLATE'0');
// On passe en locale française, encodage UTF-8
setlocale(LC_COLLATE'fr_FR.utf8');
// On trie notre tableau
asort($countriesSORT_LOCALE_STRING);
// On restaure la locale initiale
setlocale(LC_COLLATE$oldLocale);
?>

Ensuite, je me suis dis que pour optimiser les performances, je pouvais désactiver la traduction automatique des libellés. En effet, ces derniers sont déjà traduits et comme la liste est assez longue, ce n'est pas la peine d'appeler Zend_Translate pour rien. Et là, je suis tombé sur un bogue du Zend Framework qui fait que les options du select disparaissent lorsqu'on désactive la traduction automatique. Heureusement, ce bogue est très facile à résoudre, il suffit de changer un true en false comme expliqué dans Jira.

Sur ma lancée, je me suis dit que c'était pas trop sympa pour les utilisateurs d'avoir à chaque fois à faire défiler toute la liste pour sélectionner leur pays d'origine, donc j'ai ajouté un peu de code pour que le pays correspond à la locale courante soit ramené en tête de liste.

Pour aller encore un peu plus loin, j'ai ajouté des options (en ajoutant des setters à ma classe) qui permettent de spécifier la table et la colonne de la base de données. On peut ainsi récupérer la liste des valeurs les plus fréquemment utilisées pour les amener elles aussi en haut de la liste.

<?php
class Wiip_Form_Element_SelectCountry extends Zend_Form_Element_Select
{
    
/* @var string */
    
protected $_column 'country';

    
/* @var string */
    
protected $_table;

    
/* @var string */
    
protected $_where;

    
// Désactive le traducteur (les noms de pays sont déjà traduits)
    
protected $_translatorDisabled true;

    public function 
init()
    {
        
/* @var $locale Zend_Locale */
        
$locale Zend_Registry::get('Zend_Locale');

        if (!
$locale) {
            throw new 
Exception('No locale set in registry');
        }

        
$countries Zend_Locale::getTranslationList('territory'$locale2);

        
// Le code ZZ correspond à "région indéterminée", on le remplace par "(Non renseigné)"
        
unset($countries['ZZ']);

        
// Tri de la liste
        
$oldLocale setlocale(LC_COLLATE'0');
        
setlocale(LC_COLLATE'fr_FR.utf8');
        
asort($countriesSORT_LOCALE_STRING);
        
setlocale(LC_COLLATE$oldLocale);

        
// Ajout d'une valeur nulle en tête de liste
        
$emptyLabel '(Non renseigné)';
        
$translator $this->getTranslator();
        if (
$translator$emptyLabel $translator->translate($emptyLabel);
        
$topItems = array('' => $emptyLabel);
        
        
// Déplace la région courante en tête de liste
        
$currentRegion $locale->getRegion();
        if (
$currentRegion) {
            
$topItems[$currentRegion] = $countries[$currentRegion];
            unset(
$countries[$currentRegion]);
        }

        
// Déplace les 5 pays les plus utilisés en tête de liste
        
if (isset($this->_table)) {
            
$table = new $this->_table;
            
$select $table->select();
            
$select->from($table, array($this->_column))
                   ->
where("{$this->_column} != ''")
                   ->
where("{$this->_column} != ?"$currentRegion)
                   ->
group($this->_column)
                   ->
order('COUNT(*) DESC')
                   ->
limit(5);
           if (isset(
$this->_where)) $select->where($this->_where);
           
$adapter $table->getAdapter();
           
$mostFrequentlyUsed $adapter->fetchCol($select);
           foreach(
$mostFrequentlyUsed as $countryCode) {
               
$topItems[$countryCode] = $countries[$countryCode];
               unset(
$countries[$countryCode]);
           }
        }

        
// Ajoute un séparateur non sélectionnable
        
$topItems['--'] = '--';
        
$this->setOptions(array('disable' => array('--')));

        
$countries array_merge($topItems$countries);

        
$this->setMultiOptions($countries);
    }
    
    public function 
setColumn($column)
    {
        
$this->_column $column;
    }

    
/**
     * Définit le nom de la classe dérivée de Zend_Db_Table qui sera
     * utilisée pour déterminer quels sont les pays les plus fréquemment
     * sélectionnés.
     * 
     * @param string $table 
     */

    
public function setTable($table)
    {
        
$this->_table $table;
    }

    public function 
setWhere($where)
    {
        
$this->_where $where;
    }
}
?>

Mise à jour du 09/01/10 : utilise getTranslationList au lieu de getCountryTranslationList qui est deprecated.

Exemple d'utilisation :

<?php
class MonFormulaire extends Zend_Form
{
    public function 
init()
    {
        [..]
        
        
// On ajoute un chemin de recherche pour que Zend_Form puisse trouver le nouvel élément
        
$this->addPrefixPath('Wiip_Form_Element''Wiip/Form/Element/'Zend_Form::ELEMENT);

        
// On ajoute l'élément au formulaire. Les valeurs les plus fréquemment utilisées
        // dans la colonne "country" de la table liée à la classe "Companies" seront
        // placées en tête de liste
        
$this->addElement(
            
'selectCountry',
            
'country',
            array(
                
'label' => 'Pays',
                
'table' => 'Companies'
            
)
        );

        [..]
     }
}
?>

Commentaires

Merci beaucoup pour ce tuto!

Moi qui cherchais justement le moyen de faire la même chose!
Je ne savais même pas qu'il existait ces fichiers!

Trop cool ^-^

Ta classe renvoi un deprecated avec la version 1.9 de Zend, je l'ai corrigé et je me permet de te la passer. Je poste aussi la classe entièrement corrigée sur mon site si tu le veux bien.
Naturellement je renvoie sur la tienne.

remplace cette ligne:

$countries = Zend_Locale::getCountryTranslationList($locale);

par:

$countries = Zend_Locale::getTranslationList('territory',$locale, 2);

et sa marchera.

OK, je ne suis pas encore passé en 1.9 et je n'ai donc pas rencontré le problème. Merci de m'avoir signalé ce changement dans l'API.

Tant que j'y suis, le bogue sur la traduction automatique des libellés des options du select a apparemment été corrigé dans la version 1.8.4 du ZF.

// On ajoute l'élément au formulaire. Les valeurs les plus fréquemment utilisées
// dans la colonne "country" de la table liée à la classe "Companies" seront
// placées en tête de liste

Je pense qu'il n'est pas judicieux d'un point de vue ergonomique de 'déplacer' les éléments dans le select.
En effet, dans le cas d'un utilisateur habitué, celui ci pourrait voir ses habitudes devoir changer en fonction des 'statistiques'.

Il serait plus judicieux selon moi d'utiliser 'selected="selected"' ça pourrait faciliter le choix de l'utilisateur, sans trop le pertuber.

Sinon, merci pour l'astuce !

C'est vrai que ce n'est pas recommandé de déplacer des éléments d'interface. En pratique, les déplacements devraient être peu fréquents (mais ça dépend de l'application).

Merci pour l'exemple ,ca fait une bonne base de départ !

Pour le tri, j'utilise plutôt : uasort($countries, 'strcoll');

Ca permet de trier correctement Åland Islands en anglais.

Merci à toi pour ce tuto car il fait exactement ce que je voulais faire. Néanmoins comme tout bon newbie, je n'arrive pas à l'implémenter.

J'ai copié ton code dans mon dossier application/form/Pays.php
Je me suis créé une table Companies avec un champs id et un champs Pays

Voici comment je l'utilise dans mon formulaire :

$pays = new Application_Form_Pays('nompays');
$pays->addElement(
'selectCountry',
'country',
array(
'label' => 'Pays',
'table' => 'Companies'
)
);

Si je ne mets pas nom pays, j'ai l'erreur :
Zend_Form_Element requires each element to have name

là, jai l'erreur No entry is registred for key 'Zend_Locale' qui point vers cette ligne là : $locale = Zend_Registry::get('Zend_Locale');

Je suis en Zend_Framework 1.10.0 (paquet Ubuntu)

Merci d'avance pour ta réponse

Dans ton Bootstrap.php, tu dois instancier un objet Zend_Locale et le placer dans le registre (Zend_Registry). Exemple :

<?php
class Bootstrap extends Zend_Application_Bootstrap_Bootstrap
{
protected $_locale;

[..]

protected function _initLocale()
{
$this->_locale = new Zend_Locale('fr_FR');
Zend_Registry::set('Zend_Locale', $this->_locale);
}

[..]
}

Ajouter un commentaire