I've created a function that loads a specific class and also should create a new instance of this class:
class File {
public static function load($file, $folder = null, $local = false, $classname = null) {
if ($folder == 'root') {
$dir = '';
} elseif ($folder) {
$dir = $folder . '/';
} else {
$dir = 'classes/';
}
if ($local) {
$root = ROOT_ABS;
} else {
$root = ROOT_CMS_ABS;
}
require_once $root . $dir . $file . '.php';
// New Instance
if ($folder == null || $classname) {
if (!$classname) {
return new $file();
} else {
return new $classname();
}
}
}
I've built in some extra parameters to specify if my CMS folder should be accessed (ROOT_CMS_ABS AND ROOT_ABS constants). So the minimum default case to load a file and also create an object is e.g.:
$html = File::load('html');
Issue:
I'm using PhpStorm and usually I could hold Ctrl + Click to jump to a function of an object.
But now PhpStorm can't recognize which class has been loaded. In my example this is because I assign $router to this class instead of writing $router = new AltoRouter();.
Question:
1) Is there another way that PhpStorm recognized which class I'm referring to?
2) I'm kinda new to OOP in PHP, could I improve my class somehow?
__
Otherwise the function works fine. So this is more likely an IDE issue.
You can easily solve it by adding inline PHPDoc comment with type hint before such assignment. For example:
/** #var MyClassName $router */
$router = File::load('AltoRouter', 'route');
Other than that: using $router = new AltoRouter(); is much better -- leave actual class loading for class autoloading mechanism (following PSR-4; be it standard these days Composer .. or your own implementation).
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;');
The standard way to recursively scan directories via SPL iterators is:
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($files as $file) {
print $file->getPathname() . PHP_EOL;
}
I want a composable set of filters to apply to my recursive file search. I'm using a RecursiveDirectoryIterator to scan a directory structure.
I want to apply more than one filter to my directory structure.
My set up code:
$filters = new FilterRuleset(
new RecursiveDirectoryIterator($path)
);
$filters->addFilter(new FilterLapsedDirs);
$filters->addFilter(new IncludeExtension('wav'));
$files = new RecursiveIteratorIterator(
$filters, RecursiveIteratorIterator::CHILD_FIRST
);
I thought I could apply N filters by using rule set:
class FilterRuleset extends RecursiveFilterIterator {
private $filters = array();
public function addFilter($filter) {
$this->filters[] = $filter;
}
public function accept() {
$file = $this->current();
foreach ($this->filters as $filter) {
if (!$filter->accept($file)) {
return false;
}
}
return true;
}
}
The filtering I set up is not working as intended. When I check the filters in FilterRuleset they are populated on the first call, then blank on subsequent calls. Its as if internally RecursiveIteratorIterator is re-instantiating my FilterRuleset.
public function accept() {
print_r($this->filters);
$file = $this->current();
foreach ($this->filters as $filter) {
if (!$filter->accept($file)) {
return false;
}
}
return true;
}
Output:
Array
(
[0] => FilterLapsedDirs Object
(
)
[1] => IncludeExtension Object
(
[ext:private] => wav
)
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)
Array
(
)
I'm using PHP 5.1.6 but have tested it on 5.4.14 and there's no difference. Any ideas?
When I check the filters in FilterRuleset they are populated on the first call, then blank on subsequent calls. Its as if internally RecursiveIteratorIterator is re-instantiating my FilterRuleset.
Yes, this is exactly the case. Each time you go into a subdirectory the array is empty because per recursive iterator rules, the recursive filter iterator needs to provide the child iterators.
So you've got two options here:
Apply filters on the flattened iteration, that is after tree-traversal. It looks feasible in your case as long as you only need to filter each individual file - not children.
The standard way: Take care that getChildren() returns a configured FilterRuleset recursive-filter-iterator object with the filters set.
I start with the second one because it's quickly done and the normal way to do this.
You overwrite the getChildren() parent method by adding it to your class. Then you take the result of the parent (which is the new FilterRuleset for the children and set the private member. This is possible in PHP (in case you wonder that his works because it's a private member) because it's on the same level of the class hierarchy. You then just return it and done:
class FilterRuleset extends RecursiveFilterIterator
{
private $filters = array();
...
public function getChildren() {
$children = parent::getChildren();
$children->filters = $this->filters;
return $children;
}
}
The other (first) variant is that you basically "degrade" it to the "flat" filter, that is a standard FilterIterator. Therefore you first do the recursive iteration with a RecursiveIteratorIterator and then you wrap that into your filter-iterator. As the tree has been already traversed by the earlier iterator, all this recursive stuff is not needed any longer.
So first of all turn it into a the FilterIterator:
class FilterRuleset extends FilterIterator
{
...
}
The only change is from what you extend with that class. And the you instantiate in a slightly different order:
$path = __DIR__;
$files = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
$filtered = new FilterRuleset($files);
$filtered->addFilter(Accept::byCallback(function () {
return true;
}));
foreach ($filtered as $file) {
echo $file->getPathname(), PHP_EOL;
}
I hope these examples are clear. If you play around with these and you run into a problem (or even if not), feedback always welcome.
Ah and before I forget it: Here is the mock I've used to create filters in my example above:
class Accept
{
private $callback;
public function __construct($callback) {
$this->callback = $callback;
}
public function accept($subject) {
return call_user_func($this->callback, $subject);
}
public static function byCallback($callback) {
return new self($callback);
}
}
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.
Hi I am trying to get RecursiveDirectoryIterator class using a extension on the FilterIterator to work but for some reason it is iterating on the root directory only.
my code is this.
class fileTypeFilter extends FilterIterator
{
public function __construct($path)
{
parent::__construct(new RecursiveDirectoryIterator($path));
}
public function accept()
{
$file = $this->getInnerIterator()->current();
return preg_match('/\.php/i', $file->getFilename());
}
}
$it = new RecursiveDirectoryIterator('./');
$it = new fileTypeFilter($it);
foreach ($it as $file)
{
echo $file;
}
my directory structure is something like this.
-Dir1
--file1.php
--file2.php
-Dir2
--file1.php
etc etc
But as I said before the class is not recursively iterating over the entire directory structure and is only looking at the root.
Question is, how do use a basic RescursiveDirectoryIterator to display folders and then run the FilterIterator to only show the php files in those directorys?
Cheers
The FilterIterator should accept another iterator through its constructor.
In order for the automatic recursion to happen, you need to use a RecursiveIteratorIterator to iterate over the RecursiveIterator. You don't need to, but if you don't, then the burden of calling hasChildren() and getChildren() etc... is on you.
Here's a sample. I didn't bother to accept any arguments in the constructor for FileTypeFilterIterator, although that would be a nice addition if you wanted to be able to alter the regex. But, otherwise, you don't need to define a constructor.
$it = new FileTypeFilterIterator (
new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($path),
RecursiveIteratorIterator::SELF_FIRST
)
);
class FileTypeFilterIterator extends FilterIterator
{
public function __construct(Iterator $iter)
{
parent::__construct($iter);
}
public function accept()
{
$file = $this->getInnerIterator()->current();
return preg_match('/\.php/i', $file->getFilename());
}
}
foreach($it as $name => $object){
echo "$name\n";
}
Btw, in this case, you might as well just use RegexIterator instead of extending FilterIterator.