Capita spesso di dover attraversare una porzione del filesystem per effettuare operazioni su file e directory in modo ricorsivo; per questo ho creato una semplice classe PHP che implementa l'attraversamento (in modo ricorsivo) e dalla quale creare discendenti per manipolare i file nel modo necessario.

Prerequisiti

Non è necessario un particolare sistema operativo, basta che sia disponibile PHP. Generalmente utilizzo queste utitlity da linea comandi ma nulla impedisce di utilizzarle anche attraverso una pagina web ed, ovviamente, un webserver come Apache o Nginx.

 

La classe folderIterator

<?php 
/*
 * Copyright (C) 2013,2014 Consulanza Informatica
 * 
 * License: GPLv3 - see http://www.gnu.org/licenses/gpl-3.0.html
 *
 */
abstract class folderIterator {
	
	const LOG_NONE = 0;
	const LOG_FATAL = 1;
	const LOG_ERROR = 2;
	const LOG_WARNING = 3;
	const LOG_INFO = 4;
	const LOG_VERBOSE = 5; 
	
	private $_log_level = 1; 
	private $_EOL = "\n";
	private $_ignore_dirs = NULL;
	private $_ignore_files = NULL;
	/**
	 * constructor
	 * @param array $options
	 * 		array of parameters
	 */
	public function __construct($options=array()) {
		if (is_array($options)) {
			$this->logLevel	= (isset($options['loglevel'])) ? $options['loglevel'] : 1;
			$this->EOL		= (isset($options['eol'])) ? $options['eol'] : "\n";
			if (isset($options['ignore_dir'])) {
				if (is_array($options['ignore_dir'])) {
					foreach ($options['ignore_dir'] as $dir) {
						$this->_ignore_dirs[] = $dir;
					}
				} else {
					$this->_ignore_dirs[] = $options['ignore_dir'];
				}
			}
		}
	}
	/**
	 * Set LOG level
	 * @param integer $loglevel
	 * 		LOG level
	 */
	public function setLogLevel($loglevel=folderIterator::LOG_ERROR) {
		$this->_log_level = $loglevel;
	}
	public function getLogLevel() {
		return $this->_log_level;
	}
	/**
	 * Set EOL
	 * Allows to switch between command-line mode (using "\n")
	 * and web mode (using "<br />")
	 * @param string $eol
	 * 		end of line
	 */
	protected function setEOL($eol) {
		$this->_EOL = $eol;
	}
	/**
	 * Get EOL
	 */
	protected function getEOL() {
		return $this->_EOL;
	}
	/**
	 * Add ignore dir name
	 * @param unknown $dir
	 */
	public function addIgnoreDir($dir) {
		$this->_ignore_dirs[] = $dir;
	}
	public function isIgnoreDir($dir) {
		
		return (is_array($this->_ignore_dirs) && in_array($dir,$this->_ignore_dirs));
	}
	/**
	 * display LOG message
	 * @param string $message
	 * 		message string
	 * @param integer $logLevel
	 * 		message LOG level
	 */
	protected function displayLog ($message,$logLevel=folderIterator::LOG_ERROR) {
		if ($this->logLevel >= $logLevel) {
			echo "$message" . $this->EOL;
		}
	}
	/**
	 * Returns file size in human readable format
	 * @param integer size
	 *
	 */
	public function humanFileSize($size) {
		$unit = 'b';
		if ($size > 1024) {
			$size = $size/1024.0;
			$unit = 'kb';
			if ($size > 1024) {
				$size = $size / 1024.0;
				$unit = 'Mb';
				if ($size > 1024) {
					$size = $size / 1024.0;
					$unit = 'Gb';
				}			
			}
		}
		return sprintf('%f %s',$size,$unit);
	}
	
	/**
	 * Optional dir preprocess
	 * Operazioni da eseguire sulle directory prima della ricorsione
	 * @param unknown $path
	 */
	protected function preProcessDir($path,$entry)  {
		
	}
	/**
	 * Optional dir postprocess
	 * Operazioni da eseguire sulle directory dopo la ricorsione
	 * @param unknown $path
	 */
	protected function postProcessDir($path,$entry)  {
	
	}
	
	/**
	 * File process
	 * Operazioni da eseguire per i file trovati. Dichiarata abstract, va
	 * definita nei discendenti
	 * @param unknown $path
	 */
	abstract protected function processFile($path,$entry);

	/**
	 * Recursive scan 
	 * @param string $root
	 * 		start directory
	 * @param boolean $recursive
	 * 		recurse dubdirectories
	 */
	public function scanDir($root,$recursive=true) {
		if (!file_exists($root) || !is_dir($root)) {
			echo "Directory inesistente: $root\n";	
		} else {
			$path = realpath($root);
			$d = dir($path);
			while ( ($entry = $d->read()) !== false ) {
				if ($entry != '.' && $entry != '..') {
					if (is_dir($path .'/' . $entry)) {
						if (!$this->isIgnoreDir($entry)) {
							$this->displayLog(sprintf("DIR: %s".$this->_EOL,$entry),folderIterator::LOG_VERBOSE);
							$this->displayLog(sprintf("DIR PRE process: %s".$this->_EOL,$entry),folderIterator::LOG_VERBOSE);
							$this->preProcessDir($path,$entry);
							$this->displayLog(sprintf("DIR recursion: %s\n",$entry),folderIterator::LOG_VERBOSE);
							if ($recursive) {
								$this->scanDir($path .'/' . $entry,$recursive);
							}
							$this->displayLog(sprintf("DIR POST process: %s".$this->_EOL,$entry),folderIterator::LOG_VERBOSE);
							$this->postProcessDir($path,$entry);
						} else {
							$this->displayLog(sprintf("DIR %s ignored".$this->_EOL,$entry),folderIterator::LOG_VERBOSE);
						}
					} else {
						$this->displayLog(sprintf("FILE: %s",$entry),folderIterator::LOG_VERBOSE);
						$this->processFile($path, $entry);
					}
				}
			}
		}
	}
}

Il cuore della classe è costituito dal metodo scanDir che effettua la scansione della struttura, in modalità ricorsiva se richiesto, permettendoci di operare su tutte le directory ed i file trovati.

Le operazini sono effettuate da diversi metodi per le directory ed i file inoltre per le directory potremo effettuare operazioni diverse prima e dopo l'iterazione sui contenuti delle stesse.

Il metodo processFile($path,$entry)

Questo metodo ci fornisce l'accesso ai file, a cui potremo accedere utilizzando i due parametri:

  • $path: contenente il percorso del file corrente
  • $entry: contenente il nome del file

Il metodo preProcessDir($path,$entry)

Questo metodo è opzionale ed andrà implementato nei discendenti solamente se abbiamo la necessità si manipolare la directory corrente prima di effettuare la scansione dei pripri contenuti. I parametri utilizzati somno gli stessi già visti per il metodo processFile, quindi:

  • $path: contenente il percorso della directory superiore a quella corrente
  • $entry: contenente il nome dela directory

Il metodo postProcessDir($path,$entry)

Questo metodo è opzionale ed andrà implementato nei discendenti solamente se abbiamo la necessità si manipolare la directory corrente dopo aver effettuato la scansione dei pripri contenuti. I parametri utilizzati somno gli stessi già visti per il metodo processFile, quindi:

  • $path: contenente il percorso della directory superiore a quella corrente
  • $entry: contenente il nome della directory