8721 sujets

Développement web côté serveur, CMS

Bonjour,

Pour un besoin spécifique, je dois chercher tous répertoires contenant paquet.xml et plugin.xml. Par la suite, si dans un répertoire j'ai paquet.xml et plugin.xml, je dois prendre en compte que paquet.xml. S'il n'y a que paquet.xml ou plugin.xml dans le répertoire, on "passe".

Je recherche les fichiers ainsi :
$result = glob('*/*/{plugin,paquet}.xml',GLOB_BRACE);
. Il me liste donc les résultats sans soucis.

Mais je ne vois pas comment attaquer ça… Comment comparer et enlever les "doublons"?
J'ai la flemme de tester, mais juste par curiosité ça donne quoi si tu utilise un "pipe" au lieu d'une virgule?

Genre comme ça :

$result = glob('*/*/{plugin|paquet}.xml',GLOB_BRACE);

Modifié par Charlycoste (16 Jul 2013 - 17:06)
Merci pour la réponse.

En fait, c'est ok, j'ai trouvé entre temps la réponse. (mea culpa, j'aurai dû la mettre ne ligne).

Ça donne ceci :

// correspond à plugins/nom_plugin/fichier.xml
if (glob(_DIR_PLUGINS . '*/{paquet,plugin}.xml',GLOB_BRACE)) {
	foreach (glob(_DIR_PLUGINS . '*/{paquet,plugin}.xml',GLOB_BRACE) as $value) {
		$list[] = $value;
	}
}
// correspond à plugins/auto/nom_plugin/fichier.xml
if (glob(_DIR_PLUGINS . '*/*/{paquet,plugin}.xml',GLOB_BRACE)) {
	foreach (glob(_DIR_PLUGINS . '*/*/{paquet,plugin}.xml',GLOB_BRACE) as $value) {
		$list[] = $value;
	}
}
// correspond à plugins/auto/nom_plugin/x.y.z/fichier.xml
if (glob(_DIR_PLUGINS . '*/*/*/{paquet,plugin}.xml',GLOB_BRACE)) {
	foreach (glob(_DIR_PLUGINS . '*/*/*/{paquet,plugin}.xml',GLOB_BRACE) as $value) {
		$list[] = $value;
	}
}


// Ici on va prendre les chemins d'extrusion uniquement, sans distinction du fichier xml
foreach ($list as $value) {
	$extract[] = str_replace(array('plugin.xml','paquet.xml'), '', $value);
}
// On dédoublonne
$extract = array_unique($extract);
foreach ($extract as $url) {
	// Et on refait une recherche pour paquet.xml d'abord
	if(glob($url . 'paquet.xml', GLOB_NOSORT)) {
		$result = glob($url . 'paquet.xml', GLOB_NOSORT);		
		$result = $result[0] ;
		// dans paquet.xml on cherche la valeur de l'attribut prefix
		if (preg_match('/prefix="([^"]*)"/i', file_get_contents($result), $r) 
			AND !$lsplugs[strtolower(trim($r[1]))]){
				preg_match('/version="([^"]*)"/i', file_get_contents($result), $n);
				$inutile[] = trim($r[1]) . ' (' . $n[1] . ')';
		}

	} else { // Si pas de paquet.xml, on cherche plugin.xml
		$result = glob($url . 'plugin.xml', GLOB_NOSORT);		
		$result = $result[0] ;
		// là, on reprend l'ancien code. On cherche la valeur de la balise prefix
		if (preg_match(',<prefix>([^<]+),ims', file_get_contents($result), $r)
			AND !$lsplugs[strtolower(trim($r[1]))]){
				preg_match(',<version>([^<]+),ims', file_get_contents($result), $n);
				$inutile[] = trim($r[1]) . ' (' . $n[1] . ')';
		}
	}
}


Comme je l'avais dit (plus ou moins), je désire n'avoir qu'un fichier.xml par répertoire pour ne pas avoir de "doublons". paquet.xml est prioritaire sur le fichier plugin.xml.
En gros, je liste tous les fichiers paquet.xml et plugin.xml. Puis, je les enlève de mon array. Ce nouveau tableau est traité avec array_unique(). Ce qui va me donner un chemin d'extraction unique.
Je fais un foreach sur chaque élément de mon tableau pour prendre paquet.xml et en retirer les infos que j'ai besoin. Si pas de paquet.xml, je regarde le fichier plugin.xml.

Et voilà. Smiley cligne
Ton sujet m'a donné envie de regarder du côté des Iterateurs récursifs histoire de voir s'il n'y avait pas plus élégant comme solution que de tester les 3 niveaux.

J'ai de temps en temps des résultats en double (genre 2 fois le même fichier qui sort) et pas trop de temps pour chercher d'où ça vient, cependant si ça peut aider par la suite:

<?php
class MyRecursiveFilterIterator extends RecursiveFilterIterator
{
  // liste des fichiers intéressant par ordre de priorité
  public static $FILTERS = array('paquet.xml', 'plugin.xml');

  // Fichier choisi pour être conservé
  protected $selected;

  public function __construct(RecursiveIterator $it, $selected = null)
  {
    parent::__construct($it);

    // Si selected a une valeur, alors je suis un itérateur sur un dossier qui contient les fichiers voulus
    // (le choix du fichier à garder à été fait par mon père)
    // sinon, je suis un itérateur sur un dossier et je cherche si je contiens un fichier intéressant
    if($selected == null) {
      $this->selected = $this->search();
    }
  }

  // cherche dans l'itérateur actuel (un RecursiveDirectoryIterator) si un fichier intéressant est présent
  protected function search()
  {
    foreach (self::$FILTERS as $f)
    {
      foreach ($this->getInnerIterator() as $file)
      {
        if(!$file->isDir() && in_array($file->getFilename(), self::$FILTERS))
        {
          return $file;
        }
      }
    }
    return null;
  }

  // accept() est appelé pour chaque fichier/dossier de l'itérateur courant
  // Si le fichier choisi est égal au fichier courant, on l'accepte
  // Si aucun fichier n'est choisi on n'accepte que les répertoires (les autres fichiers ne nous intéressent pas)
  public function accept()
  {
    if ($this->selected)
      return $this->selected->getPathname() == $this->current()->getPathname();
    else
      return $this->current()->isDir();
  }

  // On poursuit le parcours récursif en précisant quel fichier à été choisi au niveau supérieur
  // Si rien n'a été trouvé $this->selected sera nul
  public function getChildren()
  {
    return new self($this->getInnerIterator()->getChildren(), $this->selected);
  }
}


<?php
$it = new RecursiveDirectoryIterator('/tmp/test', FilesystemIterator::SKIP_DOTS);
$it = new MyRecursiveFilterIterator($it);
$it = new RecursiveIteratorIterator($it);


foreach ($it as $filePath => $fileInfo) {
    switch($fileInfo->getFilename())
    {
       case "paquet.xml":
         // code pour paquet.xml
         break;
       case "plugin.xml":
          // code pour plugin.xml
          break;
    }
}


L'idée c'est d'utiliser la classe RecursiveDirectoryIterator et de filtrer son résultat avec un RecursiveFilterIterator.

En gros, ici, peu importe le nombre de niveau. On parcours les répertoires à la recherche soit de paquet.xml soit de plugin.xml.

Si on tombe sur l'un on s'arrête. Sinon on continue à chercher plus profond.

Nous sommes d'accord que dans ton cas c'est pas moins verbeux mais c'est plus facilement réutilisable.
Modifié par Charlycoste (17 Jul 2013 - 12:44)
Bonjour,

Merci pour cette réponse. C'est vrai que je n'ai pas pensé à regarder du côté des class…
Je vais tester ce code.

L'inconvénient que je vois ici est la longueur du code. Mais son avantage sur le papier est qu'il est plus souples en profondeur. Je ne sais pas si "mes profondeurs" sont à rallonge ou pas. Ou si je dois les limiter comme dans mon premier code (1,2,3 niveaux).
moust a écrit :
Sinon pour te simplifier la vie tu peux aussi regarder du côté du composant Finder de Symfony


Le composant Finder est un wrapper pour utiliser les itérateurs PHP. Le problème ici, c'est que l'on ne doit prendre qu'un seul résultat sur les deux, il faudra donc nécessairement que RenyonParis développe une classe adaptateur pour intégrer sa logique de recherche. S'il utilise déjà Symfony, pourquoi pas? Mais dans le cas contraire, je trouve ça superflu.
RenyonParis a écrit :
L'inconvénient que je vois ici est la longueur du code. Mais son avantage sur le papier est qu'il est plus souples en profondeur.


En réalité, il est surement possible de faire plus simple avec une simple fonction récursive. Le tout est de savoir quelle type de structure tu peux avoir.

Si cette arborescence est possible:


A
 -> paquet.xml
 -> B
      -> paquet.xml


Alors il te faut un parcours total.

Si le fait que paquet.xml ne peut pas être dans B parce qu'il est déjà dans A. Alors un parcours partiel serait plus optimal. (Mon code est nul, il fait un parcours total. Mais normalement c'est surement possible d'y arriver)

Dans tous les cas il me semble nécessaire tant en terme de performance qu'en terme d'élégance de :

1. Récupérer un seul des deux fichiers candidats lors du parcours pour ne pas avoir à retraiter le tableau après
2. Utiliser les objets splFileInfo pour avoir à la fois accès au nom du fichier et à son chemin

switch( $file->getFilename() )  // => "paquet.xml" par exemple
{
  case : "paquet.xml": mon_traitement_paquet( $file->getPathname() ); break; // => "/a/b/c/paquet.xml"
  case : "plugin.xml": mon_traitement_plugin( $file->getPathname() ); break;
}


Peu importe après si tu utilise une classe ou une fonction, bien qu'utiliser un RecursiveFilterIterator permet de séparer l'aspect "parcours" de l'aspect "système de fichier". C'est particulièrement utile pour des tests, car tu peux utiliser une autre classe que RecursiveDirectoryIterator pour simuler une arborescence plutôt que de t'embêter à créer des dossiers pour chaque cas.
Modifié par Charlycoste (21 Jul 2013 - 14:19)
Sinon :


<?php
$files = array('paquet.xml', 'plugin.xml');
$dir = __DIR__;

$dir_it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($dir_it, RecursiveIteratorIterator::SELF_FIRST);

foreach($it as $path => $fo){
  if ( !$fo->isDir() ) {
    continue;
  }

  $path .= '/';

  foreach ($files as $file) {
    if ( file_exists($path . $file) ) {
      echo "Traite $file dans $path\n";
      break;
    }
  }
}


Mais c'est peut être trop simple ?
Modifié par jb_gfx (21 Jul 2013 - 18:49)
jb_gfx a écrit :
Mais c'est peut être trop simple ?


Bah disons que ça rejoint l'histoire du "parcours total ou parcours partiel?"
Dans ta proposition, c'est un parcours total obligatoirement.

Ce qui me gêne plus c'est le nombre de tests nécessitant des entrée/sortie (=> file_exists() ). A voir si cela a un impact important sur les performances.

Surtout que, si je ne m'abuse, pour un dossier /a/ et un fichier /a/plugin.xml, en réalité tu test /a/paquet.xml, puis /a/plugin.xml, ça match donc tu break la boucle foreach($files), puis le foreach($it) suivant te fait tester /a/plugin.xmlpaquet.xml et ensuite /a/plugin.xmlplugin.xml

Comme ils n'existent pas, soit! Cela fonctionne. Mais bon...

(Après... Je peux me tromper... Il est 23h30 Smiley langue )
Charlycoste a écrit :
(Après... Je peux me tromper... Il est 23h30 Smiley langue )


Oui tu te trompe, je ne teste aucun fichier (isDir() en début de boucle quitte si on est pas sur un dossier (le bien mal nommé "continue" sert à quitter l'itération courante)). Sur le teste d'existence du fichier si paquet.xml est présent je break, donc plugin.xml n'est pas testé dans ce cas (vu que paquet.xml est prioritaire). Ce sont les 2 seuls fichiers qui sont éventuellement testés. Smiley cligne

EDIT : j'ai lu de travers ta réponse, effectivement ça teste la présence de ces 2 fichiers dans tous les répertoires ! Suite à la mise à jour du cahier des charges (lol) j'ai rajouté un test sur le format de nom de dossier. Ça devrait aller. Smiley smile

OK avec toi sur le parcours de toute l'arborescence mais j'ai le sentiment que ce script n'est pas destiné à être lancé toutes les 2 secondes. On a pas l'info donc c'est juste une intuition. Cela dit sur un petit benchmark rapide j'ai trouvé que ça risquait pas de tuer le serveur si on fait pas des milliers de visiteurs par jour (même sur un mutu). Smiley langue
Modifié par jb_gfx (22 Jul 2013 - 00:09)
Bonjour,

En fait, voici une arborescence :

- plugins
-- nom_plugin
--- paquet.xml et/ou plugin.xml

- plugins
-- nom_plugin
--- x.y.z (numéro de version du plugin. Exemple : 1.4.2)
---- paquet.xml et/ou plugin.xml

- plugins
-- auto
--- nom_plugin
---- x.y.z (numéro de version du plugin. Exemple : 1.4.2)
----- paquet.xml et/ou plugin.xml

Je n'utilise pas de Framework. En fait, c'est pour CMS open source. J'ai mis à jour un plugin de mutualisation du core du CMS. Ce plugin permettra, entre autres, de lister les plugins présents sur la mutualisation. Ensuite, on les compare (grâce aux préfixes) avec ceux installés sur les différents sites mutualisés.

Pour le moment, le plugin prend en compte tous les plugins installés pour "tous" les sites (public_html/plugins/) et pas ceux installés pour un site particulier (public_html/sites/nom_du_site/plugins/).

Voilà le topo. Smiley cligne
Ok, donc à mon avis tu peux y aller avec ma version ça devrait rouler sans problème.

J'avais fait un test complet pour faire un benchmark rapide. Voilà le code, peut-être à ajuster un peu par rapport à tes variables (on a pas tout dans ton exemple) :


<?php
$dir = __DIR__;
$inutile = array();
$lsplugs = array();

$cfg = array(
 array(
   'fn' => 'paquet.xml',
   'pre' => '/prefix="([^"]*)"/i',
   'ver' => '/version="([^"]*)"/i',
 ),
 array(
   'fn' => 'plugin.xml',
   'pre' => ',<prefix>([^<]+),ims',
   'ver' => ',<version>([^<]+),ims',
 ),
);

function processConfig(&$cfg, &$lsplugs, $path) {
  echo "Process: " . $path . $cfg['fn'] . "\n";

  $data = file_get_contents($path . $cfg['fn']);

  if ( 1 === preg_match($cfg['pre'], $data, $r) AND !$lsplugs[strtolower(trim($r[1]))] ) {
    preg_match($cfg['ver'], $data, $n);
    return trim($r[1]) . ' (' . $n[1] . ')';
  }

  return false;
}

$dir_it = new RecursiveDirectoryIterator($dir, FilesystemIterator::SKIP_DOTS);
$it = new RecursiveIteratorIterator($dir_it, RecursiveIteratorIterator::SELF_FIRST);

foreach ( $it as $path => $fo ) {

  if ( !$fo->isDir() || 0 === preg_match('#[1-9\.]+#', $fo->getFileName()) ) {
    continue;
  }

  $path .= '/';

  foreach ( $cfg as $k => $v ) {
    if ( file_exists($path . $v['fn']) ) {
      $res = processConfig($cfg[$k], $lsplugs, $path);
      if ( false !== $res ) {
      	$inutile[] = $res;
      }
      break;
    }
  }
}


Version optimisée qui ne teste que les dossiers du genre 1.0, 1, 1.2.3, etc.
Modifié par jb_gfx (22 Jul 2013 - 00:11)