PHP RecursiveIteratorIterator: The system cannot find the path specified? - php

I have this error when I want to autoload classes with RecursiveIteratorIterator and spl_autoload_register,
uncaught exception 'UnexpectedValueException' with message
'RecursiveDirectoryIterator::__construct(): The system cannot find the
path specified. (code: 3)
What does it mean?
Below is my class autoloader,
function autoload_multiple_directory($class_name){
// List all the class directories in the array.
$array_directories = array(
'core/controller/',
'core/model/',
'core/helper/',
'core/ext/'
);
$parts = explode('\\', $class_name);
// Set the class file name.
$file_name = end($parts).'.php';
foreach($array_directories as $path_directory){
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path_directory),
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject) {
if ($fileObject->isDir()) {
$files[] = str_replace('\\', '/', $fileObject->getPathname()).'/';
}
}
}
$array_directories = array_merge($array_directories,$files);
// Loop the array.
foreach($array_directories as $path_directory){
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name)){
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
}
}
spl_autoload_register('autoload_multiple_directory');
The error line is pointing to new RecursiveDirectoryIterator($path_directory), why?

I must use absolute path in new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$path_directory) as the init.php is called via ajax sometimes.

Related

cakephp - scandir failed to open dir error

I'm tryin to get a list of controller files
$files = scandir(APP_DIR . '/Controller/');
...but I get this error:
Warning (2): scandir(src/Controller/): failed to open dir: No such file or directory [APP/Controller/HomeController.php, line 13]
... when i try to remove some file names from the list:
$files = array_diff( $files, array('.','..','Component','AppController.php','HomeController.php'));
... i get this error:
Warning (2): array_diff() [function.array-diff]: Argument #1 is not an array [APP/Controller/HomeController.php, line 23]
my entire class looks this:
<?php
namespace App\Controller;
use ReflectionClass;
use ReflectionMethod;
class HomeController extends AppController{
public function index(){
/**
* http://stackoverflow.com/questions/20963786/how-do-i-get-a-list-of-all-functions-inside-a-controller-in-cakephp
* http://stackoverflow.com/questions/25892594/list-all-controllers-actions-in-cakephp-3
*/
$files = scandir(APP_DIR . '/Controller/');
$controllers = [];
$ignoreFilesList = [
'.',
'..',
'Component',
'AppController.php',
'HomeController.php'
];
$files = array_diff( $files, array('.','..','Component','AppController.php','HomeController.php'));
$className = '';
$class = null;
$ignoreList2 = ['beforeFilter', 'afterFilter', 'initialize', 'delete'];
echo is_array ( (array)$files );// returns 0 without cast
foreach( (array)$files as $file){
$controller = str_replace('Controller', '', explode('.', $file)[0]);
array_push($controllers, $controller); //str_replace('Controller', '', $controller));
//dump( $controller );
/* controller methods */
$this->$className = 'App\\Controller\\'.$controller.'Controller';
$this->$class = new ReflectionClass($this->$className);
$methods = $this->$class->getMethods(ReflectionMethod::IS_PUBLIC);
echo "<ul><li>$controller</li>";
echo "<ol>";
foreach($methods as $method){
if ( isset( $method->name )){
if($method->class == $className && !in_array($method->name, $ignoreList2))
{
$controllers[$controller][]= $method->name;
echo "<li>$method->name</li>";
}
}
}
echo "</ol>";
echo "</ul>";
}
$this->set('allControllers', $controllers );
}
}
Only use PHP: scandir with a full path.
While the PHP documentation says nothing about it, scandir generally does not play nice with relative paths. (Though there are cases where relative paths can work perfectly fine.)
Path arg is a relative path here
As APP_DIR stores a single directory name, and not a path, scandir will attempt to access src/Controller/, which may not relative to the script currently running.
$files = scandir(APP_DIR . '/Controller/');
Solution 1: Use CakePhp: constant APP to build the full path
Thanks to ndm for reminding us of the APP constant in his comment on the OP. APP stores an absolute path to your application directory, including a trailing slash.
$path = APP .'Controller/';
$files = scandir($path);
Solution 2: Use PHP: $_SERVER['DOCUMENT_ROOT'] to build a full path.
$path = $_SERVER['DOCUMENT_ROOT'] . '/'. APP_DIR .'/Controller/';
$files = scandir($path);
Alternatively, you may be able to use PHP: Realpath to retrieve the full path. *reminder: working directory will vary depending on the script running.

Cannot redeclare class: how to autoload a class if exists already in a folder?

How can I check if a class exists already in a folder then do not load this class again from another folder?
I have this folder structure for instance,
index.php
code/
local/
And I have these two identical classes in code/ and local/
from local/
class Article
{
public function getArticle()
{
echo 'class from local';
}
}
from core,
class Article
{
public function getArticle()
{
echo 'class from core';
}
}
So I need a script that can detects the class of Article in local/ - if it exits already in that folder than don't load the class again from core/ folder. Is it possible?
This is my autoload function in index.php for loading classes,
define ('WEBSITE_DOCROOT', str_replace('\\', '/', dirname(__FILE__)).'/');
function autoloadMultipleDirectory($class_name)
{
// List all the class directories in the array.
$main_directories = array(
'core/',
'local/'
);
// Set other vars and arrays.
$sub_directories = array();
// When you use namespace in a class, you get something like this when you auto load that class \foo\tidy.
// So use explode to split the string and then get the last item in the exloded array.
$parts = explode('\\', $class_name);
// Set the class file name.
$file_name = end($parts).'.php';
// List any sub dirs in the main dirs above and store them in an array.
foreach($main_directories as $path_directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator(WEBSITE_DOCROOT.$path_directory), // Must use absolute path to get the files when ajax is used.
RecursiveIteratorIterator::SELF_FIRST
);
foreach ($iterator as $fileObject)
{
if ($fileObject->isDir())
{
// Replace any backslash to '/'.
$pathnameReplace = str_replace('\\', '/', $fileObject->getPathname());
//print_r($pathnameReplace);
// Explode the folder path.
$array = explode("/",$pathnameReplace);
// Get the actual folder.
$folder = end($array);
//print_r($folder);
// Stop proccessing if the folder is a dot or double dots.
if($folder === '.' || $folder === '..') {continue;}
//var_dump($fileObject->getPathname());
// Must trim off the WEBSITE_DOCROOT.
$sub_directories[] = preg_replace('~.*?(?=core|local)~i', '', str_replace('\\', '/', $fileObject->getPathname())) .'/';
}
}
}
// Mearge the main dirs with any sub dirs in them.
$merged_directories = array_merge($main_directories,$sub_directories);
// Loop the merge array and include the classes in them.
foreach($merged_directories as $path_directory)
{
if(file_exists(WEBSITE_DOCROOT.$path_directory.$file_name))
{
// There is no need to use include/require_once. Autoload is a fallback when the system can't find the class you are instantiating.
// If you've already included it once via an autoload then the system knows about it and won't run your autoload method again anyway.
// So, just use the regular include/require - they are faster.
include WEBSITE_DOCROOT.$path_directory.$file_name;
}
}
}
// Register all the classes.
spl_autoload_register('autoloadMultipleDirectory');
$article = new Article();
echo $article->getArticle();
of course I get this error,
Fatal error: Cannot redeclare class Article in C:\wamp\...\local\Article.php on line 3
class_exists seems to be the answer I should look into, but how can I use it with the function above, especially with spl_autoload_register. Or if you have any better ideas?
Okay, I misunderstood your question. This should do the trick.
<?php
function __autoload($class_name) {
static $core = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "core";
static $local = WEBSITE_DOCROOT . DIRECTORY_SEPARATOR . "local";
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR):
$file_local = "{$local}{$file_name}.php";
require is_file($file_local) ? $file_local : "{$core}{$file_name}.php";
}
This is easily solved by using namespaces.
Your core file goes to /Core/Article.php:
namespace Core;
class Article {}
Your local file goes to /Local/Article.php:
namespace Local;
class Article {}
And then use a very simple autoloader, e.g.:
function __autoload($class_name) {
$file_name = strtr($class_name, "\\", DIRECTORY_SEPARATOR);
require "/var/www{$file_name}.php";
}
PHP loads your classes on demand, there's no need to load the files up front!
If you want to use an article simply do:
<?php
$coreArticle = new \Core\Article();
$localArticle = new \Local\Article();

Instantiating all classes in directory

I'm using Laravel and creating artisan commands but I need to register each one in start/artisan.php by calling
Artisan::add(new MyCommand);
How can I take all files in a directory (app/commands/*), and instantiate every one of them in an array ? I'd like to get something like (pseudocode) :
$my_commands = [new Command1, new Command2, new Command3];
foreach($my_commands as $command){
Artisan::add($command);
}
Here is a way to auto-register artisan commands. (This code was adapted from the Symfony Bundle auto-loader.)
function registerArtisanCommands($namespace = '', $path = 'app/commands')
{
$finder = new \Symfony\Component\Finder\Finder();
$finder->files()->name('*Command.php')->in(base_path().'/'.$path);
foreach ($finder as $file) {
$ns = $namespace;
if ($relativePath = $file->getRelativePath()) {
$ns .= '\\'.strtr($relativePath, '/', '\\');
}
$class = $ns.'\\'.$file->getBasename('.php');
$r = new \ReflectionClass($class);
if ($r->isSubclassOf('Illuminate\\Console\\Command') && !$r->isAbstract() && !$r->getConstructor()->getNumberOfRequiredParameters()) {
\Artisan::add($r->newInstance());
}
}
}
registerArtisanCommands();
If you put that in your start/artisan.php file, all commands found in app/commands will be automatically registered (assuming you follow Laravel's recommendations for command and file names). If you namespace your commands like I do, you can call the function like so:
registerArtisanCommands('App\\Commands');
(This does add a global function, and a better way to do this would probably be creating a package. But this works.)
<?php
$contents = scandir('dir_path');
$files = array();
foreach($contents as $content) {
if(substr($content,0,1 == '.') {
continue;
}
$files[] = 'dir_path'.$content;
}
That reads the contents of a folder, itterates over it and saves the filename including path in the $files array. Hope this is what you're looking for
PS: Im not familiar with laravel or artisan. So if you have to use specific semantics(like camelCase) to register them, then please tell me so

PHPUnit Dry Run or Discover Test without executing

I would like to write a script that "discovers" PHPUnit test in a given folder.
Currently I can execute:
phpunit .
And all my test names are shown however they are executed which can take quite a bit of time.
What I would like is the ability to view which test I have in a project without actually executing the test. In a format similar to
ExampleTest::testNameHere
Is this possible?
Thank you for your time.
You're looking for:
--list-tests
/* phpunit/php-file-iterator */
$iteratorFactory = new File_Iterator_Factory();
$paths = array(
/* testsuite path */
'/var/www/project/tests/unit/phpUnit/',
);
$suffixes = array(
'Test.php',
);
$iterator = $iteratorFactory->getFileIterator($paths, $suffixes);
foreach ($iterator as $file) {
$fileName = $file->getFileName();
$path = $file->getPathName();
// you should use autoloader
require_once $path;
// build className, according to your file structure
$class = preg_replace('/\.php/', '', $fileName);
$refrlection = new ReflectionClass($class);
$methods = $refrlection->getMethods(ReflectionMethod::IS_PUBLIC);
foreach ($methods as $method) {
// test* is test method
if (preg_match('/^test[A-Z]/', $method->getName())) {
echo $refrlection->getName() . ':' . $method->getName();
echo PHP_EOL;
}
}
}
Code that detects if a file contains:
A class that inherits the PHPUnit base class for test classes (sorry, don't have the name in front of me)
Methods in that class that start with 'test'
should do the trick.

Why does isDot() fail on me? (PHP)

I'm finalizing a code segment that lists the files in a directory. I have no problems listing the files in a directory but for some reason I can get the isDot() method to work to make sure the file isn't a "." or ".." . The following below results in this error:
Fatal error: Call to undefined method SplFileInfo::isDot() in ....
Before I switched over to using the Recursive Iterator I was using the Directory Iterator and it worked fine. Is there anything wrong with the code below? It should work.
$files = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($pathToFolder));
//if there is a subdirectory it makes sure the proper extension is passed
foreach($files as $name => $file){
if (!$file->isDot()) { //this is where it shuts me down
$realfile = str_replace($pathToFolder, "", $file);
$url = getDownloadLink($folderID, $realfile);
$fileArray[] = $url;
}
}
This is, because DirectoryIterator::current() (the method, that is call within a foreach-loop) returns an object, which is itself of type DirectoryIterator. FileSystemIterator (that RecursiveDirectoryIterator extends) returns an object of SplFileInfo be default. You can influence, what is return, via flags
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator(
$pathToFolder,
FilesystemIterator::KEY_AS_PATHNAME | FilesystemIterator::CURRENT_AS_SELF));
But in your case, you don't need to test, if an item is a dot-file. Just set FilesystemIterator::SKIP_DOTS and they will not appear at all. Note, that this is also the default behavior.
The other answer is excellent, but for a different approach you can set the
SKIP_DOTS flag:
<?php
$o_dir = new RecursiveDirectoryIterator($pathToFolder);
$o_dir->setFlags(RecursiveDirectoryIterator::SKIP_DOTS);
$o_iter = new RecursiveIteratorIterator($o_dir);
foreach ($o_iter as $o_info) {
echo $o_info->getPathname(), "\n";
}
https://php.net/filesystemiterator.setflags

Categories