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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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