mirror of
https://github.com/danog/class-finder.git
synced 2024-11-30 04:29:03 +01:00
Add support for finding interfaces and traits
This commit is contained in:
parent
73301a35ba
commit
d1f7671588
43
README.md
43
README.md
@ -1,7 +1,7 @@
|
|||||||
ClassFinder
|
ClassFinder
|
||||||
===========
|
===========
|
||||||
|
|
||||||
A dead simple utility to identify classes in a given namespace.
|
A dead simple utility to identify classes, traits and interfaces in a given namespace.
|
||||||
|
|
||||||
This package is an improved implementation of an [answer on Stack Overflow](https://stackoverflow.com/a/40229665/3000068)
|
This package is an improved implementation of an [answer on Stack Overflow](https://stackoverflow.com/a/40229665/3000068)
|
||||||
and provides additional features with less configuration required.
|
and provides additional features with less configuration required.
|
||||||
@ -50,7 +50,14 @@ Examples
|
|||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
// Will default to ClassFinder::ALLOW_CLASSES
|
||||||
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
|
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
|
||||||
|
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::ALLOW_CLASSES);
|
||||||
|
$interfaces = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::ALLOW_INTERFACES);
|
||||||
|
$traits = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::ALLOW_TRAITS);
|
||||||
|
|
||||||
|
// You can combine any of the flags
|
||||||
|
$interfacesTraits = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::ALLOW_TRAITS | ClassFinder::ALLOW_INTERFACES);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* array(
|
* array(
|
||||||
@ -60,6 +67,27 @@ $classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
|
|||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
var_dump($classes);
|
var_dump($classes);
|
||||||
|
/**
|
||||||
|
* array(
|
||||||
|
* 'TestApp1\Foo\BarInterface',
|
||||||
|
* 'TestApp1\Foo\FooInterface'
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
var_dump($interfaces);
|
||||||
|
/**
|
||||||
|
* array(
|
||||||
|
* 'TestApp1\Foo\BazTrait',
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
var_dump($traits);
|
||||||
|
/**
|
||||||
|
* array(
|
||||||
|
* 'TestApp1\Foo\BarInterface',
|
||||||
|
* 'TestApp1\Foo\FooInterface',
|
||||||
|
* 'TestApp1\Foo\BazTrait',
|
||||||
|
* )
|
||||||
|
*/
|
||||||
|
var_dump($interfacesTraits);
|
||||||
```
|
```
|
||||||
|
|
||||||
**Recursive Mode** *(in v0.3-beta)*
|
**Recursive Mode** *(in v0.3-beta)*
|
||||||
@ -69,7 +97,15 @@ var_dump($classes);
|
|||||||
|
|
||||||
require_once __DIR__ . '/vendor/autoload.php';
|
require_once __DIR__ . '/vendor/autoload.php';
|
||||||
|
|
||||||
|
// As before, will default to ClassFinder::ALLOW_CLASSES
|
||||||
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE);
|
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE);
|
||||||
|
$classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE | ClassFinder::ALLOW_CLASSES);
|
||||||
|
$interfaces = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE | ClassFinder::ALLOW_INTERFACES);
|
||||||
|
$traits = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE | ClassFinder::ALLOW_TRAITS);
|
||||||
|
|
||||||
|
|
||||||
|
// You can combine any of the flags
|
||||||
|
$classesTraits = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECURSIVE_MODE | ClassFinder::ALLOW_CLASSS | ClassFinder::ALLOW_TRAITS);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* array(
|
* array(
|
||||||
@ -85,6 +121,11 @@ $classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECUR
|
|||||||
* )
|
* )
|
||||||
*/
|
*/
|
||||||
var_dump($classes);
|
var_dump($classes);
|
||||||
|
|
||||||
|
// You get the idea :)
|
||||||
|
var_dump($interfaces);
|
||||||
|
var_dump($traits);
|
||||||
|
var_dump($classesTraits);
|
||||||
```
|
```
|
||||||
|
|
||||||
Documentation
|
Documentation
|
||||||
|
@ -13,6 +13,11 @@ class ClassFinder
|
|||||||
{
|
{
|
||||||
const STANDARD_MODE = 1;
|
const STANDARD_MODE = 1;
|
||||||
const RECURSIVE_MODE = 2;
|
const RECURSIVE_MODE = 2;
|
||||||
|
const ALLOW_CLASSES = 4;
|
||||||
|
const ALLOW_INTERFACES = 8;
|
||||||
|
const ALLOW_TRAITS = 16;
|
||||||
|
|
||||||
|
const MODE_MASK = 3;
|
||||||
|
|
||||||
/** @var AppConfig */
|
/** @var AppConfig */
|
||||||
private static $config;
|
private static $config;
|
||||||
@ -71,6 +76,10 @@ class ClassFinder
|
|||||||
*/
|
*/
|
||||||
public static function getClassesInNamespace($namespace, $options = self::STANDARD_MODE)
|
public static function getClassesInNamespace($namespace, $options = self::STANDARD_MODE)
|
||||||
{
|
{
|
||||||
|
if (!($options & (self::ALLOW_INTERFACES | self::ALLOW_TRAITS))) {
|
||||||
|
// Default to classes
|
||||||
|
$options |= self::ALLOW_CLASSES;
|
||||||
|
}
|
||||||
self::initialize();
|
self::initialize();
|
||||||
|
|
||||||
$findersWithNamespace = self::findersWithNamespace($namespace);
|
$findersWithNamespace = self::findersWithNamespace($namespace);
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
namespace HaydenPierce\ClassFinder\Classmap;
|
namespace HaydenPierce\ClassFinder\Classmap;
|
||||||
|
|
||||||
use HaydenPierce\ClassFinder\AppConfig;
|
use HaydenPierce\ClassFinder\AppConfig;
|
||||||
|
use HaydenPierce\ClassFinder\ClassFinder;
|
||||||
|
|
||||||
class ClassmapEntryFactory
|
class ClassmapEntryFactory
|
||||||
{
|
{
|
||||||
@ -17,12 +18,18 @@ class ClassmapEntryFactory
|
|||||||
/**
|
/**
|
||||||
* @return ClassmapEntry[]
|
* @return ClassmapEntry[]
|
||||||
*/
|
*/
|
||||||
public function getClassmapEntries()
|
public function getClassmapEntries($allowAdditional = ClassFinder::ALLOW_INTERFACES | ClassFinder::ALLOW_TRAITS)
|
||||||
{
|
{
|
||||||
// Composer will compile user declared mappings to autoload_classmap.php. So no additional work is needed
|
// Composer will compile user declared mappings to autoload_classmap.php. So no additional work is needed
|
||||||
// to fetch user provided entries.
|
// to fetch user provided entries.
|
||||||
$classmap = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_classmap.php');
|
$classmap = require($this->appConfig->getAppRoot() . 'vendor/composer/autoload_classmap.php');
|
||||||
|
|
||||||
|
$classmap = array_filter($classmap, function ($potentialClass) use ($allowAdditional) {
|
||||||
|
return ($allowAdditional & ClassFinder::ALLOW_CLASSES && class_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_INTERFACES && interface_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_TRAITS && trait_exists($potentialClass));
|
||||||
|
}, ARRAY_FILTER_USE_KEY);
|
||||||
|
|
||||||
// if classmap has no entries return empty array
|
// if classmap has no entries return empty array
|
||||||
if(count($classmap) == 0) {
|
if(count($classmap) == 0) {
|
||||||
return array();
|
return array();
|
||||||
|
@ -38,7 +38,7 @@ class ClassmapFinder implements FinderInterface
|
|||||||
*/
|
*/
|
||||||
public function findClasses($namespace, $options)
|
public function findClasses($namespace, $options)
|
||||||
{
|
{
|
||||||
$classmapEntries = $this->factory->getClassmapEntries();
|
$classmapEntries = $this->factory->getClassmapEntries($options);
|
||||||
|
|
||||||
$matchingEntries = array_filter($classmapEntries, function(ClassmapEntry $entry) use ($namespace, $options) {
|
$matchingEntries = array_filter($classmapEntries, function(ClassmapEntry $entry) use ($namespace, $options) {
|
||||||
return $entry->matches($namespace, $options);
|
return $entry->matches($namespace, $options);
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace HaydenPierce\ClassFinder\Files;
|
namespace HaydenPierce\ClassFinder\Files;
|
||||||
|
|
||||||
|
use HaydenPierce\ClassFinder\ClassFinder;
|
||||||
|
|
||||||
class FilesEntry
|
class FilesEntry
|
||||||
{
|
{
|
||||||
/** @var string */
|
/** @var string */
|
||||||
@ -38,14 +40,14 @@ class FilesEntry
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Gets a list of classes that belong to the given namespace.
|
* Gets a list of classes that belong to the given namespace
|
||||||
*
|
|
||||||
* @param string $namespace
|
* @param string $namespace
|
||||||
|
* @param int $options
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
public function getClasses($namespace)
|
public function getClasses($namespace, $options)
|
||||||
{
|
{
|
||||||
$classes = $this->getClassesInFile();
|
$classes = $this->getClassesInFile($options);
|
||||||
|
|
||||||
return array_values(array_filter($classes, function($class) use ($namespace) {
|
return array_values(array_filter($classes, function($class) use ($namespace) {
|
||||||
$classNameFragments = explode('\\', $class);
|
$classNameFragments = explode('\\', $class);
|
||||||
@ -58,6 +60,19 @@ class FilesEntry
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute PHP code and return retuend value
|
||||||
|
*
|
||||||
|
* @param string $script
|
||||||
|
* @return mixed
|
||||||
|
*/
|
||||||
|
private function execReturn($script)
|
||||||
|
{
|
||||||
|
exec($this->php . " -r \"$script\"", $output);
|
||||||
|
$classes = 'return ' . implode('', $output) . ';';
|
||||||
|
return eval($classes);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dynamically execute files and check for defined classes.
|
* Dynamically execute files and check for defined classes.
|
||||||
*
|
*
|
||||||
@ -68,24 +83,35 @@ class FilesEntry
|
|||||||
*
|
*
|
||||||
* @return array
|
* @return array
|
||||||
*/
|
*/
|
||||||
private function getClassesInFile()
|
private function getClassesInFile($options = ClassFinder::ALLOW_INTERFACES | ClassFinder::ALLOW_TRAITS)
|
||||||
{
|
{
|
||||||
// get_declared_classes() returns a bunch of classes that are built into PHP. So we need a control here.
|
// get_declared_*() returns a bunch of classes|interfaces|traits that are built into PHP. So we need a control here.
|
||||||
$script = "var_export(get_declared_classes());";
|
list($initialInterfaces,
|
||||||
exec($this->php . " -r \"$script\"", $output);
|
$initialClasses,
|
||||||
$classes = 'return ' . implode('', $output) . ';';
|
$initialTraits
|
||||||
$initialClasses = eval($classes);
|
) = $this->execReturn("var_export(array(get_declared_interfaces(), get_declared_classes(), get_declared_traits()));");
|
||||||
|
|
||||||
// clear the exec() buffer.
|
|
||||||
unset($output);
|
|
||||||
|
|
||||||
// This brings in the new classes. so $classes here will include the PHP defaults and the newly defined classes
|
// This brings in the new classes. so $classes here will include the PHP defaults and the newly defined classes
|
||||||
$script = "require_once '{$this->file}'; var_export(get_declared_classes());";
|
list($allInterfaces,
|
||||||
exec($this->php . ' -r "' . $script . '"', $output);
|
$allClasses,
|
||||||
$classes = 'return ' . implode('', $output) . ';';
|
$allTraits
|
||||||
$allClasses = eval($classes);
|
) = $this->execReturn("require_once '{$this->file}'; var_export(array(get_declared_interfaces(), get_declared_classes(), get_declared_traits()));");
|
||||||
|
|
||||||
return array_diff($allClasses, $initialClasses);
|
$interfaces = array_diff($allInterfaces, $initialInterfaces);
|
||||||
|
$classes = array_diff($allClasses, $initialClasses);
|
||||||
|
$traits = array_diff($allTraits, $initialTraits);
|
||||||
|
|
||||||
|
$final = array();
|
||||||
|
if ($options & ClassFinder::ALLOW_CLASSES) {
|
||||||
|
$final = $classes;
|
||||||
|
}
|
||||||
|
if ($options & ClassFinder::ALLOW_INTERFACES) {
|
||||||
|
$final = array_merge($final, $interfaces);
|
||||||
|
}
|
||||||
|
if ($options & ClassFinder::ALLOW_TRAITS) {
|
||||||
|
$final = array_merge($final, $traits);
|
||||||
|
}
|
||||||
|
return $final;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,8 +52,8 @@ class FilesFinder implements FinderInterface
|
|||||||
{
|
{
|
||||||
$filesEntries = $this->factory->getFilesEntries();
|
$filesEntries = $this->factory->getFilesEntries();
|
||||||
|
|
||||||
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace){
|
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace, $options){
|
||||||
return array_merge($carry, $entry->getClasses($namespace));
|
return array_merge($carry, $entry->getClasses($namespace, $options));
|
||||||
}, array());
|
}, array());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -168,6 +168,9 @@ class PSR4Namespace
|
|||||||
*/
|
*/
|
||||||
public function findClasses($namespace, $options = ClassFinder::STANDARD_MODE)
|
public function findClasses($namespace, $options = ClassFinder::STANDARD_MODE)
|
||||||
{
|
{
|
||||||
|
$allowAdditional = $options & ~ClassFinder::MODE_MASK;
|
||||||
|
$options &= ClassFinder::MODE_MASK;
|
||||||
|
|
||||||
$relativePath = substr($namespace, strlen($this->namespace));
|
$relativePath = substr($namespace, strlen($this->namespace));
|
||||||
|
|
||||||
$self = $this;
|
$self = $this;
|
||||||
@ -194,24 +197,27 @@ class PSR4Namespace
|
|||||||
}, $potentialClassFiles);
|
}, $potentialClassFiles);
|
||||||
|
|
||||||
if ($options == ClassFinder::RECURSIVE_MODE) {
|
if ($options == ClassFinder::RECURSIVE_MODE) {
|
||||||
return $this->getClassesFromListRecursively($namespace);
|
return $this->getClassesFromListRecursively($namespace, $allowAdditional);
|
||||||
} else {
|
} else {
|
||||||
return array_filter($potentialClasses, function($potentialClass) {
|
return array_filter($potentialClasses, function($potentialClass) use ($allowAdditional) {
|
||||||
if (function_exists($potentialClass)) {
|
if (function_exists($potentialClass)) {
|
||||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||||
// Example: DeepCopy\deep_copy
|
// Example: DeepCopy\deep_copy
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return class_exists($potentialClass);
|
return ($allowAdditional & ClassFinder::ALLOW_CLASSES && class_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_INTERFACES && interface_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_TRAITS && trait_exists($potentialClass));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @param int $allowAdditional
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
private function getDirectClassesOnly()
|
private function getDirectClassesOnly($allowAdditional)
|
||||||
{
|
{
|
||||||
$self = $this;
|
$self = $this;
|
||||||
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
|
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
|
||||||
@ -237,27 +243,30 @@ class PSR4Namespace
|
|||||||
return $selfNamespace . str_replace('.php', '', $file);
|
return $selfNamespace . str_replace('.php', '', $file);
|
||||||
}, $potentialClassFiles);
|
}, $potentialClassFiles);
|
||||||
|
|
||||||
return array_filter($potentialClasses, function($potentialClass) {
|
return array_filter($potentialClasses, function($potentialClass) use ($allowAdditional) {
|
||||||
if (function_exists($potentialClass)) {
|
if (function_exists($potentialClass)) {
|
||||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||||
// Example: DeepCopy\deep_copy
|
// Example: DeepCopy\deep_copy
|
||||||
return false;
|
return false;
|
||||||
} else {
|
} else {
|
||||||
return class_exists($potentialClass);
|
return ($allowAdditional & ClassFinder::ALLOW_CLASSES && class_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_INTERFACES && interface_exists($potentialClass))
|
||||||
|
|| ($allowAdditional & ClassFinder::ALLOW_TRAITS && trait_exists($potentialClass));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param string $namespace
|
* @param string $namespace
|
||||||
|
* @param $allowAdditional
|
||||||
* @return string[]
|
* @return string[]
|
||||||
*/
|
*/
|
||||||
public function getClassesFromListRecursively($namespace)
|
public function getClassesFromListRecursively($namespace, $allowAdditional)
|
||||||
{
|
{
|
||||||
$initialClasses = strpos( $this->namespace, $namespace) !== false ? $this->getDirectClassesOnly() : array();
|
$initialClasses = strpos( $this->namespace, $namespace) !== false ? $this->getDirectClassesOnly($allowAdditional) : array();
|
||||||
|
|
||||||
return array_reduce($this->getDirectSubnamespaces(), function($carry, PSR4Namespace $subNamespace) use ($namespace) {
|
return array_reduce($this->getDirectSubnamespaces(), function($carry, PSR4Namespace $subNamespace) use ($namespace, $allowAdditional) {
|
||||||
return array_merge($carry, $subNamespace->getClassesFromListRecursively($namespace));
|
return array_merge($carry, $subNamespace->getClassesFromListRecursively($namespace, $allowAdditional));
|
||||||
}, $initialClasses);
|
}, $initialClasses);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ EOL
|
|||||||
|
|
||||||
$files = new FilesEntry($tmpFilename, $this->findPHP());
|
$files = new FilesEntry($tmpFilename, $this->findPHP());
|
||||||
|
|
||||||
$classes = $files->getClasses($namespace);
|
$classes = $files->getClasses($namespace, 0);
|
||||||
|
|
||||||
$this->assertEquals($expected, $classes, 'FilesEntry should be able to determine the classes defined in a given file.');
|
$this->assertEquals($expected, $classes, 'FilesEntry should be able to determine the classes defined in a given file.');
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user