I want to get all classes inside a namespace. I have something like this:
#File: MyClass1.php
namespace MyNamespace;
class MyClass1() { ... }
#File: MyClass2.php
namespace MyNamespace;
class MyClass2() { ... }
#Any number of files and classes with MyNamespace may be specified.
#File: ClassHandler.php
namespace SomethingElse;
use MyNamespace as Classes;
class ClassHandler {
public function getAllClasses() {
// Here I want every classes declared inside MyNamespace.
}
}
I tried get_declared_classes() inside getAllClasses() but MyClass1 and MyClass2 were not in the list.
How could I do that?
Update: Since this answer became somewhat popular, I've created a packagist package to simplify things. It contains basically what I've described here, without the need to add the class yourself or configure the $appRoot manually. It may eventually support more than just PSR-4.
That package can be found here: haydenpierce/class-finder.
$ composer require haydenpierce/class-finder
See more info in the README file.
I wasn't happy with any of the solutions here so I ended up building my class to handle this. This solution requires that you are:
Using Composer
Using PSR-4
In a nutshell, this class attempts to figure out where the classes actually live on your filesystem based on the namespaces you've defined in composer.json. For instance, classes defined in the namespace Backup\Test are found in /home/hpierce/BackupApplicationRoot/src/Test. This can be trusted because mapping a directory structure to namespace is required by PSR-4:
The contiguous sub-namespace names after the "namespace prefix"
correspond to a subdirectory within a "base directory", in which the
namespace separators represent directory separators. The subdirectory
name MUST match the case of the sub-namespace names.
You may need to adjust appRoot to point to the directory that contains composer.json.
<?php
namespace Backup\Util;
class ClassFinder
{
//This value should be the directory that contains composer.json
const appRoot = __DIR__ . "/../../";
public static function getClassesInNamespace($namespace)
{
$files = scandir(self::getNamespaceDirectory($namespace));
$classes = array_map(function($file) use ($namespace){
return $namespace . '\\' . str_replace('.php', '', $file);
}, $files);
return array_filter($classes, function($possibleClass){
return class_exists($possibleClass);
});
}
private static function getDefinedNamespaces()
{
$composerJsonPath = self::appRoot . 'composer.json';
$composerConfig = json_decode(file_get_contents($composerJsonPath));
return (array) $composerConfig->autoload->{'psr-4'};
}
private static function getNamespaceDirectory($namespace)
{
$composerNamespaces = self::getDefinedNamespaces();
$namespaceFragments = explode('\\', $namespace);
$undefinedNamespaceFragments = [];
while($namespaceFragments) {
$possibleNamespace = implode('\\', $namespaceFragments) . '\\';
if(array_key_exists($possibleNamespace, $composerNamespaces)){
return realpath(self::appRoot . $composerNamespaces[$possibleNamespace] . implode('/', $undefinedNamespaceFragments));
}
array_unshift($undefinedNamespaceFragments, array_pop($namespaceFragments));
}
return false;
}
}
The generic approach would be to get all fully qualified classnames (class with full namespace) in your project, and then filter by the wanted namespace.
PHP offers some native functions to get those classes (get_declared_classes, etc), but they won't be able to find classes that have not been loaded (include / require), therefore it won't work as expected with autoloaders (like Composer for example).
This is a major issue as the usage of autoloaders is very common.
So your last resort is to find all PHP files by yourself and parse them to extract their namespace and class:
$path = __DIR__;
$fqcns = array();
$allFiles = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($path));
$phpFiles = new RegexIterator($allFiles, '/\.php$/');
foreach ($phpFiles as $phpFile) {
$content = file_get_contents($phpFile->getRealPath());
$tokens = token_get_all($content);
$namespace = '';
for ($index = 0; isset($tokens[$index]); $index++) {
if (!isset($tokens[$index][0])) {
continue;
}
if (
T_NAMESPACE === $tokens[$index][0]
&& T_WHITESPACE === $tokens[$index + 1][0]
&& T_STRING === $tokens[$index + 2][0]
) {
$namespace = $tokens[$index + 2][1];
// Skip "namespace" keyword, whitespaces, and actual namespace
$index += 2;
}
if (
T_CLASS === $tokens[$index][0]
&& T_WHITESPACE === $tokens[$index + 1][0]
&& T_STRING === $tokens[$index + 2][0]
) {
$fqcns[] = $namespace.'\\'.$tokens[$index + 2][1];
// Skip "class" keyword, whitespaces, and actual classname
$index += 2;
# break if you have one class per file (psr-4 compliant)
# otherwise you'll need to handle class constants (Foo::class)
break;
}
}
}
If you follow PSR 0 or PSR 4 standards (your directory tree reflects your namespace), you don't have to filter anything: just give the path that corresponds to the namespace you want.
If you're not a fan of copying/pasting the above code snippets, you can simply install this library: https://github.com/gnugat/nomo-spaco .
If you use PHP >= 5.5, you can also use the following library: https://github.com/hanneskod/classtools .
Quite a few interesting answers above, some actually peculiarly complex for the proposed task.
To add a different flavor to the possibilities, here a quick and easy non-optimized function to do what you ask using the most basic techniques and common statements I could think of:
function classes_in_namespace($namespace) {
$namespace .= '\\';
$myClasses = array_filter(get_declared_classes(), function($item) use ($namespace) { return substr($item, 0, strlen($namespace)) === $namespace; });
$theClasses = [];
foreach ($myClasses AS $class):
$theParts = explode('\\', $class);
$theClasses[] = end($theParts);
endforeach;
return $theClasses;
}
Use simply as:
$MyClasses = classes_in_namespace('namespace\sub\deep');
var_dump($MyClasses);
I've written this function to assume you are not adding the last "trailing slash" (\) on the namespace, so you won't have to double it to escape it. ;)
Please notice this function is only an example and has many flaws. Based on the example above, if you use 'namespace\sub' and 'namespace\sub\deep' exists, the function will return all classes found in both namespaces (behaving as if it was recursive). However, it would be simple to adjust and expand this function for much more than that, mostly requiring a couple of tweaks in the foreach block.
It may not be the pinnacle of the code-art-nouveau, but at least it does what was proposed and should be simple enough to be self-explanatory.
I hope it helps pave the way for you to achieve what you are looking for.
Note: PHP 5, 7, AND 8 friendly.
Pretty interesting that there does not seem to be any reflection method that does that for you. However I came up with a little class that is capable of reading namespace information.
In order to do so, you have to traverse trough all defined classes. Then we get the namespace of that class and store it into an array along with the classname itself.
<?php
// ClassOne namespaces -> ClassOne
include 'ClassOne/ClassOne.php';
// ClassOne namespaces -> ClassTwo
include 'ClassTwo/ClassTwo.php';
include 'ClassTwo/ClassTwoNew.php';
// So now we have two namespaces defined
// by ourselves (ClassOne -> contains 1 class, ClassTwo -> contains 2 classes)
class NameSpaceFinder {
private $namespaceMap = [];
private $defaultNamespace = 'global';
public function __construct()
{
$this->traverseClasses();
}
private function getNameSpaceFromClass($class)
{
// Get the namespace of the given class via reflection.
// The global namespace (for example PHP's predefined ones)
// will be returned as a string defined as a property ($defaultNamespace)
// own namespaces will be returned as the namespace itself
$reflection = new \ReflectionClass($class);
return $reflection->getNameSpaceName() === ''
? $this->defaultNamespace
: $reflection->getNameSpaceName();
}
public function traverseClasses()
{
// Get all declared classes
$classes = get_declared_classes();
foreach($classes AS $class)
{
// Store the namespace of each class in the namespace map
$namespace = $this->getNameSpaceFromClass($class);
$this->namespaceMap[$namespace][] = $class;
}
}
public function getNameSpaces()
{
return array_keys($this->namespaceMap);
}
public function getClassesOfNameSpace($namespace)
{
if(!isset($this->namespaceMap[$namespace]))
throw new \InvalidArgumentException('The Namespace '. $namespace . ' does not exist');
return $this->namespaceMap[$namespace];
}
}
$finder = new NameSpaceFinder();
var_dump($finder->getClassesOfNameSpace('ClassTwo'));
The output will be:
array(2) { [0]=> string(17) "ClassTwo\ClassTwo" [1]=> string(20) "ClassTwo\ClassTwoNew" }
Of course everything besides the NameSpaceFinder class itself if assembled quick and dirty. So feel free to clean up the include mess by using autoloading.
I think a lot of people might have a problem like this, so I relied on the answers from #hpierce and #loïc-faugeron to solve this problem.
With the class described below, you can have all classes within a namespace or they respect a certain term.
<?php
namespace Backup\Util;
final class ClassFinder
{
private static $composer = null;
private static $classes = [];
public function __construct()
{
self::$composer = null;
self::$classes = [];
self::$composer = require APP_PATH . '/vendor/autoload.php';
if (false === empty(self::$composer)) {
self::$classes = array_keys(self::$composer->getClassMap());
}
}
public function getClasses()
{
$allClasses = [];
if (false === empty(self::$classes)) {
foreach (self::$classes as $class) {
$allClasses[] = '\\' . $class;
}
}
return $allClasses;
}
public function getClassesByNamespace($namespace)
{
if (0 !== strpos($namespace, '\\')) {
$namespace = '\\' . $namespace;
}
$termUpper = strtoupper($namespace);
return array_filter($this->getClasses(), function($class) use ($termUpper) {
$className = strtoupper($class);
if (
0 === strpos($className, $termUpper) and
false === strpos($className, strtoupper('Abstract')) and
false === strpos($className, strtoupper('Interface'))
){
return $class;
}
return false;
});
}
public function getClassesWithTerm($term)
{
$termUpper = strtoupper($term);
return array_filter($this->getClasses(), function($class) use ($termUpper) {
$className = strtoupper($class);
if (
false !== strpos($className, $termUpper) and
false === strpos($className, strtoupper('Abstract')) and
false === strpos($className, strtoupper('Interface'))
){
return $class;
}
return false;
});
}
}
In this case, you must use Composer to perform class autoloading. Using the ClassMap available on it, the solution is simplified.
Using finder
composer require symfony/finder
usage
public function getAllNameSpaces($path)
{
$filenames = $this->getFilenames($path);
$namespaces = [];
foreach ($filenames as $filename) {
$namespaces[] = $this->getFullNamespace($filename) . '\\' . $this->getClassName($filename);
}
return $namespaces;
}
private function getClassName($filename)
{
$directoriesAndFilename = explode('/', $filename);
$filename = array_pop($directoriesAndFilename);
$nameAndExtension = explode('.', $filename);
$className = array_shift($nameAndExtension);
return $className;
}
private function getFullNamespace($filename)
{
$lines = file($filename);
$array = preg_grep('/^namespace /', $lines);
$namespaceLine = array_shift($array);
$match = [];
preg_match('/^namespace (.*);$/', $namespaceLine, $match);
$fullNamespace = array_pop($match);
return $fullNamespace;
}
private function getFilenames($path)
{
$finderFiles = Finder::create()->files()->in($path)->name('*.php');
$filenames = [];
foreach ($finderFiles as $finderFile) {
$filenames[] = $finderFile->getRealpath();
}
return $filenames;
}
I am going to give an example which is actually being used in our Laravel 5 app but can be used almost everywhere. The example returns class names with the namespace which can be easily taken out, if not required.
Legend
{{1}} - Path to remove from current file's path to get to app folder
{{2}} - The folder path from app folder where the target classes exist
{{3}} - Namespace path
Code
$classPaths = glob(str_replace('{{1}}', '',__DIR__) .'{{2}}/*.php');
$classes = array();
$namespace = '{{3}}';
foreach ($classPaths as $classPath) {
$segments = explode('/', $classPath);
$segments = explode('\\', $segments[count($segments) - 1]);
$classes[] = $namespace . $segments[count($segments) - 1];
}
Laravel people can use app_path() . '/{{2}}/*.php' in glob().
After trying the composer solutions above, was not satisfied with the time it took to obtain the recursive classes inside a namespace, up to 3 seconds but on some machines it took 6-7 seconds which was unacceptable. Below class renders the classes in ~0.05 in a normal 3-4 levels depth directory structure.
namespace Helpers;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
class ClassHelper
{
public static function findRecursive(string $namespace): array
{
$namespacePath = self::translateNamespacePath($namespace);
if ($namespacePath === '') {
return [];
}
return self::searchClasses($namespace, $namespacePath);
}
protected static function translateNamespacePath(string $namespace): string
{
$rootPath = __DIR__ . DIRECTORY_SEPARATOR;
$nsParts = explode('\\', $namespace);
array_shift($nsParts);
if (empty($nsParts)) {
return '';
}
return realpath($rootPath. implode(DIRECTORY_SEPARATOR, $nsParts)) ?: '';
}
private static function searchClasses(string $namespace, string $namespacePath): array
{
$classes = [];
/**
* #var \RecursiveDirectoryIterator $iterator
* #var \SplFileInfo $item
*/
foreach ($iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($namespacePath, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::SELF_FIRST
) as $item) {
if ($item->isDir()) {
$nextPath = $iterator->current()->getPathname();
$nextNamespace = $namespace . '\\' . $item->getFilename();
$classes = array_merge($classes, self::searchClasses($nextNamespace, $nextPath));
continue;
}
if ($item->isFile() && $item->getExtension() === 'php') {
$class = $namespace . '\\' . $item->getBasename('.php');
if (!class_exists($class)) {
continue;
}
$classes[] = $class;
}
}
return $classes;
}
}
Usage:
$classes = ClassHelper::findRecursive(__NAMESPACE__);
print_r($classes);
Result:
Array
(
[0] => Helpers\Dir\Getters\Bar
[1] => Helpers\Dir\Getters\Foo\Bar
[2] => Helpers\DirSame\Getters\Foo\Cru
[3] => Helpers\DirSame\Modifiers\Foo\Biz
[4] => Helpers\DirSame\Modifiers\Too\Taz
[5] => Helpers\DirOther\Modifiers\Boo
)
Note: This solution seems to work with Laravel directly. For outside Laravel, you might need to copy and modify the ComposerClassMap class from the given source. I didn't try.
If you are already using Composer for PSR-4 compliant autoloading, you can use this method to get all autoloaded classes and filter them (That's the example from my module system, directly copied and pasted from there):
function get_available_widgets()
{
$namespaces = array_keys((new ComposerClassMap)->listClasses());
return array_filter($namespaces, function($item){
return Str::startsWith($item, "App\\Modules\\Widgets\\") && Str::endsWith($item, "Controller");
});
}
Source of the ComposerClassMap class: https://github.com/facade/ignition/blob/master/src/Support/ComposerClassMap.php
Locate Classes
A class can be located in the file system by its name and its namespace, like the autoloader does. In the normal case the namespace should tell the relative path to the class files. The include paths are the starting points of the relative paths. The function get_include_path() returns a list of include paths in one string. Each include path can be tested, whether there exists a relative path which matches the namespace. If the matching path is found, you will know the location of the class files.
Get Class Names
As soon as the location of the class files is known, the classes can be extracted from the file names, because the name of a class file should consist of the class name followed by .php.
Sample Code
Here is a sample code to get all class names of the namespace foo\bar as a string array:
$namespace = 'foo\bar';
// Relative namespace path
$namespaceRelativePath = str_replace('\\', DIRECTORY_SEPARATOR, $namespace);
// Include paths
$includePathStr = get_include_path();
$includePathArr = explode(PATH_SEPARATOR, $includePathStr);
// Iterate include paths
$classArr = array();
foreach ($includePathArr as $includePath) {
$path = $includePath . DIRECTORY_SEPARATOR . $namespaceRelativePath;
if (is_dir($path)) { // Does path exist?
$dir = dir($path); // Dir handle
while (false !== ($item = $dir->read())) { // Read next item in dir
$matches = array();
if (preg_match('/^(?<class>[^.].+)\.php$/', $item, $matches)) {
$classArr[] = $matches['class'];
}
}
$dir->close();
}
}
// Debug output
var_dump($includePathArr);
var_dump($classArr);
class_parents, spl_classes() and class_uses can be used to retrieve all the class names
You can use get_declared_classes but with a little additional work.
$needleNamespace = 'MyNamespace';
$classes = get_declared_classes();
$neededClasses = array_filter($classes, function($i) use ($needleNamespace) {
return strpos($i, $needleNamespace) === 0;
});
So first you get all declared classes and then check which of them starts with your namespace.
Note: you will get array where keys do not start with 0. To achive this, you can try: array_values($neededClasses);.
The easiest way should be to use your own autoloader __autoload function and inside of it save the loaded classes names. Does that suits You ?
Otherwise I think You will have to deal with some reflection methods.
I just did something similar, this is relatively simple but can be built off of.
public function find(array $excludes, ?string $needle = null)
{
$path = "../".__DIR__;
$files = scandir($path);
$c = count($files);
$models = [];
for($i=0; $i<$c; $i++) {
if ($files[$i] == "." || $files[$i] == ".." || in_array($dir[$i], $excludes)) {
continue;
}
$model = str_replace(".php","",$dir[$i]);
if (ucfirst($string) == $model) {
return $model;
}
$models[] = $model;
}
return $models;
}
for symfony you can use the Finder Component:
http://symfony.com/doc/current/components/finder.html
$result1 = $finder->in(__DIR__)->files()->contains('namespace foo;');
$result2 = $finder->in(__DIR__)->files()->contains('namespace bar;');
Related
I've set up a simple autoload to understand the principles when working together with namespaces.
test.php:
namespace House;
function __autoload($classname)
{
$parts = explode('\\', $classname);
$class = 'Room/'.end($parts).'.php';
require_once($class);
}
$Toy = new Toy();
echo $Toy->hello();
Room/Toy.php:
namespace House;
class Toy
{
public function hello() { return "HELLO"; }
}
When declaring $Toy I get Fatal error: Class 'House\Toy' not found in test.php on line 18
What have I dont wrong here?
When removing __autoload function and instead just putting in
require_once('Room/Toy.php');
It works!
Your test.php should look like this:
use House\Toy as Toy;
function __autoload($classname)
{
$parts = explode('\\', $classname);
$class = 'Room/'.end($parts).'.php';
require_once($class);
}
$Toy = new Toy();
echo $Toy->hello();
If you are putting namespace House on the top of the file, all functions declared in that file will belong to the namespace House. Meaning __autoload() get's House\__autoload() and will loose it's magic meaning.
Another thing, wouldn't it be better to store the files of the namespace House in a folder named House? (You are currently using Room). If you are doing so, the __autoload() function could be written more generally:
function __autoload($classname)
{
$parts = explode('\\', $classname);
$path = implode('/', $parts) . '.php';
require_once($path);
}
Try using this autoloader instead :) and add the absolute path to directory Room to your include path. Inside Room have another directory called House where Toy.php will be located.
<?php
spl_autoload_register ( function ( $className, $fileExtensions = null ) {
$className = str_replace ( '_', '/', $className );
$className = str_replace ( '\\', '/', $className );
$file = stream_resolve_include_path ( $className . '.php' );
if ( $file !== false ) {
include $file;
return true;
}
return false;
});
I have this autoloader class to autoload classes initially, but now I want to autoload interfaces and abstracts as well.
So I made the change following this answer,
$reflection = new ReflectionClass($class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
I tested it but this autoloader class does not load interfaces at all. Any ideas what I have missed?
For instance, this is my interface file,
interface_methods.php
and its content,
interface methods
{
public function delete();
}
Below is my entire this autoloader class.
class autoloader
{
/**
* Set the property.
*/
public $directory;
public $recursive;
public function __construct($directory, $recursive = array('search' => 'models') )
{
# Store the data into the property.
$this->directory = $directory;
$this->recursive = $recursive;
# When using spl_autoload_register() with class methods, it might seem that it can use only public methods, though it can use private/protected methods as well, if registered from inside the class:
spl_autoload_register(array($this,'get_class'));
}
private function get_class($class_name)
{
# List all the class directories in the array.
if ($this->recursive)
{
$array_directories = self::get_recursive_directory($this->directory);
}
else
{
if (is_array($this->directory)) $array_directories = $this->directory;
else $array_directories = array($this->directory);
}
# Determine the class is an interface.
$reflection = new ReflectionClass($class_name);
$file_pieces = explode('\\', $class_name);
# Return boolean if it is an interface.
if ($reflection->isInterface())
{
$file_name = 'interface_'.strtolower(array_pop($file_pieces)).'.php';
}
else
{
$file_name = 'class_'.strtolower(array_pop($file_pieces)).'.php';
}
# Loop the array.
foreach($array_directories as $path_directory)
{
if(file_exists($path_directory.$file_name))
{
include $path_directory.$file_name;
}
}
}
public function get_recursive_directory($directory)
{
$iterator = new RecursiveIteratorIterator
(
new RecursiveDirectoryIterator($directory),
RecursiveIteratorIterator::CHILD_FIRST
);
# This will hold the result.
$result = array();
# Loop the directory contents.
foreach ($iterator as $path)
{
# If object is a directory and matches the search term ('models')...
if ($path->isDir() && $path->getBasename() === $this->recursive['search'])
{
# Add it to the result array.
# Must replace the slash in the class - dunno why!
$result[] = str_replace('\\', '/', $path).'/';
//$result[] = (string) $path . '/';
}
}
# Return the result in an array.
return $result;
}
}
PHP makes no difference between any class or interface or abstract class. The autoloader function you define always gets the name of the thing to autoload, and no kind of hint which one it was.
So your naming strategy cannot be autoloaded because you prefix interfaces with "interface_" and classes with "class_". Personally I find such a naming convention rather annoying.
On the other hand, your autoloader is completely unperformant. It scans whole directory trees recursively just to find one class! And the next class has to do all the work again, without the benefit of having done it before!
Please do implement a PSR-0 autoloader if you really want to do it on your own (and not use things like composer to do it for you) and stick to this naming scheme for classes and interfaces.
And please select a distinguishing classname prefix or namespace, and as a first step check inside your autoloader if the class to be loaded has this prefix. Return instantly if it has not. This frees you from having to spin the harddrive and see if the filename for the class exists.
If the prefix does not match, it is not "your" class that wants to be loaded, so your autoloader cannot know how to do it and shouldn't even try, but a different autoloader that was registered will know.
I have tried several methods to ignore certain directories using RecursiveIteratorIterator on a file system.
For the sake of an example say I want to ignore the following directory: /cache.
My Iterator looks like this:
//$dirname is root
$directory = new RecursiveDirectoryIterator($dirname);
$mega = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
foreach ($mega as $fileinfo) {
echo $fileinfo;
}
I have been able to ignore certain file extensions using pathinfo for example this works:
$filetypes = array("jpg", "png");
$filetype = pathinfo($fileinfo, PATHINFO_EXTENSION);
foreach ($mega as $fileinfo) {
if (!in_array(strtolower($filetype), $filetypes)) {
echo $fileinfo;
}
}
When I try it using PATHINFO_DIRNAME (with the appropriate directory paths in the array) it does not work, there are no errors, it just does not ignore the directories.
I have also experimented with using FilterIterator to no avail, and now I'm thinking maybe I should use RegexIterator.
What would be the simplest and most efficient way to ignore directories using RecursiveIteratorIterator ?
Experiments that didn't work.
Using PATHINFO_DIRNAME
$dirignore = array("cache", "cache2");
$directory = new RecursiveDirectoryIterator($dirname);
$mega = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
$dirtype = pathinfo($fileinfo, PATHINFO_DIRNAME);
if (!in_array($dirtype), $dirignore)) {
echo $fileinfo; //no worky still echo directories
}
Using FilterIterator (not sure how this works)
class DirFilter extends FilterIterator
{
public function accept()
{
//return parent::current() != "..\cache\";
$file = $this->getInnerIterator()->current();
if ($file != "..\cache")
return $file->getFilename();
//also tried with same error as below
//return !preg_match('/\cache', $file->getFilename());
}
}
$directory= new RecursiveDirectoryIterator($dirname);
$directory = new DirFilter($directory);
$mega = new RecursiveIteratorIterator($directory, RecursiveIteratorIterator::SELF_FIRST);
// loop
Tis results in Error: An instance of RecursiveIterator or IteratorAggregate creating it is required
Your DirFilter wants to extend RecursiveFilterIterator, since it is being used with a recursive iterator. Not doing that, is the cause of your error.
class DirFilter extends RecursiveFilterIterator
{
// …
}
The next step is figuring out how to use accept() properly. It is supposed to return a true or false value, which dictates whether the current item should be included (true) or excluded (false) from the iteration. Returning a file name from accept() is not what you want to be doing.
class DirFilter extends RecursiveFilterIterator
{
public function accept()
{
$excludes = array("cache", "cache2");
return !($this->isDir() && in_array($this->getFilename(), $excludes));
}
}
It might be nice to be able to pass in the array of excluded directories. This requires a little more code but is much more reusable.
class DirFilter extends RecursiveFilterIterator
{
protected $exclude;
public function __construct($iterator, array $exclude)
{
parent::__construct($iterator);
$this->exclude = $exclude;
}
public function accept()
{
return !($this->isDir() && in_array($this->getFilename(), $this->exclude));
}
public function getChildren()
{
return new DirFilter($this->getInnerIterator()->getChildren(), $this->exclude);
}
}
This code could then be used like:
$directory = new RecursiveDirectoryIterator($dirname);
$filtered = new DirFilter($directory, $dirignore);
$mega = new RecursiveIteratorIterator($filtered, …);
I'm in the process of updating a framework that I wrote a while ago. I would like to adopt new standards such as namespaces and using the autoload feature.
Right now my framework has a very rudimentary but functional autoload function that looks like this:
protected function runClasses()
{
$itemHandler = opendir( V_APP_PATH );
while( ( $item = readdir( $itemHandler ) ) !== false )
{
if ( substr( $item, 0, 1 ) != "." )
{
if( !is_dir( $item ) && substr( $item, -10 ) == ".class.php" )
{
if( !class_exists( $this->registry->$item ) )
{
require_once( V_APP_PATH . $item );
$item = str_replace( ".class.php", "", $item );
$this->registry->$item = new $item( $this->registry );
}
}
}
}
}
As you can see in the code this function is limited to a single folder only, but the advantage to it is it loads the class into my registry allowing me to access that specific class in other files by doing something similar to $this->registry->Class->somevar which is the functionality I need.
What I'm needing/wanting to accomplish is to use the autoloader function but have that function not limited to a single folder, instead be able to navigate through multiple folders and instantiate the needed classes.
I have just some test files going and here is my current file structure:
For MyClass2 I have:
namespace model;
Class MyClass2 {
function __construct() {
echo "MyClass2 is now loaded!";
}
}
For MyClass1 I have:
Class MyClass1 {
function __construct() {
echo "MyClass1 is now loaded!<br />";
}
}
And for Autoload I have:
function __autoload( $className ) {
$file = $className . ".php";
printf( "%s <br />", $file );
if(file_exists($file)) {
require_once $file;
}
}
$obj = new MyClass1();
$obj2 = new model\MyClass2();
My Question is the way that is set up it can't find the file for MyClass2 so I'm wondering what I've done wrong and secondly, is there a way like my first "autoload" function to not need to specify the namespace in the autoload file and assign it to my registry?
Sorry for such a lengthy question but any help is greatly appreciated.
I see two things here.
The first one makes your problem a bit complicated. You want to make use of namespaces, but your current configuration is via the file-system. The file-names of the class definition files does not contain the namespace so far. So you can not just continue as you actually do.
The second is that you do not have what's covered by PHP autoloading, you just load a defined set of classes and register it with the registry.
I'm not really sure if you need PHP autoloading here. Sure it might look promising for you to bring both together. Solving the first point will probably help you to solve the later, so I suggest to start with it first.
Let's make the hidden dependencies more visible. In your current design you have got three things:
The name under which an object is registered in the registry.
The filename which contains the class definition.
The name of the class itself.
The values of 2. and 3. are in one, you parse the name of the class itself from the filename. As written, namespaces make this complicated now. The solution is easy, instead of reading from a directory listing, you can read from a file that contains this information. A lightweight configuration file format is json:
{
"Service": {
"file": "test.class.php",
"class": "Library\\Of\\Something\\ConcreteService"
}
}
This contains now the three needed dependencies to register a class by a name into the registry because the filename is known as well.
You then allow to register classes in the registry:
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
And add a loader class for the json format:
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
There is not much magic here, file-names in the json file are relative to the directory of it. If a class is not yet defined (here with triggering PHP autoloading), the file of the class is being required. After that is done, the class is registered by it's name:
$registry = new Registry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
This example as well is pretty straight forward and it works. So the first problem is solved.
The second problem is the autoloading. This current variant and your previous system did hide something else, too. There are two central things to do. The one is to actually load class definitions and the other is to instantiate the object.
In your original example, autoloading technically was not necessary because the moment an object is registered within the registry, it is instantiate as well. You do this to assign the registry to it as well. I don't know if you do it only because of that or if this just happened that way to you. You write in your question that you need that.
So if you want to bring autoloading into your registry (or lazy loading), this will vary a bit. As your design is already screwed, let's continue to add more magic on top. You want to defer the instantiation of a registry component to the moment it's used the first time.
As in the registry the name of the component is more important than it's actual type, this is already pretty dynamic and a string only. To defer component creation, the class is not created when registered but when accessed. That is possible by making use of the __get function which requires a new type of Registry:
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class)
{
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
The usage example again is quite the same, however, the type of the registry has changed:
$registry = new LazyRegistry();
$json = new JsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
So now the creation of the concrete service objects has been deferred until first accessed. However this still yet is not autoloading. The loading of the class definitions is already done inside the json loader. It would not be consequent to already make verything dynamic and magic, but not that. We need an autoloader for each class that should kick in in the moment the objects is accessed the first time. E.g. we actually want to be able to have rotten code in the application that could stay there forever unnoticed because we don't care if it is used or not. But we don't want to load it into memory then.
For autoloading you should be aware of spl_autoload_register which allows you to have more than one autoloader function. There are many reasons why this is generally useful (e.g. imagine you make use of third-party packages), however this dynamic magic box called Registry of yours, it's just the perfect tool for the job. A straight forward solution (and not doing any premature optimization) is to register one autoloader function for each class we have in the registry definition. This then needs a new type of loader and the autoloader function is just two lines of code or so:
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
The usage example again didn't change much, just the type of the loader:
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
Now you can be lazy as hell. And that would mean, to actually change the code again. Because you want to remote the necessity to actually put those files into that specific directory. Ah wait, that is what you asked for, so we leave it here.
Otherwise consider to configure the registry with callables that would return the instance on first access. That does normally make things more flexible. Autoloading is - as shown - independent to that if you actually can leave your directory based approach, you don't care any longer where the code is packaged in concrete (http://www.getcomposer.org/).
The whole code-example in full (without registry.json and test.class.php):
class Registry
{
public function registerClass($name, $class) {
$this->$name = new $class($this);
}
}
class LazyRegistry extends Registry
{
private $defines = [];
public function registerClass($name, $class) {
$this->defines[$name] = $class;
}
public function __get($name) {
$class = $this->defines[$name];
return $this->$name = new $class($this);
}
}
interface Register
{
public function register(Registry $registry);
}
class JsonClassmapLoader implements Register
{
private $file;
public function __construct($file) {
$this->file = $file;
}
public function register(Registry $registry) {
$definitions = $this->loadDefinitionsFromFile();
foreach ($definitions as $name => $definition) {
$class = $definition->class;
$path = dirname($this->file) . '/' . $definition->file;
$this->define($class, $path);
$registry->registerClass($name, $class);
}
}
protected function define($class, $path) {
if (!class_exists($class)) {
require($path);
}
}
protected function loadDefinitionsFromFile() {
$json = file_get_contents($this->file);
return json_decode($json);
}
}
class LazyJsonClassmapLoader extends JsonClassmapLoader
{
protected function define($class, $path) {
$autoloader = function ($classname) use ($class, $path) {
if ($classname === $class) {
require($path);
}
};
spl_autoload_register($autoloader);
}
}
$registry = new LazyRegistry();
$json = new LazyJsonClassmapLoader('path/registry.json');
$json->register($registry);
echo $registry->Service->invoke(); # Done.
I hope this is helpful, however this is mainly playing in the sandbox and you will crush that the sooner or later. What you're actually want to learn about is Inversion of Control, Dependency Injection and then about Dependency Injection containers.
The Registry you have is some sort of smell. It's all totally full of magic and dynamic. You might think this is cool for development or for having "plugins" in your system (it's easy to extend), however you should keep the amount of objects therein low.
Magic can be hard to debug, so you might want to check the format of the json file if it makes sense in your case first to prevent first-hand configuration issues.
Also consider that the registry object passed to each constructor is not one parameter but represents a dynamic amount of parameters. This will start to create side-effects the sooner or later. If you are using the registry too much, then more the sooner. These kind of side-effects will cost you maintenance a lot because by design this is already flawed, so you can only control it with hard work, heavy integration tests for the regressions etc..
However, make your own experiences, it's just some outlook not that you tell me later I didn't notice it.
For your second question: the use of __autoload is discouraged and should be replaced with a spl_autoload_register. What the autoloader should do is split namespace and class:
function __autoload( $classname )
{
if( class_exists( $classname, false ))
return true;
$classparts = explode( '\\', $classname );
$classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
$namespace = implode( '\\', $classparts );
// at this point you have to decide how to process further
}
Depending on you file structure I would suggest building a absolute path based on the namespace and classname:
define('ROOT_PATH', __DIR__);
function __autoload( $classname )
{
if( class_exists( $classname, false ))
return true;
$classparts = explode( '\\', $classname );
$classfile = '/' . strtolower( array_pop( $classparts )) . '.php';
$namespace = implode( '\\', $classparts );
$filename = ROOT_PATH . '/' . $namespace . $classfile;
if( is_readble($filename))
include_once $filename;
}
I've took the PSR0 approach, where the namespace is part of the path.
I'm trying to become an object-oriented coder here, so I'm giving myself some simple tasks.
I built a class that displays all the images in a given directory. That worked fine, so I separated that class into two classes, one to read the filenames in the directory and pass them into an array, and one to parse that array and display the pictures. The method in the child class is exactly the same as it was when it was in the parent class (except of course substituting parent:: for this->).
Now it seems when I instantiate the child class and call its method, nothing happens at all.
Classes:
class Picfind
{
public function findPics($dir){
$files = array();
$i=0;
$handle = opendir($dir);
while (false !== ($file = readdir($handle))){
$extension = strtolower(substr(strrchr($file, '.'), 1));
if($extension == 'jpg' || $extension == 'gif' || $extension == 'png'){
// now use $file as you like
$i++;
$files['file' . $i] = $file;
}
}
return $files;
}
}
class DisplayPics extends Picfind
{
function diplayPics($dir)
{
echo 'displayPics method called';
foreach(parent::findPics($dir) as $key => $val) {
echo '<img src="' . $dir . $val . '" img><br/>';
}
}
}
Instantiation:
include("class.picFind.php");
$Myclass = new DisplayPics();
$Myclass->displayPics('./images/');
To be honest: your whole design is wrong.
DisplayPics shouldn't inherit from Picfind. Honestly, either have Picfind have a display method, or have DisplayPics take the output from Picfind. Think, does the following make sense: "DisplayPics is a PicFind."? If not, it's probably wrong.
Classes normally aren't verbs. A better name would be Pictures, with find and display methods. In your case, you are finding something in a directory, which leads to the next point:
You should make use of the PHP DirectoryIterator class. This way, you can do whatever you'd like with the files you find. You'll have all the information about the file available to you, and it's integrated nicely with PHP.
You need a separation of concerns. This is what hakre's suggestion is all about. Reducing dependencies and decoupling things is normally helpful.
/**
* ExtensionFinder will find all the files in a directory that have the given
* extensions.
*/
class ExtensionFinder extends DirectoryIterator {
protected $extensions = array();
public function __contruct($directory) {
parent::__construct($directory);
}
/**
* Sets the extensions for the iterator.
* #param array $extensions The extensions you want to get (without the dot).
*/
public function extensions(array $extensions) {
$this->extensions = $extensions;
}
/**
* Determines if this resource is valid. If you return false from this
* function, the iterator will stop.
* #return boolean Returns true if the value is a file with proper extension.
*/
public function valid() {
if (parent::valid()) {
$current = parent::current();
if ($current->isFile()) {
// if the extensions array is empty or null, we simply accept it.
if (empty($this->extensions)) {
//otherwise filter it
if (in_array($current->getExtension(), $this->extensions)) {
return true;
} else {
parent::next();
return $this->valid();
}
} else {
return true;
}
} else {
parent::next();
return $this->valid();
}
} else {
return false;
}
}
}
class PictureFinder extends ExtensionFinder {
public function __construct($directory) {
parent::__construct($directory);
$this->extensions = array (
'jpg',
'gif',
'png'
);
}
}
How to use:
$iterator = new PictureFinder('img/');
foreach($iterator as $file) {
//do whatever you want with the picture here.
echo $file->getPathname()."\n";
}
Note that you could use the ExtensionFinder class I defined above to find files of ANY extension. That could potentially be more useful than simply finding images, but I defined a PictureFinder class for you for that specific use-case.
You wrote that you want to learn object oriented programming. What about the following:
class PicFinder
{
/**
* #return array
*/
public function inDirectory($directory)
{
return // array of files
}
}
class PicPresentation
{
public function present(array $pictures)
{
// your presentation code
}
}
$path = '/your/path';
$datasource = new PicFinder();
$presentation = new PicPresentation();
$pictures = $datasource->inDirectory($path);
$presentation->present($pictures);
Keep things separated and loosely coupled. One object should be responsible for one thing, e.g. one object to obtain the list of pictures from a directory and another one for the presentation. Good luck!
$Myclass->displayPics('./images/');
is calling the constructor and nothing is happening.
You have a typo in your function name aswell.
I'd suggest that design instead:
class PicFinder
{
public function findPics($dir){
...
}
}
class PicDisplayer
{
protected $picFinder;
public function __construct() {
// Default pic finder
$this->setPicFinder(new PicFinder());
}
public function diplayPics($dir) {
echo 'displayPics method called';
foreach($this->getPicFinder()->findPics($dir) as $key => $val) {
echo '<img src="' . $dir . $val . '" img><br/>';
}
}
protected function setPicFinder(PicFinder $picFinder) {
$this->picFinder = $picFinder;
}
protected function getPicFinder() {
return $this->picFinder;
}
}
That way you only use PicDisplayer and don't care how it finds the pics. But you can still change the "PicFinder" if needed, by extending the PicFinder class and implementing a specific behavior.