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.

// 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($countries, SORT_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.

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', $locale, 2);
 
        // 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($countries, SORT_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 :

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'
            )
        );
 
        [..]
     }
}
Portrait de Anonyme

Merci beaucoup pour ce

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 ^-^

Portrait de Anonyme

Ta classe renvoi un

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.

Portrait de Maxence Delannoy

OK, je ne suis pas encore

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.

Portrait de Maxence Delannoy

Tant que j'y suis, le bogue

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.

Portrait de Anonyme

// On ajoute l'élément au

// 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 !

Portrait de Maxence Delannoy

C'est vrai que ce n'est pas

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).

Portrait de Anonyme

Merci pour l'exemple ,ca fait

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.

Portrait de Anonyme

Merci à toi pour ce tuto car

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

Portrait de Maxence Delannoy

Dans ton Bootstrap.php, tu

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);
    }
 
    [..]
}

Poster un nouveau commentaire

Le contenu de ce champ ne sera pas montré publiquement. If you have a Gravatar account, used to display your avatar.
  • Les adresses de pages web et de messagerie électronique sont transformées en liens automatiquement.
  • Allowed HTML tags: <a> <em> <strong> <cite> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Les lignes et les paragraphes vont à la ligne automatiquement.
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>. The supported tag styles are: <foo>, [foo].

Plus d'informations sur les options de formatage

CAPTCHA
La vérification ne tient pas compte des minuscules ou des majuscules.
Image CAPTCHA
Enter the characters shown in the image.