Simplified Recursive PHP - php

I've been working on a method of including files recursively with the __autoload() function in php. This way, You could throw your class anywhere in the "classes" folder, and have them organized by sub directory, But the __autoload function would still be able to find them. This is what I've gotten so far, And was wondering if anyone might be able to help me simplify it so that it isn't so lengthy. It is completely functional currently, And works like a charm. I'm just trying to make it shorter.
<?php
function readRecursive($path){
if(!is_dir($path)){
return false;
}
$dir = glob($path ."/*");
$retArr = array();
foreach($dir as $f){
if(is_dir($f)){
$m = readRecursive($f);
foreach($m as $n){
$retArr[] = $n;
}
}else{
$retArr[] = $f;
}
}
return $retArr;
}
function endsWith($haystack, $needle){
return $needle === "" || substr($haystack, -strlen($needle)) === $needle;
}
/* Set up AutoLoading for object classes */
function __autoload($class_name){
$classes = readRecursive("classes");
foreach($classes as $class){
if(endsWith(strtolower($class), strtolower($class_name.".class.php"))){
include_once ($class);
}
}
}
?>

Here's my attempt at this autoload for you.
I've slightly modified Emil Condrea's Answer.
To start, I'll show you the file structure of my classes:
As you can see above, the classes are set into seperate files and such to show.
Now taking Emil's answer and slightly changing it:
(Provided the file name is something like "Class.php" as seen above in the file structure)
function getClasses($path) {
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$pathname = $item->getPathName();
$filename = $item->getFileName();
if ($item->isDir()) {
getClasses($item);
} else {
$files[$filename] = $pathname;
}
}
return $files;
}
Will warrant a return array of files like the following [FILE_NAME] => [PATH_NAME]:
Array
(
[Error.php] => /home2/DERP/public_html/something.com/watch/model/Site/Error.php
[Form.php] => /home2/DERP/public_html/something.com/watch/model/Site/Form.php
[Site.php] => /home2/DERP/public_html/something.com/watch/model/Site/Site.php
[Db.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db.php
[Db_pdo.php] => /home2/DERP/public_html/something.com/watch/model/Database/Db_pdo.php
[Session.php] => /home2/DERP/public_html/something.com/watch/model/Security/Session.php
[Auth.php] => /home2/DERP/public_html/something.com/watch/model/Security/Auth.php
[Input.php] => /home2/DERP/public_html/something.com/watch/model/Security/Input.php
[Postcode.php] => /home2/DERP/public_html/something.com/watch/model/Postcode.php
[Rep.php] => /home2/DERP/public_html/something.com/watch/model/User/Rep.php
[User.php] => /home2/DERP/public_html/something.com/watch/model/User/User.php
[Notifications.php] => /home2/DERP/public_html/something.com/watch/model/User/Notifications.php
[Log.php] => /home2/DERP/public_html/something.com/watch/model/Log/Log.php
[Hook.php] => /home2/DERP/public_html/something.com/watch/model/Hook.php
)
Now that would've been called by something like the following:
getClasses(realpath(dirname(__FILE__)) . '/model')
Allowing us to run an __autoload() like the following:
$model_classes = getClasses(realpath(dirname(__FILE__)) . '/model');
function __autoload($class_name) {
global $model_classes;
$filename = ucfirst($class_name) . '.php';
$model = $filename;
if (!isset($model_classes[$model])) {
// dead
return false;
} else {
// include first file (model)
include($model_classes[$model]);
}
}
Now
Obviously you shouldn't use global but to me it seemed a far better alternative to running the getClasses() function every single time within the __autoload() function.
If someone else has anything to add, feel free! I just tried my own little method on this and it works without fault!
Note: I was using file_exists() before and the above method is, in my opinion; a lot faster.
UPDATE
I had a brain wave just the other night and thought;
"Why not scan the application root and fetch all the php files then run a function to check if said file actually contains a class to make this as universal as possible.."
So I did a little research and found this nifty little function from php: token_get_all()
Now after a little digging through SO I found this answer: Determine class in file...
and after some modification, the getClasses() function now looks like this:
function getClasses($path) {
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$pathname = $item->getPathName();
$filename = $item->getFileName();
if ($item->isDir()) {
get_classes($item);
} else {
if (substr($filename, -4) === '.php') {
if (get_php_classes(file_get_contents($pathname))) {
$files[$filename] = $pathname;
}
}
}
}
return $files;
}
With the addition of this new function from the above question:
function get_php_classes($php_code) {
$tokens = token_get_all($php_code);
$class_token = false;
foreach ($tokens as $token) {
if (is_array($token)) {
if ($token[0] == T_CLASS) {
$class_token = true;
} else if ($class_token && $token[0] == T_STRING) {
$classes[] = $token[1];
// $class_token = false;
}
}
}
return $class_token;
}
Now that allows you to simply run a $classes = getClasses(ROOTPATH) and itterate through them.
DOWNFALL: each of the classes will have to have unique class names and/or file names. Unless somebody could lend their hand at a modification to allow.

You can use
RecursiveDirectoryIterator to iterate through the directory recursively. This might simplify your function.
function getRecursive($path){
$files = array();
$dir_iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),RecursiveIteratorIterator::SELF_FIRST);
foreach ($dir_iterator as $item) {
$subPath = $dir_iterator->getSubPathName();
if($item->isDir())
$files[$subPath] = array();
else
$files[$subPath][] = $subPath;
}
return $files;
}

Related

PHP - Directory browsing from recursive to iterative

Hello I am trying to make the following function iterative. It browses threw all directories and gives me all files in there.
function getFilesFromDirectory($directory, &$results = array()){
$files = scandir($directory);
foreach($files as $key => $value){
$path = realpath($directory.DIRECTORY_SEPARATOR.$value);
if(!is_dir($path)) {
$results[] = $path;
} else if($value != "." && $value != "..") {
getFilesFromDirectory($path, $results);
$results[] = $path;
}
}
return $results;
}
I am sure that it is possible to make this function iterative but I really have no approach how I can do this.
Your going to want to use a few PHP base classes to implement this.
Using a RecursiveDirectoryIterator inside of a RecursiveIteratorIterator will allow you to iterate over everything within a directory regardless of how nested.
Its worth noting when looping over the $iterator below each $item is an object of type SplFileinfo. Information on this class can be found here: http://php.net/manual/en/class.splfileinfo.php
<?php
//Iterate over a directory and add the filenames of all found children
function getFilesFromDirectory($directory){
//Return an empty array if the directory could not be found
if(!is_dir($directory)){
return array();
}
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory)
);
$found = array();
foreach($iterator as $item){
if(method_exists($item, 'isFile') && $item->isFile()){
//Uncomment the below to exclude dot files
//if(method_exists($item, 'isDot') && $item->isDot()){
// continue;
//}
//Pathname == full file path
$found[] = $item->getPathname();
}
}
return $found;
}
An var_dump of some found files i did using this function as a test:
Hope this helps!

PHP RecursiveDirectoryIterator return but do not traverse directory matching "x"

I am writing a disk catalogue application in PHP. My script loops over directories, storing all filenames and metadata in a database. There are certain directories I do not want to travel down. I want the iterator to simply return the names of those directories as though they are files, then move onto the next sibling. I have implemented a RecursiveCallbackFilterIterator that allows omitting directories based on a matching filename pattern:
$filter = array(".app");
$files = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator(
$zpath,
RecursiveDirectoryIterator::SKIP_DOTS
),
function ($current, $key, $iterator) use ($filter) {
$match = 0;
foreach ($filter as $skip) {
if (substr($current->getBaseName(), -4, 4) == $skip) {
$match = 1;
}
}
if ($match) {
return false;
} else {
return true;
}
}
),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD
);
foreach ($files as $splFileInfo) {
$path = $splFileInfo->getRealPath();
echo $path."\n";
}
My question is, how do I modify this code so that directories matching the pattern are included in the result set, but not returned to the iterator for further traversal?
So far, all examples of RecursiveCallbackFilterIterator I've found show some variation on the above (eg, omit certain files or directories). I simply want to return the directory name if it matches the pattern, then move on to the next sibling.
In other words, I need to turn this:
File1.txt
File2.txt
Folder1/
Folder1/FileA.txt
Folder1/FileB.txt
MyThing.app/
MyThing.app/Contents/
Mything.app/Contents/Manifest.plist
Mything.app/Menu.nib
Portfolio.zip
Zee.txt
Into this:
File1.txt
File2.txt
Folder1/
Folder1/FileA.txt
Folder1/FileB.txt
MyThing.app
Portfolio.zip
Zee.txt
I've created an eval.in to test this, though on this env I cannot create Directories, so I've testet only with files, but should work the same with dir too.
file_put_contents("./file2.txt", "test");
file_put_contents("./Zee.txt", "test");
file_put_contents("./fileA.txt", "test");
file_put_contents("./fileB.txt", "test");
file_put_contents("./manifest.plist", "test");
file_put_contents("./manifest.app", "test");
file_put_contents("./MyApp.app", "test");
file_put_contents("./Menu.nib", "test");
$zpath=realpath("./");
$filter = array(".app");
$appFolders =array();
$files = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator(
$zpath,
RecursiveDirectoryIterator::SKIP_DOTS
),
function ($current, $key, $iterator) use ($filter) {
foreach ($filter as $skip) {
preg_match_all("(".$skip.")", $current->getRealPath(), $result);
if (!empty($result[0])) {
$GLOBALS["appFolders"][] =$current->getRealPath();
return false;
}
}
return true;
}
),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD
);
echo "\nFiles:\n";
foreach ($files as $splFileInfo) {
$path = $splFileInfo->getRealPath();
echo $path."\n";
}
echo "\nAppFolders:\n";
foreach ($appFolders as $app){
echo $app."\n";
}
And the output is:
Files:
/tmp/execpad-4917ea112c86/file2.txt
/tmp/execpad-4917ea112c86/input-4917ea112c86
/tmp/execpad-4917ea112c86/manifest.plist
/tmp/execpad-4917ea112c86/Menu.nib
/tmp/execpad-4917ea112c86/output-4917ea112c86
/tmp/execpad-4917ea112c86/fileA.txt
/tmp/execpad-4917ea112c86/fileB.txt
/tmp/execpad-4917ea112c86/Zee.txt
/tmp/execpad-4917ea112c86/source-4917ea112c86
AppFolders:
/tmp/execpad-4917ea112c86/MyApp.app
/tmp/execpad-4917ea112c86/manifest.app
Thanks to Edwin's answer, I was able to create this code that is working perfectly. I thought I would share it here in case it is helpful to anyone else. The key was that I needed to learn more about the methods available to splFileInfo, particularly Path. By checking Path, it is possible to know if the parent, and not the filename, contains a wildcard. Combining this with fnmatch, we can then surmise if a file is downstream from a ".app" dir, and skip that branch entirely, while still including the parent. Thanks Edwin!
// Do not descend into matching directories
$wopt_nodescend = array("*.app", "*.sparsebundle");
// Ignore matching files and directories
$wopt_ignore = array(".DS_Store", "*.jdk");
$nodescended = 0;
$ignored = 0;
$files = new RecursiveIteratorIterator(
new RecursiveCallbackFilterIterator(
new RecursiveDirectoryIterator(
$zpath,
RecursiveDirectoryIterator::SKIP_DOTS
),
function ($current, $key, $iterator) use ($wopt_ignore, $wopt_nodescend) {
global $nodescended, $ignored;
$clean = true;
if (is_array($wopt_ignore)) {
foreach ($wopt_ignore as $wildcard) {
if (fnmatch($wildcard, $current->getFilename())) {
$clean = false;
$ignored++;
echo "Skipping: ".$current->getFilename()."\n";
}
}
}
if (is_array($wopt_nodescend)) {
foreach ($wopt_nodescend as $wildcard) {
if (fnmatch($wildcard, $current->getPath())) {
$clean = false;
$nodescended++;
echo "Nodescending: ".$current->getFilename()."\n";
}
}
}
return $clean;
}
),
RecursiveIteratorIterator::SELF_FIRST,
RecursiveIteratorIterator::CATCH_GET_CHILD
);

How to get the number of files in the folder recursive in php

I have folder structure like below in my web application.
How can I get the number of files in each folder recursively using php.
I'm working with Codeignaiter.
I haven't any idea for how to do this task.
I can't use scandir function because there are no physical
directory in path.Only files and folders save in database.
This is my database
Please any help needed.
Thank you.
Try this function by passing the path as a parameter in it:
function getFileCount($path) {
$size = 0;
$ignore = array('.','..','cgi-bin','.DS_Store');
$files = scandir($path);
foreach($files as $t) {
if(in_array($t, $ignore)) continue;
if (is_dir(rtrim($path, '/') . '/' . $t)) {
$size += getFileCount(rtrim($path, '/') . '/' . $t);
} else {
$size++;
}
}
return $size;
}
If you would like to get, say, all the *.php files in your project folder, recursively, you could use the following: Source
<?php
$Directory = new RecursiveDirectoryIterator('path/to/project/');
$Iterator = new RecursiveIteratorIterator($Directory);
$Regex = new RegexIterator($Iterator, '/^.+\.php$/i', RecursiveRegexIterator::GET_MATCH);
?>
$Regex will contain a single index array for each PHP file.
You can extend RecursiveArrayIterator to get kind of RecursiveDirectoryIterator but for your virtual filesystem:
class RecursiveVirtualDirectoryIterator extends RecursiveArrayIterator
{
private $files;
public function __construct($parentId, $array = [], $flags = 0)
{
$this->files = $array;
parent::__construct(
$this->getFilesByParentId($parentId),
$flags
);
}
private $children;
public function hasChildren()
{
$file = $this->current();
if ($file['is_file']) {
return false;
}
$this->children = $this->getFilesByParentId($file['id']);
return !empty($this->children);
}
private function getFilesByParentId($id)
{
return array_filter($this->files, function ($file) use ($id) {
return $file['parent_id'] === $id;
});
}
public function getChildren()
{
$file = $this->current();
return new static(
$file['id'],
$this->children,
$this->getFlags()
);
}
}
Then you can iterate over your array returned from databases using RecursiveIteratorIterator and count files for, say, topmost folders:
$iterator = new RecursiveIteratorIterator(
new RecursiveVirtualDirectoryIterator(0, $files),
RecursiveIteratorIterator::SELF_FIRST
);
$currentDirectoryName = null;
$filesCount = [];
foreach ($iterator as $file) {
if ($iterator->getDepth() === 0 && !$file['is_file']) {
$currentDirectoryName = $file['name'];
$filesCount[$currentDirectoryName] = 0;
continue;
}
$filesCount[$currentDirectoryName] += 1;
}
Here is working demo.
While Standard PHP Library (SPL) is poorly documented, it contains many useful things that save you from reinventing the wheel over and over again.

Recursive function scanning a folder and storing it in an array

I am trying to make a recursive function to go through all of the folder path that I have given it in the parameters.
What I am trying to do is to store the folder tree into an array for example I have Folder1 and this folder contains 4 text files and another folder and I want the structure to be a multidimensional array like the following
Array 1 = Folder one
Array 1 = text.text.....So on so forth
I have the following function that I build but its not working as I want it too. I know that I need to check whether it is in the root directory or not but when it becomes recursive it becoems harder
function displayAllFolders($root)
{
$foldersArray = array();
$listFolderFile = scandir($root);
foreach($listFolderFile as $row)
{
if($row == "." || $row == "..")
{
continue;
}
elseif(is_dir("$root/$row") == true)
{
$foldersArray["$root/$row"] = "$row";
$folder = "$root/$row";
#$foldersArray[] = displayAllFolders("$root/$row");
}
else
{
$foldersArray[]= array("$root/$row") ;
}
}
var_dump($foldersArray);
}
Using RecursiveDirectoryIterator with RecursiveIteratorIterator this becomes rather easy, e.g.:
$it = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
// root dir
'.',
// ignore dots
RecursiveDirectoryIterator::SKIP_DOTS
),
// include directories
RecursiveIteratorIterator::SELF_FIRST
// default is:
// RecursiveIteratorIterator::LEAVES_ONLY
//
// which would only list files
);
foreach ($it as $entry) {
/* #var $entry \SplFileInfo */
echo $entry->getPathname(), "\n";
}
Your approach isn't recursive at all.
It would be recursive if you called the same function again in case of a directory. You only make one sweep.
Have a look here:
http://php.net/manual/en/function.scandir.php
A few solutions are posted. I would advise you to start with the usercomment by mmda dot nl.
(function is named dirToArray, exactly what you are tryting to do.)
In case it will be removed, I pasted it here:
function dirToArray($dir) {
$result = array();
$cdir = scandir($dir);
foreach ($cdir as $key => $value) {
if (!in_array($value,array(".",".."))) {
if (is_dir($dir . DIRECTORY_SEPARATOR . $value)) {
$result[$value] = dirToArray($dir . DIRECTORY_SEPARATOR . $value);
}
else {
$result[] = $value;
}
}
}
return $result;
}
Why not using PHP itself? Just have a look at the RecursiveDirectoryIterator of the standard php library (SPL).
$folders = [];
$iterator = new RecursiveDirectoryIterator(new RecursiveDirectoryIterator($directory));
iterator_apply($iterator, 'scanFolders', array($iterator, $folders));
function scanFolders($iterator, $folders) {
while ($iterator->valid()) {
if ($iterator->hasChildren()) {
scanFolders($iterator->getChildren(), $folders);
} else {
$folders[] = $iterator->current();
}
$iterator->next();
}
}

Get all modules, controllers and actions from a Zend Framework application

I want to create a Zend Controller for ACL management so my problem is: How can I get all Module names, Control names and Action names in a Zend application to build a ACL Control?
I use Zend_Navigation and if the resource don't exist in your ACL Zend_Navigation is thrown a exception. And I want to use a database to deny and allow access. So I must build the database first. And if I must do that by hand it's a pain to do that.
This may be an old question but this is how I am doing this...
// $front = Zend_Controller_Front::getInstance(); // use this line instead on a model class
$front = $this->getFrontController(); // this in controller
$acl = array();
foreach ($front->getControllerDirectory() as $module => $path) {
foreach (scandir($path) as $file) {
if (strstr($file, "Controller.php") !== false) {
include_once $path . DIRECTORY_SEPARATOR . $file;
$class = substr($file,0,strpos($file,".php"));
if (is_subclass_of($class, 'Zend_Controller_Action')) {
$controller = strtolower(substr($file, 0, strpos($file, "Controller")));
$methods = array();
foreach (get_class_methods($class) as $method) {
if (strstr($method,"Action") != false) {
array_push($methods,substr($method,0,strpos($method,"Action")));
}
}
}
$acl[$module][$controller] = $methods;
}
}
}
I have created a function that can get all the actions, controllers and modules from a zend application. Here it is:
$module_dir = substr(str_replace("\\","/",$this->getFrontController()->getModuleDirectory()),0,strrpos(str_replace("\\","/",$this->getFrontController()->getModuleDirectory()),'/'));
$temp = array_diff( scandir( $module_dir), Array( ".", "..", ".svn"));
$modules = array();
$controller_directorys = array();
foreach ($temp as $module) {
if (is_dir($module_dir . "/" . $module)) {
array_push($modules,$module);
array_push($controller_directorys, str_replace("\\","/",$this->getFrontController()->getControllerDirectory($module)));
}
}
foreach ($controller_directorys as $dir) {
foreach (scandir($dir) as $dirstructure) {
if (is_file($dir . "/" . $dirstructure)) {
if (strstr($dirstructure,"Controller.php") != false) {
include_once($dir . "/" . $dirstructure);
}
}
}
}
$default_module = $this->getFrontController()->getDefaultModule();
$db_structure = array();
foreach(get_declared_classes() as $c){
if(is_subclass_of($c, 'Zend_Controller_Action')){
$functions = array();
foreach (get_class_methods($c) as $f) {
if (strstr($f,"Action") != false) {
array_push($functions,substr($f,0,strpos($f,"Action")));
}
}
$c = strtolower(substr($c,0,strpos($c,"Controller")));
if (strstr($c,"_") != false) {
$db_structure[substr($c,0,strpos($c,"_"))][substr($c,strpos($c,"_") + 1)] = $functions;
}else{
$db_structure[$default_module][$c] = $functions;
}
}
}
}
I actually found the best way to have an easily available reflection reference was to recursively tokenise the correct directories and then build an xml document as a result. Caching the xml document for speed and using xpath for retrieving the data.
The plugin builds the reflection xml and caches it for later. I've taken this code out of its original implementation, so its more to give you a feel rather than copy and paste.
Of course, a database works just as well here. But if you're trying to limit your queries per page, a cached xml doc works pretty well.
class My_Reflection_Plugin extends My_Controller_Plugin_Abstract
{
public function routeShutdown(Zend_Controller_Request_Abstract $request)
{
$cache = $this -> getCacheManager() -> getCache('general');
if (!$xml = $cache->load("Reflection"))
{
$paths = array(
PATH_APPLICATION . "/Core",
PATH_SITE . "/Project"
);
foreach ($paths as $path)
{
$this -> inspectDir($path);
}
$cache -> save($this->getReflectionXML(), "Reflection");
}
else
{
$this -> getReflectionXML($xml);
}
}
private function inspectDir($path)
{
$rdi = new RecursiveDirectoryIterator($path);
$rii = new RecursiveIteratorIterator($rdi);
$filtered = new My_Reflection_Filter($rii);
iterator_apply($filtered, array($this, 'process'), array($filtered));
}
private function process($it = false)
{
$this -> getReflectionXML() -> addItem($it -> current());
return true;
}
}
Tokenisation happens inside the filter:
class My_Reflection_Filter extends FilterIterator
{
public function accept()
{
$file = $this->getInnerIterator()->current();
// If we somehow have something other than an SplFileInfo object, just
// return false
if (!$file instanceof SplFileInfo) {
return false;
}
// If we have a directory, it's not a file, so return false
if (!$file->isFile()) {
return false;
}
// If not a PHP file, skip
if ($file->getBasename('.php') == $file->getBasename()) {
return false;
}
// Resource forks are no good either.
if (substr($file->getBaseName(), 0, 2) == '._')
{
return false;
}
$contents = file_get_contents($file->getRealPath());
$tokens = token_get_all($contents);
$file->className = NULL;
$file->classExtends = NULL;
$file->classImplements = array();
$last = null;
while (count($tokens) > 0)
{
$token = array_shift($tokens);
if (!is_array($token))
{
continue;
}
list($id, $content, $line) = $token;
switch ($id)
{
case T_ABSTRACT:
case T_CLASS:
case T_INTERFACE:
$last = 'object';
break;
case T_EXTENDS:
$last = "extends";
break;
case T_IMPLEMENTS:
$last = "implements";
break;
case T_STRING:
switch ($last)
{
case "object":
$file -> className = $content;
break;
case "extends":
$file -> classExtends = $content;
break;
case "implements":
$file -> classImplements[] = $content;
break;
}
break;
case T_WHITESPACE:
// Do nothing, whitespace should be ignored but it shouldnt reset $last.
break;
default:
// If its not directly following a keyword specified by $last, reset last to nothing.
$last = null;
break;
}
}
return true;
}
}
Once you have your reflection xml populated with whatever information you need out of the class, your acl plugin can come after it and query that information with xpath.
I don't think there is a solution for this in Zend. You will have to do it yourself...
One way to do it, is to list all classes, and check if the classes extend (for example) the Zend_Controller_Action class...
check the php functions get_declared_classes and is_subclass_of
foreach(get_declared_classes() as $c){
if(is_subclass_of($c, 'Zend_Controller_Action')){
...
}
}

Categories