PHP instantiating child class - php

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.

Related

Assign functions from another file to a Class

I am trying to add functions to class from a separate file, I wonder if this could be possible!
$mClass = new MyClass();
$mClass->new_Functions[0](10); // Is there a way to have it in this form?
class myClass
{
private $Pvar = 5;
$new_Fcuntions;
function __construct()
{
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
}
}
[additional.functions.php] file
function operate($y)
{
return $this->Pvar * $y;
}
----- Edited ------- as it wasn't clear!
"additional.functions.php" is a module and there will be multiple modules to be added to the application, and every module could have more than single function and modules could call one another!
additional.functions.php [module file]
function operate($y)
{
return $this->Pvar * $y;
}
function do-more($foo)
{
return $this->operate(20) + $foo;
}
another.functions.php [another module]
function do-another($foo)
{
return $this->do-more(30) - $foo;
}
function add($foo, $bar)
{
return $foo + $bar;
}
appreciate every participation, its been a while since I am trying to maneuver around with it!
Is this possible or should I give up!
It looks to me like you are looking for Traits, which are a new feature as of PHP 5.4.0. Using traits, you can have snippets of code "mixed in" to other classes, a concept known as "horizontal reuse".
If you are not looking for traits, it's possible that you could do what you wanted with Runkit, however I would suggest staying as far away from it as possible, if you are not genuinely interested in PHP internals as well.
In any event, whatever you are trying to do is very interesting
I got it to work with dependency injection. The pvar has to be public or create a __get method to return the private variable. I also used the function name because it seems cleaner to me to use it via name rather than it's position in the list but if you want to keep that then just put $key where you see $value from the line: $this->function_list[$value] = ...
function operate($y, $that)
{
return $that->Pvar * $y;
}
class Example {
public $function_list = array();
private $Pvar = 5;
public function __construct()
{
$list = get_defined_functions();
$that = $this;
foreach ($list['user'] as $key => $value) {
$this->function_list[$value] = function() use ($value, $that) {
print call_user_func_array($value, array_merge(func_get_args(), array($that )));
};
}
}
public function __get($key)
{
if (isSet($this->$key)) {
return $this->$key;
} else {
throw new \Exception('Key "'.$key.'" does not exist');
}
}
}
$Ex = new Example();
$Ex->function_list['operate'](10);
If you want to extend MyClass from your modules (and not to initialize it, like in your example code), than you could do it in a way like this:
<?php
namespace modules\MyModuleA;
class MyClassExtension
{
private $MyObject;
public function __construct(\MyClass $MyObject)
{
$this->MyObject = $MyObject;
}
public function doSomething($anyParameter)
{
return $this->MyObject->doSomethingElse($anyParameter * 5, 42, 'foo');
}
}
And MyClass:
<?php
class MyClass extends \Extensible
{
// some code
}
abstract class Extensible
{
private $extensions = [];
public function extend($extension)
{
$this->extensions[] = $extension;
}
public function __call($methodName, $parameters)
{
foreach ($this->extensions as $Extension) {
if (in_array($methodName, get_class_methods($Extension))
return call_user_func_array([$Extension, $methodName], $parameters);
}
throw new \Exception('Call to undefined method ' . $methodName . '...');
}
public function hasExtension($extensionName)
{
return in_array($this->extensions, $extensionName);
}
}
And put it all together:
<?php
$moduleNames = ['MyModuleA', 'MyModuleB'];
$MyObject = new \MyClass;
foreach ($moduleNames as $moduleName) {
$className = '\\modules\\' . $moduleName . '\\MyClassExtension';
$module = new $className($MyObject);
$MyObject->extend($module);
}
// Now you can call a method, that has been added by MyModuleA:
$MyObject->doSomething(10);
You should add an interface for the extension classes of course...
The problem is: What happens if any code in your application calls a method of $MyObject, that is not there, because the module has not been loaded. You would always have to check if ($MyObject->hasExtension('ModuleA')) { ... }, but, of course, the application shouldn't be aware of any module. So I would not design an application in such a way.
I would suggest to use traits (mix-ins). See PHP reference
If you can have another class in that file instead of file with functions
- the best solution will be Traits
http://php.net/manual/en/language.oop5.traits.php
or using inheritance
If you move that code to class you can avoid a lot of unnecessary code. I mean:
include('additional.functions.php');
$arr = get_defined_functions();
$this->new_Functions = $arr['user'];
// trying to call the function with parameter 10
call_user_func(array($this, $this->new_Functions[0]), 10);
It'll be e.g.:
class myClass extends MyBaseClassWithMyAwesomeFunctions
{
private $Pvar = 5;
}
Maybe this approach helps you:
In the files with the additional functions, don't define named functions, but return a closure, that expects (at least) the object (instance of MyClass) as parameter:
<?php
// additional.functions.php
return function ($myObject) {
$Object->multiplyPvar($myObject->getTheNumber());
$Object->doSomethingElse(42, 'foo');
};
The client, that builds MyClass collects those functions from the files into the array:
<?php
$files = [
'/path/to/my/additional.functions1.php',
'/path/to/my/additional.functions2.php'
];
$initFunctions = [];
foreach ($files as $path)
$initFunctions[] = include $path;
$MyObject = new \MyClass($initFunctions);
The constructor then calls those functions:
<?php
class MyClass
{
public function __construct(array $additionalInitFunctions)
{
foreach ($additionalInitFunctions as $additionalInitFunction)
$additionalInitializerFunction($this); // you can also add parameters of course
}
}
This way the class keeps very well testable as well as the function files. Maybe this could help you in any way. You should never ever think about modifying the internal (private) state of an object directly from any code from outside of the class. This is not testable! Think about writing tests before you implement your code (called "test driven development"). You will see, it is not possible to test a class, if you allow any code outside of that class to modify the internal (private) state of the class instance. And you don't want to have this. If you change some internal implementation detail in your class without breaking the unit test of that class, you will anyways probably break some code in any of your additional.functions.php files and no test will tell you: "Hey: you've broken something right now".

How to get all class names inside a particular namespace?

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;');

How to ignore directories using RecursiveIteratorIterator?

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, …);

Building a "factory" in PHP

I have created a File class, which takes care of all operations on files, I/O, and which acts differently depending on the nature of the files. I'm not happy with its actual structure, which looks like this:
class File
{
function __construct($id)
{
$bbnq = sprintf("
SELECT *
FROM documents
WHERE id = %u",
$id);
$req = bbnf_query($bbnq);
$bbn = $req->fetch();
$this->file_type = $bbn['file_type'];
$this->file_name = $bbn['file_name'];
$this->title = $bbn['title'];
}
function display()
{
return ''.$this->title.'';
}
}
class Image extends File
{
function __construct($id)
{
global $bbng_imagick;
if ( $bbng_imagick )
$this->imagick = true;
parent::__construct($id);
}
function display()
{
return '<img src="'.$this->file_name.'" alt="'.$this->title.'" />';
}
}
Here I need first to know the file type in order to determine which class/subclass to use.
And I'd like to achieve the opposite, i.e. send an ID to my class, which returns an object corresponding to the file type.
I have recently updated to PHP 5.3, and I know there are some new features which could be of use for creating a "factory" (late static bindings?). My OOP knowledge is pretty light, so I wonder if some have structural suggestions in order to make a unique class which will call the right constructor.
Thanks!
I don't think late static bindings is relevant here - a factory pattern doesn't require them. Try this:
class FileFactory
{
protected static function determineFileType($id)
{
// Replace these with your real file logic
$isImage = ($id>0 && $id%2);
$isFile = ($id>0 && !($id%2));
if ($isImage) return "Image";
elseif ($isFile) return "File";
throw new Exception("Unknown file type for #$id");
}
public static function getFile($id) {
$class = self::determineFileType($id);
return new $class($id);
}
}
// Examples usage(s)
for ($i=3; $i>=0; $i--) {
print_r(FileFactory::getFile($i));
}
As an aside, you should definitely escape your output from the DB, no matter how safe you think it is. Test with double quotes in a title, for example (let alone more malicious input).
Also if it's part of a project, you might want to separate the View layer (your HTML output) from this Model layer, ie implement MVC...
In your factory's constructor, you need to determine the file type, then with that, create an object of the corresponding class. Something like this perhaps:
class File
{
public static function factory($id)
{
$fileData = <query this $id>
switch ($fileData->type) {
case image:
return new ImageFile($fileData);
break;
case html:
return new HtmlFile($fileData);
break;
default:
// error?
}
}
}
abstract class FileAbstract
{
// common file methods here
}
// override the custom bits for each type
class ImageFile extends FileAbstract
{
public function display()
{
// ...
}
}
class HtmlFile extends FileAbstract
{
public function display()
{
// ...
}
}
Your code would then simply be:
$myFile = File::factory($id);
$myFile->display();

Autoloader for functions

Last week I learned that classes can be included in your project by writing an __autoload() function. Then I learned that using an autoloader isn't only a technique but also a pattern.
Now I'm using the autoloader in my project and I've found it very very useful. I was wondering if it could be possible to do the same thing with functions. It could be very useful to forget about including the right PHP file with functions inside it.
So, is it possible to create a function autoloader?
There is no function auto-loader for functions. You have four realistic solutions:
Wrap all functions into namespacing classes (context appropriate). So let's say you have a function called string_get_letters. You could add that to a class called StringFunctions as a static function. So instead of calling string_get_letters(), you'd call StringFunctions::get_letters(). You would then __autoload those namespaced classes.
Pre-load all functions. Since you're using classes, you shouldn't have that many functions, so just pre-load them.
Load functions prior to using them. In each file, require_once the function files that are going to be used in that file.
Don't use functions in the first place. If you are developing OOP code (which it seems like you are anyway), there should be little to no need for functions at all. Everything you would need a function (or multiple) for, you could build in a OO manner and avoid the need for functions.
Personally, I'd suggest either 1, 2 or 4 depending on your exact need and the quality and size of your codebase...
If you are using Composer in your Project, you can add a files directive to the autoload section.
This will than actually generate a require_once in the autoloader, but it feels like real autoloading, because you dont have to take care of that.
Its not lazy loading though.
Example taken from Assetic:
"autoload": {
"psr-0": { "Assetic": "src/" },
"files": [ "src/functions.php" ]
}
I read something a while back about an ugly hack that caught fatal errors and tried to include and execute the missing function(s), but I definitely wouldn't go that road.
The closest thing you have is the __call() magic method, which is sort of a __autoload() for methods, not functions. It might be good enough for your needs; if you can afford to call a class and require each different function separately. Since PHP 5.3.0, you also have __callStatic().
An example using __callStatic():
class Test
{
public function __callStatic($m, $args)
{
if (function_exists($m) !== true)
{
if (is_file('./path/to/functions/' . $m . '.php') !== true)
{
return false;
}
require('./path/to/functions/' . $m . '.php');
}
return call_user_func_array($m, $args);
}
}
Test::functionToLoad(1, 2, 3);
This would call the functionToLoad() function defined in ./path/to/functions/functionToLoad.php.
Well, as usual there is a PECL extension for that:
automapPECL
(via: http://phk.tekwire.net/joomla/support/doc/automap.htm)
It's supposed to autoload functions as well as classes. Which however doesn't work with the current PHP interpreter yet.
(An alternative option btw, is generating stub functions that load and run namespaced counterparts.)
That being said. Autoloading is not universally considered a good practice. It leads to overly fractured class hierarchies and object happiness. And the real reason PHP has autoloading is because include and dependency management systems are inmature.
namespace MyNamespace;
class Fn {
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
public static function __callStatic($fn, $args) {
if (!function_exists($fn)) {
$fn = "YOUR_FUNCTIONS_NAMESPACE\\$fn";
require str_replace('\\', '/', $fn) . '.php';
}
return call_user_func_array($fn, $args);
}
}
And using namespaces, we can do: Fn::myFunc() and spl_autoload_register(). I've used this code with examples at: https://goo.gl/8dMIMj
I use a Class and __invoke. The __invoke method is called when a script calls a class as a function. I often do something like this:
<?php
namespace API\Config;
class Slim {
function __invoke() {
return [
'settings' => [
'displayErrorDetails' => true,
'logger' => [
'name' => 'api',
'level' => Monolog\Logger\Logger::DEBUG,
'path' => __DIR__ . '/../../logs/api.log',
],
]
];
}
}
I can then call like a function:
$config = API\Config\Slim;
$app = Slim\App($config())
new Functions\Debug() will load functions to root namespace.
namespace Functions
{
class Debug
{
}
}
namespace
{
if (! function_exists('printr')) {
/**
*
* #param mixed $expression
*/
function printr()
{
foreach (func_get_args() as $v) {
if (is_scalar($v)) {
echo $v . "\n";
} else {
print_r($v);
}
}
exit();
}
}
}
Here is another rather complex example, based on the suggestions in this discussion.
The code can also be seen here: lib/btr.php
<?php
/**
* A class that is used to autoload library functions.
*
* If the function btr::some_function_name() is called, this class
* will convert it into a call to the function
* 'BTranslator\some_function_name()'. If such a function is not
* declared then it will try to load these files (in this order):
* - fn/some_function_name.php
* - fn/some_function.php
* - fn/some.php
* - fn/some/function_name.php
* - fn/some/function.php
* - fn/some/function/name.php
* The first file that is found will be loaded (with require_once()).
*
* For the big functions it makes more sense to declare each one of them in a
* separate file, and for the small functions it makes more sense to declare
* several of them in the same file (which is named as the common prefix of
* these files). If there is a big number of functions, it can be more
* suitable to organize them in subdirectories.
*
* See: http://stackoverflow.com/questions/4737199/autoloader-for-functions
*/
class btr {
/**
* Make it TRUE to output debug info on '/tmp/btr.log'.
*/
const DEBUG = FALSE;
/**
* The namespace of the functions.
*/
const NS = 'BTranslator';
/**
* Relative directory where the functions are located.
*/
const FN = 'fn';
private function __construct() {}
private function __wakeup() {}
private function __clone() {}
/**
* Return the full name (with namespace) of the function to be called.
*/
protected static function function_name($function) {
return self::NS . '\\' . $function;
}
/**
* Return the full path of the file to be loaded (with require_once).
*/
protected static function file($fname) {
return dirname(__FILE__) . '/' . self::FN . '/' . $fname . '.php';
}
/**
* If a function does not exist, try to load it from the proper file.
*/
public static function __callStatic($function, $args) {
$btr_function = self::function_name($function);
if (!function_exists($btr_function)) {
// Try to load the file that contains the function.
if (!self::load_search_dirs($function) or !function_exists($btr_function)) {
$dir = dirname(self::file($fname));
$dir = str_replace(DRUPAL_ROOT, '', $dir);
throw new Exception("Function $btr_function could not be found on $dir");
}
}
return call_user_func_array($btr_function, $args);
}
/**
* Try to load files from subdirectories
* (by replacing '_' with '/' in the function name).
*/
protected static function load_search_dirs($fname) {
do {
self::debug($fname);
if (file_exists(self::file($fname))) {
require_once(self::file($fname));
return TRUE;
}
if (self::load_search_files($fname)) {
return TRUE;
}
$fname1 = $fname;
$fname = preg_replace('#_#', '/', $fname, 1);
} while ($fname != $fname1);
return FALSE;
}
/**
* Try to load files from different file names
* (by removing the part after the last undescore in the functin name).
*/
protected static function load_search_files($fname) {
$fname1 = $fname;
$fname = preg_replace('/_[^_]*$/', '', $fname);
while ($fname != $fname1) {
self::debug($fname);
if (file_exists(self::file($fname))) {
require_once(self::file($fname));
return TRUE;
}
$fname1 = $fname;
$fname = preg_replace('/_[^_]*$/', '', $fname);
}
return FALSE;
}
/**
* Debug the order in which the files are tried to be loaded.
*/
public static function debug($fname) {
if (!self::DEBUG) {
return;
}
$file = self::file($fname);
$file = str_replace(DRUPAL_ROOT, '', $file);
self::log($file, 'Autoload');
}
/**
* Output the given parameter to a log file (useful for debugging).
*/
public static function log($var, $comment ='') {
$file = '/tmp/btr.log';
$content = "\n==> $comment: " . print_r($var, true);
file_put_contents($file, $content, FILE_APPEND);
}
}
While you can't autoload functions and constants, you can use something like jesseschalken/autoload-generator which will automatically detect what files contain things which can't be autoloaded and load them eagerly.
The solution I came up with. As lightweight as I could come up with.
class functions {
public static function __callstatic($function, $arguments) {
if (!function_exists($function)) {
$file = strtok($function, '_') .'.php';
include '/path/to/functions/'.$file;
}
return call_user_func_array($function, $arguments);
}
}
Use it by calling functions::foo_bar($anything).
I try to use the autoloading of classes to my advantage. So, when a class is auto-loaded, the class file is executed. Therefore, I create a class with a static method called 'boot' that does nothing. When I invoke that method, the class will be autoloaded, hence every function in that file will be defined in the global scope. What's even more interesting is that the functions will be defined in the namespace of the class, so there are no clashes.
For example:
<?PHP
namespace Functions;
// functions are defined in this file
class GlobalFunctions{
public static function boot(){};
}
function foo(){ // code... }
?>
// the client file
<?php
// VS Code automatically does this.
use Functions\GlobalFunctions;
use function Functions\foo;
// I usually put this in the bootstrap file
GlobalFunctions::boot();
// call foo() from the Functions namespace
foo();
?>
Include all functions file in one file and then include it
//File 1
db_fct.php
//File 2
util_fct.php
//In a functions.php include all other files
<?php
require_once 'db_fct.php';
require_once 'util_fct.php';
?>
Include functions.php whenever you need functions ..
try this
if ($handle = opendir('functions')) {
while (false !== ($entry = readdir($handle))) {
if (strpos($entry, '.php') !== false) {
include("functions/$entry");
}
}
closedir($handle);
}

Categories