FilesystemIterator, RegexIterator, et FilterIterator

ViPHP
AB
ViPHP | 5818 Messages

08 juil. 2011, 17:55

Bonjour,

Je souhaite faire une recherche sur les fichiers d'un répertoire suivant la première lettre de leur nom. Pour la lettre "a" j'ai donc un motif de regex basique : $matche = '#^a#ui'; .

Mais voilà, cela ne fonctionne pas (ne retourne rien) avec le code suivant :
header('Content-type: text/html; charset=UTF-8');

//fichiers du répertoire : ATV_1.jpg ... ATV_2400.jpg (+ d'autres de nom différents)
$dossier = 'PHOTOS';

$matche = '#^a#ui';

$files = new RegexIterator(new FilesystemIterator($dossier),$matche) ;

foreach ($files as $f) echo($f->getFilename()).'<br />';
Le bug ne se produit pas (et le retour est correct) lorsqu'on enlève le "^" dans le motif du regex.

Par contre cela fonctionne sans problème si j'utilise RegexIterator + DirectoryIterator à la place de RegexIterator + FilesystemIterator. Mais je souhaitais utiliser FilesystemIterator qui est plus rapide que DirectoryIterator car j'aurai potentiellement de très nombreux fichiers dans le répertoire...


Vous avez une explication ? Est-ce un bug ou ai-je manqué quelque chose ?
Modifié en dernier par AB le 18 juil. 2011, 01:49, modifié 5 fois.

Eléphant du PHP | 209 Messages

08 juil. 2011, 18:06

L'expression match sur le chemin complet du fichier et pas sur son nom ?
$dossier = '/tmp/';
$matche = "#^{$dossier}a#ui";

$FSI = new FilesystemIterator($dossier);
$files = new RegexIterator($FSI,$matche) ;


foreach ($files as $f) echo($f->getFilename()).'<br />';
--
Eric

ViPHP
AB
ViPHP | 5818 Messages

11 juil. 2011, 20:46

Salut,

Pas d'autres idées ?

ViPHP
ViPHP | 5462 Messages

11 juil. 2011, 21:10

FilesystemIterator retourne le path pas le nom du fichier

utilise RegexIterator::USE_KEY et FilesystemIterator::KEY_AS_FILENAME

ViPHP
AB
ViPHP | 5818 Messages

11 juil. 2011, 22:42

epommate2 m'avait mis sur la voie, mais j'avais pas su exploiter.

T'as bien fait d'insister :)

En fait il fallait déclarer le mode du RegexIterator avant le flag USE_KEY. Je pensais qu'il devait y avoir une valeur par défaut et ce qui ne m'a pas aidé c'est que cela ne renvoyait pas de message d'erreur, car effectivement la valeur du flag RegexIterator::USE_KEY (=1) à une correspondance dans le mode RegexIterator::GET_MATCH (=1) #-o

Bon donc le code complet avec RegexIterator et FilesystemIterator:
<?php
	
$dossier = 'PHOTOS/';

$matche = '#^a#ui';

$f = new FilesystemIterator($dossier, FilesystemIterator::KEY_AS_FILENAME);
		
$r = new RegexIterator($f, $matche, RegexIterator::MATCH, RegexIterator::USE_KEY);
// équivalent $r = new RegexIterator($f, $matche, 0, 1);

foreach ($r as $t) 
			{
				echo $t->getFilename();
			}
?>
Modifié en dernier par AB le 12 juil. 2011, 18:03, modifié 1 fois.

ViPHP
ViPHP | 5462 Messages

11 juil. 2011, 23:00

sinon tu pouvais faire ton propre filtre ou utilise GlobIterator :wink:

ViPHP
AB
ViPHP | 5818 Messages

12 juil. 2011, 17:58

sinon tu pouvais faire ton propre filtre ou utilise GlobIterator :wink:
A propos de filtre, si l'on doit utiliser un regex, on a quand même intérêt à utiliser RegexIterator préalablement à FilterIterator plutôt que de mettre un preg_match dans le filtre. La vitesse de traitement est très sensiblement améliorée.

Test utilisé pour vérifier la différence de vitesse :
(modifier les variables $dossier et $matche suivant vos besoins)
$dossier = 'PHOTOS/';


class Filtre_result_match extends FilterIterator {
 
    public function accept() {
		
		$it = $this->getInnerIterator();
		$it_name = $it->current()->getFilename();
		$matche = '#^a#ui';
		return (!$it->isDot() && !$it->isDir() && $it_name != '.htaccess' && preg_match($matche,$it_name));	
    }

}


$time = microtime(true);

$f = new FilesystemIterator($dossier);
$result = new Filtre_result_match ($f);

foreach ($result as $value) {
    echo $value->getFilename();
}

$time_end = microtime(true);
$time_tot = $time_end - $time;
echo 'durée exécution FilesystemIterator + FilterIterator + preg_match' . $time_tot.'<br />';


echo '<br />';
echo '<br />';


$matche = '#^a#ui';

class Filtre_result extends FilterIterator {
 
    public function accept() {
		
		$it = $this->getInnerIterator();
		$it_name = $it->current()->getFilename();
		return (!$it->isDot() && !$it->isDir() && $it_name != '.htaccess');	
    }

}


$time = microtime(true);

$f = new FilesystemIterator($dossier,FilesystemIterator::KEY_AS_FILENAME);
$r = new RegexIterator($f, $matche, RegexIterator::MATCH, RegexIterator::USE_KEY);

$result = new Filtre_result ($r);

foreach ($result as $value) {
    echo $value->getFilename();
}

$time_end = microtime(true);
$time_tot = $time_end - $time;
echo 'durée exécution FilesystemIterator + RegexIteraor + FilterIterator ' . $time_tot.'<br />';

ViPHP
ViPHP | 5462 Messages

13 juil. 2011, 20:44

pour quoi faire avec un regexp un substr suffit, voir meme
$it_name[0] === 'a'

ViPHP
AB
ViPHP | 5818 Messages

14 juil. 2011, 19:58

Parce que mon exemple de regex est minimaliste et n'est pas représentatif de tout ce que je veux faire. Je veux également pouvoir faire des recherche sur des suites de lettres ou plusieurs suites de groupes de lettres, sans que le fichier commence nécessairement par une lettre désignée etc., j'ai donné cet exemple car c'est dans ce cas que le regex posait problème avec FilesystemIterator.

Sinon attention avec la méthode qui consiste à désigner des lettres à l'aide d'un index dans une chaine de caractères car cela peut poser de gros problèmes avec l'utf-8 dans certains cas. Et plutôt que d'essayer de gérer cette méthode suivant les circonstances, j'aurai tendance à dire qu'il vaut mieux simplement éviter de l'utiliser avec l'utf-8.

ViPHP
AB
ViPHP | 5818 Messages

17 juil. 2011, 12:17

Bonjour,

Assez surprenant, je viens d'utiliser ta méthode (un peu plus performante de 7 à 8 % par rapport à un strpos($it_name,'a')===0) dans un filtre avec cette seule condition, et elle est toujours deux fois plus longue qu'un RegexIterator avec le match équivalent #^a#ui. Comme quoi le RegexIterator semble très optimisé :)

Je voudrais maintenant connaître la position des fichiers (par rapport à mon répertoire) pour les fichiers triés, soit combien le regexIerator a dû passer de fichiers en revue avant de matcher le premier fichier ? Si je fais une recherche avec #^c#ui je voudrais savoir combien de fichiers se trouvent avant le premier qui commence par "c". Je me dis que ce devrait être assez simple ... faut croire que je regarde pas au bon endroit. Y'a une méthode simple pour connaître ça à partir de mon code de base ?
<?php
       
$dossier = 'PHOTOS/';

$matche = '#^a#ui';

$f = new FilesystemIterator($dossier, FilesystemIterator::KEY_AS_FILENAME);
               
$r = new RegexIterator($f, $matche, RegexIterator::MATCH, RegexIterator::USE_KEY);

foreach ($r as $t)
                        {
                                echo $t->getFilename();
                        }
?>

ViPHP
ViPHP | 5462 Messages

17 juil. 2011, 19:47

peu être avec :
$t->getInnerIterator()->key();
mais comme la clé est le nom de fichier ...

ViPHP
AB
ViPHP | 5818 Messages

18 juil. 2011, 01:44

Et oui la clé n'a rien à voir avec la position du curseur, d'ailleurs même si j'emploie les filtres à la place du regexIterator et donc sans utiliser FilesystemIterator::KEY_AS_FILENAME, je me retrouve avec le pathname dans la clé. Je cherche encore désespérément une trace de la position du curseur #-o Et pas certain qu'elle soit quelque part sinon il y aurait certainement une fonction inverse de la fonction seek 8-|

Sinon bah je vais boucler benoitement sur mon FilesystemIterator avec le preg_match dans la boucle. La différence avec l'utilisation de regexIterator est de moins 20 à 30% (moins dans certains cas) mais cela reste encore 60% plus rapide (dans de nombreux cas) que si je met un regex dans un filtre.

Bilan de mes tests, regexIterator est optimisé mais par contre FilterIterator est très moyen.

Cela dit si certains peuvent me dire comment retrouver la position du curseur suite à regexIterator... ce sera bienvenu :)
Modifié en dernier par AB le 19 juil. 2011, 03:55, modifié 1 fois.

ViPHP
AB
ViPHP | 5818 Messages

18 juil. 2011, 02:17

Si cela peut servir à certains voici la batterie de tests utilisée. Si vous avez des améliorations à suggérer...

$dossier = 'PHOTOS/';

$matche = '#^j#ui';



class Filtre_fin extends FilterIterator {

public $posi = 0;
public $match;
public $test=true;
	
	public function _construct($iterator) {}	
	
	public function accept() 
		{
			
			if (preg_match($this->match,parent::current()->getFilename())) 
				{
					$this->test = false;
					return true;
				}
				
			if ($this->test)  $this->posi++;
		}
}


$time = microtime(true);

$dir = new FilesystemIterator($dossier,FilesystemIterator::KEY_AS_FILENAME);
$f2 = new Filtre_fin($dir);
$f2->match = $matche;


foreach ($f2 as $key => $value) echo $key.'<br />';


$time_end = microtime(true);
$time_tot = $time_end - $time;

echo '<br />';

echo 'index = '.$f2->posi.'<br />';
echo 'match = '.iterator_count($f2).'<br />';

echo '<br />';

echo 'durée FilesystemIterator  + FilterIterator et preg_match : ' . $time_tot.'<br />';

echo '<br />';
echo '<br />';






$time = microtime(true);

$files_s = new FilesystemIterator($dossier,FilesystemIterator::KEY_AS_FILENAME);

$i = 0;
$j = 0;
$m = true;

foreach ($files_s as $key => $value) 
{
	if (preg_match($matche,$key)) {echo $key.'<br />';$m=false;$j++;}
	if ($m) $i++;
}

$time_end = microtime(true);
$time_tot = $time_end - $time;

echo '<br />';

echo 'index = '.$i.'<br />';
echo 'match = '.$j.'<br />';

echo '<br />';


echo 'durée FilesystemIterator et preg_match : ' . $time_tot.'<br />';

echo '<br />';
echo '<br />';







$time = microtime(true);

$files_s = new FilesystemIterator($dossier,FilesystemIterator::KEY_AS_FILENAME);
$r = new RegexIterator($files_s, $matche, RegexIterator::MATCH, RegexIterator::USE_KEY);

foreach ($r as $key => $value)  echo $key.'<br />';

$time_end = microtime(true);
$time_tot = $time_end - $time;

echo '<br />';

echo 'index = introuvable <br />';
echo 'match = '.iterator_count($r).'<br />';

echo '<br />';

echo 'durée exécution FilesystemIterator + RegexIteraor : ' . $time_tot.'<br />';

echo '<br />';
echo '<br />';
A noter que dans tous les cas j'utilise FilesystemIterator::KEY_AS_FILENAME car j'ai remarqué un petit gain de performance du fait d'afficher directement $key plutôt que $value->getFilename().

ViPHP
AB
ViPHP | 5818 Messages

19 juil. 2011, 00:13

Bonjour,

Bon finalement la solution que j'ai retenue pour trouver les index est d'utiliser DirectoryIterator ce qui me permet par ailleurs de pourvoir utiliser conjointement RegexIterator :D
$f = new DirectoryIterator($dossier);

$f = new RegexIterator($f,$matche);

foreach ($f as $value) echo $value->key().' - '.$value->getFilename().'<br />'; 
En performances c'est aussi rapide (à très peu près) que FilesystemIterator::KEY_AS_FILENAME couplé à RegexIterator::MATCH, RegexIterator::USE_KEY

Juste il faut noter que l'index retourné prend en compte les '.' et '..' des répertoires donc le résultat est augmenté de deux par rapport au visuel du répertoire.
Modifié en dernier par AB le 19 juil. 2011, 02:04, modifié 2 fois.

Eléphant du PHP | 171 Messages

19 juil. 2011, 00:43

RegexIterator est il très bénifique lors de l'utilisation d'expression régulière ou c'est que dans certains cas particuliers ? Merci d'avance :wink:
Le bon jugement s'apprend par l'expérience qui s'acquiert en partie par le mauvais jugement.