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($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.
<?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', $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 :
<?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
Emmanuel (non vérifié)
mer, 01/07/2009 - 00:24
Permalink
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 ^-^
Throrïn (non vérifié)
mar, 18/08/2009 - 15:00
Permalink
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.
Maxence
ven, 21/08/2009 - 16:18
Permalink
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.
Maxence
ven, 21/08/2009 - 16:20
Permalink
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.
Anonymeez (non vérifié)
sam, 22/08/2009 - 14:54
Permalink
// 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 !
Maxence
dim, 06/09/2009 - 17:59
Permalink
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).
Martin (non vérifié)
sam, 27/02/2010 - 11:42
Permalink
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.
Newbie (non vérifié)
lun, 28/06/2010 - 20:44
Permalink
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
Maxence
ven, 02/07/2010 - 12:25
Permalink
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);
}
[..]
}
Ajouter un commentaire