Question ouverte : Le meilleur moyen de tester le contenu d’un objet Doctrine ?
Le problème avec les questions ouvertes, c’est que l’on ne sait pas toujours comment formuler correctement la question. Alors de mieux qu’un peu de code pour illustrer mes propos.
Le contexte
Nous avons une table post dans laquelle nous n’avons pas encore d’articles. On va donc faire les choses simplement sans se poser trop de questions. À la wordpress
.
Le code
/lib/model/doctrine/postsTable.class.php
<?php
class postsTable extends Doctrine_Table
{
public function getPosts()
{
$t = $this->createQuery('c')
->where('c.status = ?', 'publish');
return $t->execute();
}
}
/apps/frontend/modules/posts/actions/actions.class.php
<?php
/**
* posts actions
*/
class postsActions extends sfActions
{
public function executeIndex(sfWebRequst $request)
{
$this->entries = Doctrine::getTable('Posts')->getPosts();
$this->forward404Unless($this->entries);
}
}
}
Et enfin dans notre template : /apps/frontend/modules/posts/templates/indexSuccess.php
[...]
<?php if($entries) {
foreach($entries as $entry) { ?>
<h2><?php echo $entry->getTitle() ?></h2>
[...]
<?php }
} else { ?>
<h2>Oups! Aucun article n'a été trouvé</h2>
<?php } ?>
Le résultat
La première fois que j’ai fait ce code, je m’attendais vraiment à trouver mon « Oups… ». Ca me paraissait logique, table vide, la requête ne peut pas renvoyer de résultats à l’action et donc dans la vue mais non. Toujours et encore toujours du vide et aucun « Oups… ».
Et je ne comprends pas.
Je me suis donc « amusé » (c’est vite dit sur Doctrine) avec un var_dump afin de voir ce qui pouvait bien être stocké dans ma variable $entries. Après avoir décortiqué la soupe issue du var_dump, j’ai pu voir que ma variable contenait un Doctrine_collection vide et donc au final quelque chose « plein de vide ».
C’est donc la que l’on peut commencer à réfléchir.
Version 1 – getFirst
La première solution consiste à tester via Doctrine si il y a au moins un enregistrement :
Dans /apps/frontend/modules/posts/templates/indexSuccess.php
[...]
<?php if($entries->getFirst()) {
foreach($entries as $entry) { ?>
<h2><?php echo $entry->getTitle() ?></h2>
[...]
<?php }
} else { ?>
<h2>Oups! Aucun article n'a été trouvé</h2>
<?php } ?>
Version 2 – count()
Seconde solution, compter avec un bon vieux count() en PHP :
Dans /apps/frontend/modules/posts/templates/indexSuccess.php
[...]
<?php if(count($entries) > 0)) {
foreach($entries as $entry) { ?>
<h2><?php echo $entry->getTitle() ?></h2>
[...]
<?php }
} else { ?>
<h2>Oups! Aucun article n'a été trouvé</h2>
<?php } ?>
Analyse des versions 1 et 2
Les deux solutions fonctionnent mais la méthode ne me semble pas correcte. Si je donne le tout à intégrateur++ qui peut intégrer n’importe quel template WordPress, saura-t’il faire la même chose avec le mien ?
Réponse négative à mon sens, on ne va pas penser à un count() ou un getFirst() de Doctrine. C’est encore plus vrai dans le sens où venant de cet univers, je n’ai absolument pas pensé à faire cette « opération ». Il faut donc trouver autre chose.
Version 3 – count() dans l’action
<?php
/**
* posts actions
*/
class postsActions extends sfActions
{
public function executeIndex(sfWebRequst $request)
{
$entries = Doctrine::getTable('Posts')->getPosts();
if(count($entries) > 0)
{
$this->entries = $entries;
}
else
{
$this->entries = false;
}
$this->forward404Unless($entries);
}
}
}
Version 4 – count() dans le model
<?php
class postsTable extends Doctrine_Table
{
public function getPosts()
{
$t = $this->createQuery('c')
->where('c.status = ?', 'publish')
->execute();
if(count($t) > 0)
{
return $t;
}
else
{
return NULL;
}
}
}
Analyse des versions 3 et 4
Avec ces deux versions, je peux renvoyer NULL et tester ma variable beaucoup plus simplement (et pour tout le monde). La question est donc de savoir si cela doit être fait dans l’action ou dans le model.
J’y ai donc réfléchis un peu et je trouve que le faire dans le model est plus simple car permet également de vérifier le contenu d’une requête plus rapidement et avec moins de ligne de code dans l’action. De plus, on peut mettre en place des tests comme is_object() ou is_null() à tous les niveaux ce qui ne me semble pas plus mal.
Mais vous, qu’en pensez-vous ? Où avez-vous d’autres moyens ?
- Réflexion autour de l’admin-generator symfony
- Peanut : Semaine 5 – peanutPage (1ere partie)
- sfDoctrineActAsSeoablePlugin et peanutSeoPlugin disponibles sur Github
- Peanut : Semaine 7 – peanutPosts
- peanut : retour d’expériences et nouvelle roadmap






Palleas avril 21st
Moi je ne suis pas d’accord, dans ta table tu lui demandes les résultats de ta table « Post », si la table n’a aucun contenu, c’est normal d’avoir une collection vide. Je m’attendrais à tout sauf à recevoir un null.
0) : ?>
FTW o/
Alexandre avril 21st
C’est pas le problème d’avoir une collection vide, mon problème c’est de savoir comment donner à quelqu’un simplement la possibilité de savoir si c’est vide directement dans le template xD
J’ai 4 moyens et peut être pas le bon d’où la question ouverte
Palleas avril 21st
Ah oui, du coup je vote 2 o//
Clément avril 21st
Je vote 2.
Surtout pas la 4, parce que ton model te retourne une collection, c’est tout à fait logique.
Null est quelque chose de spécial, c’est l’absence de valeur. Or, tu dois obtenir une valeur, une collection, qui ne contient aucun objet, mais une collection quand même.
Pas la 3, parce que là aussi, tu a bien une liste d’entrées, qui est vide, mais pas null.
De même 1 est crade, parce que ça se lit mal. On veut savoir s’il n’y en a aucun, pas si il y en a au moins un. Et le jour ou getFirst change (eventuellement) de comportement, tu l’a dans l’os ^^.
Soit la solution 2, pour rejoindre Palleas. J’ajouterai que, pour ton intégrateur, laisse lui eventuellement une indication, du style « comment tester si une collection est vide pour les nuls ». Et si il n’y pense pas, ou n’y arrive pas, c’est qu’il doit s’améliorer, parce que ca me semble aussi important que d’utiliser la balise
<
address> au lieu de
<
div id= »contact-infos »>.
Sinon très bonne question, les débats amènent toujours de bonnes choses !! A renouveller =).
Clément avril 21st
Pas terrible le systeme de commentaire qui te met des retour à la ligne, surtout sur un blog de programmeur : BOUHHHHHH
Alexandre avril 21st
Je me demande si l’html dans les commentaires part pas en live à cause du plugin markdown en fait ^^. Je jetterais un coup d’oeil la dessus.
pastie.org !
tight avril 22nd
Je vote 2 aussi, mais sans mettre le « > 0″ (if (count($posts)) { … })
Je trouve la 1 (getFirst()) est pas terrible. Tu demandes le premier objet, mais ne l’utilises pas.
Pour les 3 et 4, j’aime pas du tout. Je n’ai pas l’impression qu’en terme de MVC, ces tests soient à la bonne place : tu veux afficher un message pour dire qu’il n’y a pas de Post, on est dans la Vue Et c’est un peu plus verbeux si tu ne veux pas afficher de message (sortie XML par ex), car une simple boucle « foreach » n’est plus suffisante, tu es obligé de tester $posts.
Loris avril 22nd
Solution 5, hydratation en tableau: … ->limit($limit) ->setResultCacheLifeSpan(3600) ->useResultCache(true) ->execute(array(), Doctrine::HYDRATE_ARRAY);
Comme ça, tu reçois un array php, pas de problème pour le template et plus c’est ton apache qui te remerciera de pas lui bouffer toute sa ram avec des doctrine_collection de 25mo
Anarko_bizounours avril 23rd
Il me semble que j’avais eu un problème similaire avec mon plugins de redimensionnement d’image. Si j’avais pas de catégorie il me renvoyais une collection vide, j’ai fait quelque chose comme ça dans le table pour contourné le problème
public function viewPictureCategory($type){
}
moi ça fonctionne, après peut être qu’en modifiant un peu, ca te fera awoogha awoogha awaa awaa awoogha awoogha awaa (see RED DWARF)!
Clément avril 23rd
Loris, vainqueur de la question par KO. Les Doctrine_Collections mangent, comparativement à des arrays, beaucoup plus de mémoire ?
tight avril 23rd
@Loris @Clément Le gros problème des Doctrine_Collections, c’est qu’elles stockent des classes étendant Doctrine_Record (les classes dans lib/model/*.class.php)
La différence en mémoire est clairement non négligeable, et varie pas mal suivant les objets (nombre de colonnes, de relations, de relations « hydratées »). Si tu veux faire le test, remplace un ->execute() par un ->execute(array(), Doctrine::HYDRATE_ARRAY()) et compare la mémoire utilisée (et la vitesse aussi, ça joue) avec la debug toolbar de symfony.
D’un autre côté, les Doctrine_Records permettent plus de choses que les arrays, et sont plus « confortables » à l’utilisation (pour les routes, les méthodes métiers, les requêtes transparentes via ->getNomDeLaRelation() – même si c’est pas forcément une bonne idée, …)
Alexandre avril 23rd
Histoire de continuer dans les différences, j’ai trouvé cet article benchmark : http://www.amicalement-web.net/benchmark-apache-doctrine-hydrate-object-vs-array/2009/10/08/
Georges@Bitbol juin 17th
Pour ma part, le test — si test il y a à faire — doit se faire dans le model. La vue et le controller ne sont pas fait pour cela.
A moins que cela ne pose un problème de ressources ou de temps d’exécution, j ene vois pas pourquoi il faudrait contourner les bases du MVC pour tester si la requête retourne des éléments.
Soit le développeur se débrouille dans sa vue avec l’objet vide retourné soit il décide de faire le test dans son model (ce qui est d’après moi, la solution le plus propre)
Tutu novembre 18th
Je ne sais pas depuis combien de temps date ce flag (c’est dommage, il y a les dates mais pas l’année, on peut donc supposer que ca date de l’année en cours mais aussi avoir des doutes), mais bon, j’y vais de mon petit grain de sel. J’ai découvert récemment une méthode fort utile native avec Doctrine, qui se nomme « find ». « find » tout court sur un Doctrine_Core::getTable(‘Machin’)->find(‘id_machin’) permet de récupérer un objet par son id (attention, il faut donner un array en cas de clé primaire composée), mais permet également de rechercher par champ, dans ton cas, tu aurais pu faire un Doctrine_Core::getTable(‘Posts’)->findByStatus(‘publish’) et il t’aurais renvoyé une Doctrine_Collection de tous les « posts » avec ce status. Ce qui est intéressant à voir, et c’est la raison qui m’a poussé à participer à ce débat, c’est que lorsqu’il ne trouve rien, il ne renvoie pas une Doctrine_Collection vide, mais un booléen valant « false ». C’est pourquoi, pour rester dans la logique de Symfony/Doctrine au sein du projet, je serais partisan à, dans le model, renvoyer « false » si aucun objet n’est trouvé, et fait le test dans l’action directement.
Alexandre novembre 18th
@Tutu : Je n’ai que rarement utilisé les findBy() et n’ai donc jamais pu constater ce que tu dis mais cela relance le débat (qui n’est donc pas prêt d’être clos).
Une autre solution consiste à ne pas executer la requête dans le model mais plutôt dans l’action (dans le model on aurait un retrieveLastPosts() à la place d’un getLastPosts() par exemple).
Cela permet au passage d’affiner l’hydratation et de choisir la collection/l’array suivant l’action/la vue.
Au final, 10 bonnes façons de faire
Thomas janvier 7th
Bonjour,
Pourquoi tu n’utilises pas la fonction de doctrine:
if ($entries->count() > 0)
Ou alors tu parcours directement ton objet
foreach ($entries as $entrie) { }
S’il n’y a pas d’enregistrement, il ne rentrera pas dans la boucle
Clement janvier 7th
Utiliser $entries->count() ne peut se faire que sur une Doctrine_Request, et effectue une requete SQL supplémentaire, puisque le count() est traduit par un COUNT() en SQL.
Pour le foreach, l’idée peut coller, sauf si on ne cherche pas a NE PAS FAIRE quelque chose quand la liste est vide, mais qu’au contraire, on cherche a FAIRE quelque chose si cette liste est vide.
Inveme-online janvier 8th
Merci d’avoir un blog interessant
Add Yours
YOU