Php namespaces with functions - php

I have a directory include. In this directory, I have only one dir: HQF. In this directory, I have many files where I always declare them belonging to the HQF namespace like this:
<?php
namespace HQF;
class MyClass
{
}
And the file is called "MyClass.php". I'm trying to stick "a bit" to PSR0.
Everything works fine, and I've made my autoloader like this:
function __autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strripos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DS, $namespace).DS;
}
$fileName .= str_replace('_', DS, $className).'.php';
$fileName = 'include/'.$fileName;
require $fileName;
}
So when I need a class I just have to do: $m = new \HQF\MyClass(); and it works like a charm, including automagically the file "include/HQF/MyClass.php"
I have a problem with "pure" functions.
I've made a file called "include/HQF/mb_utils.php" where I want to put my mb_xxx function. NB: no classes, only functions.
I've tried all the following things without success:
$test=\HQF\mb_ucname('calling HQF/mb_ucname...');
$test=\mb_ucname('calling HQF/mb_ucname...');
use \HQF\mb_utils; and then the two tests above;
None of them work. What am I missing?

I'm relying on Mark Baker's comment.
So here's my workaround: I've made a file named Mb.php and in it, a class named "Mb" (for 'multibytes') and I've put all my mutibytes functions like this:
<?php
namespace HQF;
class Mb
{
static public function ucfirst(&$string, $e ='utf-8')
{
/* blabla */
}
static public function ucname($string, $e ='utf-8')
{
/* blabla */
}
}
And then I call them like this:
$mystring_to_change = \HQF\Mb::ucname($original_name);
This is the only way I've found, I dont know if it's the best one... any suggestion welcome.

Related

how this spl_autoload_register works

I'm studying on spl_autoload_register(). I know what it is for ,just wondering how to use it practically. I searched for few references over the net for these, and each one has it's own style of approach. However I'm interested to understand the below script which found in one of my ebook.
WHat these three lines doing, I can understand roughly but not
exactly.
The autoload function receives a parameter $class, but why the below not dealing with it at all? For which class or item its defining the path?
1) get_include_path()
2) What is this doing? $flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
$paths = explode(PATH_SEPARATOR, get_include_path());
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
$file = strtolower(str_replace("\\", DIRECTORY_SEPARATOR, trim($class, "\\"))).".php";
Script
function autoload($class)
{
$paths = explode(PATH_SEPARATOR, get_include_path());
$flags = PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE;
$file = strtolower(str_replace("\\", DIRECTORY_SEPARATOR, trim($class, "\\"))).".php";
foreach ($paths as $path)
{
$combined = $path.DIRECTORY_SEPARATOR.$file;
if (file_exists($combined))
{
include($combined);
return;
}
}
throw new Exception("{$class} not found");
}
class Autoloader
{
public static function autoload($class)
{
autoload($class);
}
}
spl_autoload_register('autoload');
spl_autoload_register(array('autoloader', 'autoload'));
// these can only be called within a class context…
// spl_autoload_register(array($this, 'autoload'));
// spl_autoload_register(__CLASS__.'::load');
The function spl_autoload_register is called with parameter 'autoload'. That makes the function autoload to be called whenever a class is not found.
When a class is not found, then the function autoload is invoked with the faulty class name as its parameter, and that parameter is actually used in its third line.
$file = strtolower(str_replace("\\", DIRECTORY_SEPARATOR, trim($class, "\\"))).".php";
It is used to try to find a file with the same name as the class (Faulty class => Faulty.php file) in any of the directories returned by core function get_include_path.
The only odd thing is that the $flags variable was set to be included as one of the parameters for the str_replace function they are actually not used at all; but I don't know if they are really needed.

Namespaces and Class loading in php

I'm new to namespaces in PHP and trying to make use of them to load classes.
Whenever I run my code I get class Cheese cannot be found on line x
PHPstorm recognizes the class via the namespaces and enables its methods.
I have the following files / directory structure.
/Project
/App
Mouse.php
/Test
MouseTest.php
Mouse.php
namespace App\Mouse;
class Cheese
{
}
MouseTest.php
namespace Test\MouseTest;
use \App\Mouse\Cheese as Cheese;
class CheeseTest
{
function test() {
$cheese = new Cheese();
$cheese->eat();
}
}
if you use composer or any autoloader that follow psr-0, then file name must same with class name, change Mouse.php to Cheese.php
This looks like a similar structure to the PSR0 standard found here: http://www.php-fig.org/psr/psr-0/
This is an example autoloader following that structure:
function autoload($className)
{
$className = ltrim($className, '\\');
$fileName = '';
$namespace = '';
if ($lastNsPos = strrpos($className, '\\')) {
$namespace = substr($className, 0, $lastNsPos);
$className = substr($className, $lastNsPos + 1);
$fileName = str_replace('\\', DIRECTORY_SEPARATOR, $namespace) . DIRECTORY_SEPARATOR;
}
$fileName .= str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
require $fileName;
}
spl_autoload_register('autoload');
The spl_autoload_register call registers the autoload function so when a class is instantiated, it will use your autoload function to retrieve the class definition.
PHP namespaces don't load classes automatically, you have to include their files.
To make it automatic (like you want) you have to make an autoloader which will load the classes depending on the namespaces that you want to use.
The best way to make this, is using composer: http://code.tutsplus.com/tutorials/easy-package-management-with-composer--net-25530
but I recommend to search "php auloader" in google

PHP - namespaces and autoloading issues

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

spl_autoloader not loading any classes

so i've started using namespaces and read some docs but I seem to be doing something wrong.
First off is my application structure which is build like this:
root
-dashboard(this is where i want to use the autoloader)
-index.php
--config(includes the autoloader)
--WePack(package)
---src(includes all my classes)
now in the src directory I included the classes with:
namespace WePack\src;
class Someclass(){
}
the content of config.php is:
<?php
// Start de sessie
ob_start();
session_start();
// Locate application path
define('ROOT', dirname(dirname(__FILE__)));
set_include_path(ROOT);
spl_autoload_extensions(".php"); // comma-separated list
spl_autoload_register();
echo get_include_path();
and I use it like this in my index.php
require_once ('config/config.php');
use WePack\src;
$someclass = new Someclass;
this is what the echo get_include_path(); returns:
/home/wepack/public_html/dashboard
which is what I want I guess. but the classes are not loaded and nothing is happening. I'm obviously missing something but I can't seem to figure it out. could you guys take a look at it and explain to me why this isn't working?
The problem here is, that you don't register a callback function with spl_autoload_register(). have a look at the official docs.
To be more flexible, you can write your own class to register and autoload classes like this:
class Autoloader
{
private $baseDir = null;
private function __construct($baseDir = null)
{
if ($baseDir === null) {
$this->baseDir = dirname(__FILE__);
} else {
$this->baseDir = rtrim($baseDir, '');
}
}
public static function register($baseDir = null)
{
//create an instance of the autoloader
$loader = new self($baseDir);
//register your own autoloader, which is contained in this class
spl_autoload_register(array($loader, 'autoload'));
return $loader;
}
private function autoload($class)
{
if ($class[0] === '\\') {
$class = substr($class, 1);
}
//if you want you can check if the autoloader is responsible for a specific namespace
if (strpos($class, 'yourNameSpace') !== 0) {
return;
}
//replace backslashes from the namespace with a normal directory separator
$file = sprintf('%s/%s.php', $this->baseDir, str_replace('\\', DIRECTORY_SEPARATOR, $class));
//include your file
if (is_file($file)) {
require_once($file);
}
}
}
after this you'll register your autoloader like this:
Autoloader::register("/your/path/to/your/libraries");
Isn't this what you mean:
spl_autoload_register(function( $class ) {
include_once ROOT.'/classes/'.$class.'.php';
});
That way you can just call a class like:
$user = new User(); // And loads it from "ROOT"/classes/User.php

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

Categories