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
|
||||
===========
|
||||
|
||||
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)
|
||||
and provides additional features with less configuration required.
|
||||
@ -50,7 +50,14 @@ Examples
|
||||
|
||||
require_once __DIR__ . '/vendor/autoload.php';
|
||||
|
||||
// Will default to ClassFinder::ALLOW_CLASSES
|
||||
$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(
|
||||
@ -60,6 +67,27 @@ $classes = ClassFinder::getClassesInNamespace('TestApp1\Foo');
|
||||
* )
|
||||
*/
|
||||
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)*
|
||||
@ -69,7 +97,15 @@ var_dump($classes);
|
||||
|
||||
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 | 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(
|
||||
@ -85,6 +121,11 @@ $classes = ClassFinder::getClassesInNamespace('TestApp1\Foo', ClassFinder::RECUR
|
||||
* )
|
||||
*/
|
||||
var_dump($classes);
|
||||
|
||||
// You get the idea :)
|
||||
var_dump($interfaces);
|
||||
var_dump($traits);
|
||||
var_dump($classesTraits);
|
||||
```
|
||||
|
||||
Documentation
|
||||
|
@ -13,6 +13,11 @@ class ClassFinder
|
||||
{
|
||||
const STANDARD_MODE = 1;
|
||||
const RECURSIVE_MODE = 2;
|
||||
const ALLOW_CLASSES = 4;
|
||||
const ALLOW_INTERFACES = 8;
|
||||
const ALLOW_TRAITS = 16;
|
||||
|
||||
const MODE_MASK = 3;
|
||||
|
||||
/** @var AppConfig */
|
||||
private static $config;
|
||||
@ -71,6 +76,10 @@ class ClassFinder
|
||||
*/
|
||||
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();
|
||||
|
||||
$findersWithNamespace = self::findersWithNamespace($namespace);
|
||||
|
@ -3,6 +3,7 @@
|
||||
namespace HaydenPierce\ClassFinder\Classmap;
|
||||
|
||||
use HaydenPierce\ClassFinder\AppConfig;
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
|
||||
class ClassmapEntryFactory
|
||||
{
|
||||
@ -17,12 +18,18 @@ class ClassmapEntryFactory
|
||||
/**
|
||||
* @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
|
||||
// to fetch user provided entries.
|
||||
$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(count($classmap) == 0) {
|
||||
return array();
|
||||
|
@ -38,7 +38,7 @@ class ClassmapFinder implements FinderInterface
|
||||
*/
|
||||
public function findClasses($namespace, $options)
|
||||
{
|
||||
$classmapEntries = $this->factory->getClassmapEntries();
|
||||
$classmapEntries = $this->factory->getClassmapEntries($options);
|
||||
|
||||
$matchingEntries = array_filter($classmapEntries, function(ClassmapEntry $entry) use ($namespace, $options) {
|
||||
return $entry->matches($namespace, $options);
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
namespace HaydenPierce\ClassFinder\Files;
|
||||
|
||||
use HaydenPierce\ClassFinder\ClassFinder;
|
||||
|
||||
class FilesEntry
|
||||
{
|
||||
/** @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 int $options
|
||||
* @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) {
|
||||
$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.
|
||||
*
|
||||
@ -68,24 +83,35 @@ class FilesEntry
|
||||
*
|
||||
* @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.
|
||||
$script = "var_export(get_declared_classes());";
|
||||
exec($this->php . " -r \"$script\"", $output);
|
||||
$classes = 'return ' . implode('', $output) . ';';
|
||||
$initialClasses = eval($classes);
|
||||
|
||||
// clear the exec() buffer.
|
||||
unset($output);
|
||||
// get_declared_*() returns a bunch of classes|interfaces|traits that are built into PHP. So we need a control here.
|
||||
list($initialInterfaces,
|
||||
$initialClasses,
|
||||
$initialTraits
|
||||
) = $this->execReturn("var_export(array(get_declared_interfaces(), get_declared_classes(), get_declared_traits()));");
|
||||
|
||||
// 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());";
|
||||
exec($this->php . ' -r "' . $script . '"', $output);
|
||||
$classes = 'return ' . implode('', $output) . ';';
|
||||
$allClasses = eval($classes);
|
||||
list($allInterfaces,
|
||||
$allClasses,
|
||||
$allTraits
|
||||
) = $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();
|
||||
|
||||
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace){
|
||||
return array_merge($carry, $entry->getClasses($namespace));
|
||||
return array_reduce($filesEntries, function($carry, FilesEntry $entry) use ($namespace, $options){
|
||||
return array_merge($carry, $entry->getClasses($namespace, $options));
|
||||
}, array());
|
||||
}
|
||||
}
|
||||
|
@ -168,6 +168,9 @@ class PSR4Namespace
|
||||
*/
|
||||
public function findClasses($namespace, $options = ClassFinder::STANDARD_MODE)
|
||||
{
|
||||
$allowAdditional = $options & ~ClassFinder::MODE_MASK;
|
||||
$options &= ClassFinder::MODE_MASK;
|
||||
|
||||
$relativePath = substr($namespace, strlen($this->namespace));
|
||||
|
||||
$self = $this;
|
||||
@ -194,24 +197,27 @@ class PSR4Namespace
|
||||
}, $potentialClassFiles);
|
||||
|
||||
if ($options == ClassFinder::RECURSIVE_MODE) {
|
||||
return $this->getClassesFromListRecursively($namespace);
|
||||
return $this->getClassesFromListRecursively($namespace, $allowAdditional);
|
||||
} else {
|
||||
return array_filter($potentialClasses, function($potentialClass) {
|
||||
return array_filter($potentialClasses, function($potentialClass) use ($allowAdditional) {
|
||||
if (function_exists($potentialClass)) {
|
||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||
// Example: DeepCopy\deep_copy
|
||||
return false;
|
||||
} 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[]
|
||||
*/
|
||||
private function getDirectClassesOnly()
|
||||
private function getDirectClassesOnly($allowAdditional)
|
||||
{
|
||||
$self = $this;
|
||||
$directories = array_reduce($this->directories, function($carry, $directory) use ($self){
|
||||
@ -237,27 +243,30 @@ class PSR4Namespace
|
||||
return $selfNamespace . str_replace('.php', '', $file);
|
||||
}, $potentialClassFiles);
|
||||
|
||||
return array_filter($potentialClasses, function($potentialClass) {
|
||||
return array_filter($potentialClasses, function($potentialClass) use ($allowAdditional) {
|
||||
if (function_exists($potentialClass)) {
|
||||
// For some reason calling class_exists() on a namespace'd function raises a Fatal Error (tested PHP 7.0.8)
|
||||
// Example: DeepCopy\deep_copy
|
||||
return false;
|
||||
} 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 $allowAdditional
|
||||
* @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_merge($carry, $subNamespace->getClassesFromListRecursively($namespace));
|
||||
return array_reduce($this->getDirectSubnamespaces(), function($carry, PSR4Namespace $subNamespace) use ($namespace, $allowAdditional) {
|
||||
return array_merge($carry, $subNamespace->getClassesFromListRecursively($namespace, $allowAdditional));
|
||||
}, $initialClasses);
|
||||
}
|
||||
|
||||
|
@ -46,7 +46,7 @@ EOL
|
||||
|
||||
$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.');
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user