PHP Class for working with Directories and Files


The code documented here was originally posted as a single page as is. It just didn’t feel right, it felt lazy and not what this site is about, which is some kind of commentary or notes even though it’s not always extensive, so here you are, the verbose version.

Most seasoned devs probably got a similar class of their own already, however, if you are a newbie this piece is definitely a good read.

Source Code

static function dirAsArr(&$d){ 
  $arr = array(); 
  for($i = 0; false !== ($entry = $d->read()); $i++){ 
    if($entry != "." && $entry != "..")      
      array_push($arr,$entry); 
  } 
  return $arr; 
}

We require a directory object (created with dir), passed as reference.

I just noticed that the above way of looping with for is pointless because we make no use of $i, a while would have been better. Anyway, the main thing here is that we grab everything except . and .. which are system symbols. The double dots for instance refer to the parent directory and I’ve yet to experience a situation where walking upwards too is wanted behavior, and I probably never will. That’s why we leave them out, most of what we’re trying to achieve would simply break otherwise.

I can’t remember why I’m using array_push here, I might not have realized that I just as easily could do $arr[] = $entry.

static function listDir($strDir){
	$d = dir($strDir);
	return self::dirAsArr($d);
}

By using the above dirAsArr we can now return an array with all file and directory names in any given directory whose name is passed as a string in the single argument.

static function listFilesInDir($strDir){
	$d = dir($strDir);
	$temp = self::dirAsArr($d);
	$rarr = array();
	foreach($temp as $item){
		if(is_file("$strDir/$item")){
			$rarr[] = $item;
		}
	}
	return $rarr;
}

Similar do the above listDir method but this time we don’t want to work with sub directories, only the files contained in $strDir. That’s why we use is_file to check every item, if it isn’t a file we don’t include it in the resultant array.

static function clearDir($strDir){ 
    $files = self::listFilesInDir($strDir); 
    foreach($files as $file){ 
        unlink("$strDir/$file"); 
    } 
}

Here we use our new listFilesInDir function to get all the files in a directory in order to delete them.

static function replaceInFile($fileName, $needle, $replacement){ 
      $lines = file($fileName); 
      foreach($lines as &$line){ 
        $line = str_replace($needle, $replacement, $line); 
    } 
    return $lines; 
}

We open a file through $fileName and replace all instances of $needle with $replacement. This is identical to the replace all in document function in any text editor.

static function searchCountInFile($fileName, $needle){ 
      $lines = file($fileName); 
      if($lines === false) 
          return 0; 
      $count = 0; 
      foreach($lines as &$line){ 
            $count += substr_count($line, $needle); 
    } 
     
    return $count; 
}

Similar to the above replaceInFile but now we count all instances of $needle instead.

static function emptyWrite2D(&$arr, $fileName){ 
      $handle = fopen($fileName, "w+"); 
      foreach($arr as $line){ 
        fwrite($handle, $line); 
    } 
    fclose($handle); 
}

Easy enough, we output the contents of an array to a file. I don’t really know why it has 2D at the end there. Anyway it’s a good example on how fopen, fwrite and fclose are used in conjunction with each other.

static function replaceInDir(&$dir, $needle, $replacement){ 
      $fileNames = self::dirAsArr($dir); 
      foreach($fileNames as $fileName){ 
            $fileName = $dir->path."/".$fileName; 
        $fileAsArr = self::replaceInFile($fileName, $needle, $replacement); 
        self::emptyWrite2D($fileAsArr, $fileName); 
    } 
}

We walk through a directory with and use the above replaceInFile function to replace a $needle with $replacement in a whole directory. The emptyWrite2D function is used to resave each file. I can’t for the life of me understand why I use dirAsArr directly here, now that I review the code it feels like list_files_in_dir would’ve been a much better choice since I don’t recurse into sub directories.

static function getTargetsInDir(&$dir, $needle){ 
      $fileNames = self::dirAsArr($dir); 
      $files = array(); 
      foreach($fileNames as $fileName){ 
            $fileName = $dir->path."/".$fileName; 
        if(self::searchCountInFile($fileName, $needle) > 0) 
            array_push($files, $fileName); 
    } 
     
    return $files; 
}

Another variation, instead of replacing a needle in all files in a directory we count them and return an array with the names of all files where the count is bigger than zero. Again, I don’t understand why I didn’t use list_files_in_dir instead of dirAsArr.

static function download_file($filename, $ctype){ 
    header("Pragma: public"); // required 
    header("Expires: 0"); 
    header("Cache-Control: must-revalidate, post-check=0, pre-check=0"); 
    header("Cache-Control: private",false); // required for certain browsers  
    header("Content-Type: $ctype"); 
    header("Content-Disposition: attachment; filename=\"".basename($filename)."\";" ); 
    header("Content-Transfer-Encoding: binary"); 
    header("Content-Length: ".filesize($filename)); 
    readfile("$filename"); 
}

And finally we have this recursive masterpiece which will take a reference to an array and store the result in it:

static function listFilesInTree($strDir, &$res, $grab = array()){
	$d = dir($strDir);
	$temp = self::dirAsArr($d);
	foreach($temp as $item){
		if(is_file("$strDir/$item")){
			if(empty($grab))
				$res[] = "$strDir/$item";
			else{
				$ext = substr($item, -4, 4);
				if(in_array($ext, $grab))
					$res[] = "$strDir/$item";
			}
		}else if(is_dir("$strDir/$item")){
			if(strpos($item, '.') !== 0)
				self::listFilesInTree("$strDir/$item", $res, $grab);
		}
	}
}

We have the path to begin with in $strDir, the result will be stored in $res and $grab can be used to extract only the paths to files which for instance ends with .php and php5 if $grab is array(‘php’, ‘php5’). As you can see we take all file paths in the whole directory tree. Note that we also do not enter hidden directories.

This one will create a file download of the file whose name is stored in $filename, $ctype is describing the type of the file (MIME) so that the correct software can be used for “open with” in the download dialog (for instance application/msword if you want to open with Microsoft Word).

Note the use of basename, filesize, readfile and of course header to build the headers we need to send to the browser.


Related Posts

Tags: , ,