1
0
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:
Daniil Gentili 2020-10-11 17:39:05 +02:00
parent 73301a35ba
commit d1f7671588
Signed by: danog
GPG Key ID: 8C1BE3B34B230CA7
8 changed files with 126 additions and 34 deletions

View File

@ -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

View File

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

View File

@ -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();

View File

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

View File

@ -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;
}
/**

View File

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

View File

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

View File

@ -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.');
}