DirectoryIterator, ma source est crade ?

Signaler
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
-
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
-
Bonjour,

je suis habitué à utiliser des Iterator et à en créer des simples... mais sur la classe DirectoryIterator je voudrais effectuer un tri.

Merci d'avance à ceux qui maitrisent très bien le sujet de me corriger si c'est pas bon (je ne parle pas des améliorations du code mais surtout de la METHODE de mon code) :

class OFDirectoryIterator extends DirectoryIterator
{
    private $tabRes;
    private $sort;
    
    const SORT_ASC = 'ASC';
    const SORT_DESC = 'DESC';
    
    public function __construct($path)
    {
        parent::__construct($path);
        
        $tabRes = array();
        foreach ($this as $file)
        {
            $myFile['isfile'] = $file->isFile();
            $myFile['filename'] = $file->getFilename();
                
            $this->tabRes[] = $myFile;
        }
    }
    
    private function alphaSort($a, $b)
    {
        if ($this->sort === SORT_ASC )
            return strcmp($a['filename'], $b['filename']);
        else
            return strcmp($b['filename'], $a['filename']);
    }
    
    public function setAlphaSort($constant_sort)
    {
        $this->sort = $constant_sort;
        usort($this->tabRes, array($this,'alphaSort'));
    }
    
    public function getResultIterator()
    {
        return $this->tabRes;
    }
    
}


exemple d'utilisation :

$dir = new OFDirectoryIterator ($path);
$path->setAlphaSort(OFDirectoryIterator ::SORT_ASC);

foreach ($dir->getResultIterator() as $value)
{
  ...
}


<--St@iLeR-->

13 réponses

Messages postés
2483
Date d'inscription
jeudi 30 novembre 2006
Statut
Membre
Dernière intervention
14 janvier 2011
15
Salut,

Je ne comprends pas pourquoi tu utilises strcmp pour trier ?
Tu ne vérifies pas que l'argument de setAlphaSort a une valeur autorisée (l'une des constantes possibles).
Je ne comprends pas comment tu utilises ton iterateur... En fait, ton itérateur, n'est pas utilisé pour itérer : il produit, avec la méthode getResultIterator() non pas un iterateur, mais un tableau.

Je pense que tu ne fais pas ce qu'il faut.
Ton itérateur n'itère pas : si on parcours l'objet avec foreach, on obtient les éléments, dans l'ordre naturel, c'est à dire dans l'ordre croissant.
Si on veut réellement obtenir quelque chose de trié, il faut utiliser getResultIterator qui ne renvoit même pas ce qu'elle prétend : un Array au lieu d'un Iterator. La moindre des choses serait de renvoyer un ArrayIterator.

Je pense que ton itérateur devrait permettre de réellement parcourir le répertoire par ordre décroissant.
C'est lors du construct que tu devrais stocker les noms des fichiers dans ton tableau. Tu ne devrais pas non plus filtrer les fichiers réguliers, mais également laisser les liens et les répertoires, conformément au comportement d'orgine de DirectoryIterator.
Il faut réécrire les méthodes de base de l'itérateur (next(), current(), rewind(), valid(), key() ) pour qu'elles utilisent le contenu du tableau.
Alors seulement ton itérateur sera un vrai itérateur, avec tri croissant/décroissant.
Messages postés
10840
Date d'inscription
lundi 24 février 2003
Statut
Modérateur
Dernière intervention
2 mars 2010
22
Hello,

Neige a raison : tu utilises la SPL et n'en tire finalement pas partie. Autant faire un sort($aDir = scandir('mondir')) ou pareil avec un rsort(), ça en revient au même et ce sera nettement plus optimisé.
Un itérateur, c'est fait pour se déplacer "ligne à ligne"...c'est un curseur. Imposer un ordre d'affichage à ce compte me parait curieux. Surtout sans index. A mon sens, il est intéressant de se pencher sur le problème et de trouver une parade sympa...mais je doute fort que ce soit réellement utile par contre. Ce serait juste un problème intéressant à résoudre. Tu ne pourras sans doute jamais obtenir un truc performant.
L'idée serait du coup en effet de redéfinir les méthodes d'iteration de base...par exemple, avec un filterIterator dynamique : il va filtrer sur l'élément le plus "bas" dans la hiérarchie, puis le stocke, puis cherche celui juste au-dessus, etc...mais c'est lourd.
Le strcmp() est, dans ce sens, une bonne idée justement. Mais pas utilisé ainsi.
Brefn l'idée est correcte, mais tu es hors du sujet que tu t'es imposé ;-) Tu ne fais pas de l'itération.
A la limite, tu peux ne pas utiliser un simple array() pour stocker ton répertoire, mais un ArrayIterator, qui lui implémente des méthodes de tri. Mais là, ça devient très simple ;-) Trop!
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
Oui c'était débile ,effectivement ça n'itere pas.

ceci dit, le code suivant ne serait il pas mieux tout de même :

<?php
function sortAlpha($a, $b)
{
    return strcmp($a['filename'],$b['filename']);
}

class OFDirectoryIterator extends ArrayObject
{
    private $compteur = 0;
   
    public function __construct($path)
    {
        $list = new DirectoryIterator($path);

        foreach ($list as $file)
        {
            $myFile['isfile'] = $file->isFile();
            $myFile['filename'] = $file->getFilename();
                   
            $this->append($myFile);
        }
       
        $this->ksort( 'sortAlpha');
       
    }
}
?>

exemple d'utilisation :
$iter = new OFDirectoryIterator($path);
       
foreach ($iter as $f) {
         echo $f['filename'].'
';
 }

A terme, ce tableau serait remplacé pas un objet pour l'évolution de la classe, et de nouvelles méthodes "utilitaires" seraient intégrées.

ps: j'ai intégré le tri directement sur le constructeur mais bien entendu ça ne fonctionnera pas comme ça ... c'est juste pour avoir votre avis avant de continuer.

<--St@iLeR-->
Messages postés
2483
Date d'inscription
jeudi 30 novembre 2006
Statut
Membre
Dernière intervention
14 janvier 2011
15
Re,

Si tu veux faire un itérateur sur un répertoire, la meilleure classe à étendre, c'est vraiment DirectoryIterator. Pas ArrayObject.
Ton itérateur OFDirectoryIterator va itérer sur un répertoire. La manière dont il itère dessus, tu vas la réécrire. Pour permettre de parcourir depuis la fin vers le début (tri décroissant), ton itérateur, puisqu'étant écrit en PHP et non en C dans l'extension SPL, devra gérer la liste avec sa propre méthode : via un tableau me parait tout à fait adapté. Certes, on y perd un peu en performances, du fait que tu parcours une première fois le répertoire pour récupérer les fichiers, stocker leur nom dans un tableau, trier le tableau avant d'itérer réellement comme on veut. N'empêche que l'intérêt de la classe est de permettre le tri suivant certains critères (ordre alphabétique, date de création, date de modification, nom de l'utilisateur propriétaire, etc : pourquoi ne trier que sur le nom ?) avec une syntaxe extrêmement lisible et facile à maintenir (et tant pis pour la double boucle !)

Donc :
- dans ton constructeur, tu parcours le répertoire et stockes les noms de fichiers dans un tableau.
- dans ton constructeur, toujours, tu tries le tableau suivant le critère passé en argument
- tu itères sur le tableau qui contient les éléments dans le bon ordre.

Pour ne rien perdre des méthodes de DirectoryIterator, il faut que toutes celles-ci soient utilisables avec ta classe étendue (sinon, aucun intérêtà ta classe, on peut se contenter de tout faire soi-même : parcourir le répertoire, et stocker les fichiers dans un tableau, le trier, et afficher dans l'ordre qu'on veut. Pas besoin de classe pour faire juste ça. Si, par contre, on peut itérer sur le répertoire trié dans le sens qu'on veut et qu'on peut utiliser toutes les méthodes de DirectoryIterator (isFile(), isLink(), getADate(), etc) alors c'est VRAIMENT intéressant, en terme d'utilisation et de syntaxe.

Quand je serai de retour chez moi, je reprendrai certainement ma dernière source pour y ajouter le tri... Je me rends compte que j'y ai ajouté des trucs sympa (à mes yeux en tout cas), mais j'avais oublié le tri...
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
Mais justement le but c'est de faire toutes sortes de tris.... Tu as vu ça : $this->ksort( 'sortAlpha');
c'est uniquement pour l'exemple, je vais bien évidemment faire des méthodes pour trier dans le  sens ou l'ou vent etc.

ensuite tu as vu :
    $myFile['isfile'] = $file->isFile();
    $myFile['filename'] = $file->getFilename();

le but c'est que ça devienne :
 $myFile = new MyFile();
  $myFile->isFile = $file->isFile();

    $myFile->Filename = $file->getFilename();
etc... avec des traitements spéciaux dans cette classe MyFile qui permettront de définir pleins d'autres choses (récupérer uniquement les images jpg par exemple.. enfin bref, une classe évolutive)

Donc pourquoi pas étendre de DirectoryIterator... mais pourquoi s'embêter à redéfinir toutes les méthodes de base alors qu'ArrayObject me semble bien adapter ,avec des méthodes internes pour le tri ?

<--St@iLeR-->
Messages postés
2483
Date d'inscription
jeudi 30 novembre 2006
Statut
Membre
Dernière intervention
14 janvier 2011
15
"Donc pourquoi pas étendre de DirectoryIterator... mais pourquoi s'embêter à redéfinir toutes les méthodes de base alors qu'ArrayObject me semble bien adapter ,avec des méthodes internes pour le tri ?"

Parce que ArrayObject n'offre pas les méthodes de DirectoryIterator.
Tu parcours un répertoire, pas un tableau. Tu veux stocker les données de l'itérateur dans un tableau, tu peux éventuellement utiliser un itérateur comme ArrayIterator ou ArrayObject, ton itérateur OFDirectoryIterator étant alors un wrapper pour cet itérateur. Jette un coup d'oeil aux tutos de malalam sur les Design Patterns.
Jette aussi un oeil à ma classe de listing de répertoire... J'ai fait un Iterateur avec Filtres : je n'ai pas utilisé FilterIterator, parce que je ne pouvais pas étendre 2 classes : j'ai donc réécrit les méthodes de l'itérateur pour qu'il se comporte comme un FilterIterator, même si ce n'en est pas officiellement un.

Bref. Quand on parcourt un répertoire, on aime avoir accès à toutes les informations que donne DirectoryIterator sur le fichier courant. ArrayObject peut te servir pour parcourir les noms de fichiers dans l'ordre que tu veux, comme Iterateur interne. Ainsi, tu bénéficies des fonctionnalités de chaque, en respectant la logique de programmation : tu itères sur un répertoire, pas sur un tableau...
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
Tu parlais par exemple de la SPL... j'ai fait ça en vitesse :

class MyFile extends SplFileInfo
{

}

class OFDirectoryIterator extends ArrayObject
{
    function sortAlpha($a, $b) {
        return strcmp($a->getFilename(),$b->getFilename());
    }
   
    public function __construct($path)
    {
        $list = new DirectoryIterator($path);

        foreach ($list as $file)
        {
            $myFile = new MyFile($path.$file);
            $this->append($myFile);
        }
       
        $this->ksort($this,array('sortAlpha'));
    }
}

à partir de la je peux me faire des méthodes pour trier comme je veux il me semble
<--St@iLeR-->
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
... ou faire un petit système qui permet au programmeur de trier commeil veut ;)

j'aurais donc :
1- toutes les infos possibles sur le fichier dans ma boucle
2 - tous les tris possibles avant d'exécuter ma boucle, et des tris 'spécifiques' que je pourrais ajouter à mon arrayobject

C'est un peu lourd ok, mais ça me semble plus simple qu'avec DirectoryIterator et plus intuitif à développer...
maintenant je me trompe peut-être
<--St@iLeR-->
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
Voilà un début plus approfondi,et je trouve que c'est pas mal (j'ai changé ArrayObject par ArrayIterator) :

class OFDirectoryIterator extends ArrayIterator
{
    const SORT_ASC = 'ASC';
    const SORT_DESC = 'DESC';
   
    private $value1 = 'a';
    private $value2 = 'b';
   
    public function __construct($path)
    {
        $list = new DirectoryIterator($path);

        foreach ($list as $file) {
            $this->append(new SplFileInfo($path.$file)); }
    }
   
    function __call($method, $args = array())
    {
        if ($args[0] === self::SORT_DESC ) {
            $this->value1 = 'b';
            $this->value2 = 'a';
        }
   
        switch ($method) {
            case 'setSortAlpha':
            case 'setSortMTime':
                uasort($this, array(get_class($this), str_replace('set','',$method))); break;
        }
    }
   
    private function SortAlpha($a, $b) {
        return strcasecmp(${$this->value1}->getFilename(),${$this->value2}->getFilename());
    }

    private function SortMTime($a, $b)
    {
        if (${$this->value1}->getMTime() ===  ${$this->value2}->getMTime()) return 0;
           return (${$this->value1}->getMTime() > ${$this->value2}->getMTime()) ? -1 : 1;
    }
}

Exemple :
$files = new OFDirectoryIterator ($path);
$files->setSortAlpha(OFDirectoryIterator::SORT_DESC);
foreach ($files as $file)
{
  echo $file->getFilename().'
';
}

Qu'en pensez-vous ?
Messages postés
10840
Date d'inscription
lundi 24 février 2003
Statut
Modérateur
Dernière intervention
2 mars 2010
22
Hello,

l'avantage, c'est que ça fonctionne.
Je pense quand même qu'on peut faire mieux en réflêchissant un peu (je ne suis pas en état de réflêchir, là, désolé, et j'ai pas trop le temps).
Sur ton code, 2-3 trucs quand même : __call() ne sert pas à grand chose ici et complique l'utilisation de ton code pour rien. Autant faire une méthode sort() en lui passant une constante de classe, et point barre.
get_class($this) est équivalent, dans ton cas, à __CLASS__, et tu économises un appel de fonction.
Réflêchis quand même...de toute manière, tu construis un tableau. Tu peux mapper chaque élément de ton tableau à un SplFileInfo. Et ainsi, faciliter ensuite les fonctions de tri sans avoir besoin de construire ton tableau, et bénéficier quand même des infos sur tes fichiers :

    public function __construct($sDir = '.') {
        parent::__construct(array_map(array($this, 'mapFileInfo'), scandir($sDir)));
    }

    private static function mapFileInfo($v) {
        return new SplFileInfo($v);
    }

Ou un truc dans le genre.
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
Hm... ton construct marche mais ça pète mon système de filtre... je vais poster la source demain ou lundi, on verra bien les commentaires... Par contre j'ai effectivement remplacé get_class($this) par __CLASS__ et j'avais déjà commencé à modifier DirectoryIterator par scandir ;)

<--St@iLeR-->
Messages postés
10840
Date d'inscription
lundi 24 février 2003
Statut
Modérateur
Dernière intervention
2 mars 2010
22
L'avantage de DirectoryIterator, d'un autre côté, c'est que tu peux utiliser RecursiveDirectoryIterator...ça va être plus pénible avec scandir et du récursif.
Enfin, c'est à creuser.
Messages postés
507
Date d'inscription
jeudi 28 mars 2002
Statut
Membre
Dernière intervention
13 mai 2009
1
En ce qui me concerrne je vais faire une classe spéciale à mon application pour récurser un répertoire... Etant donné que chaque item retourne est un SplFileInfo, je n'ai juste qu'à ajouter une méthode getChildren à cette classe qui me retournera mon OF_DirectoryIterator pour le répertoire enfant. La encore, je ne peux utiliser ton construct avec ce système.

Ce sera donc un peu plus lourd, mais complètement utilisable et personnalisable pour mon besoin.
J'upload ce que j'ai fait dans quelques minutes ;)

<--St@iLeR-->