kafkiansky / discovery
Discover interfaces, traits and classes from application autoload classmap.
Installs: 12 816
Dependents: 0
Suggesters: 0
Security: 0
Stars: 1
Watchers: 2
Forks: 0
Open Issues: 0
Requires
- php: ^8.1
Requires (Dev)
- phpunit/phpunit: ^9.5
- vimeo/psalm: ^4.23
README
Contents
- Installation
- Usage
- Loader constraints
- Composer loader
- Array loader
- Performance optimization
- Testing
- License
Installation
composer require kafkiansky/discovery
Usage
It is often necessary to find all classes that implement specific interface(s), inherit a specific class or use specific traits.
This information could be used, in example, to implement autowiring in IOC containers. To avoid parsing the entire project, you could use classes information from the autoload_classmap.php
file that the composer
uses for optimization.
Class Implements
Discover all classes that implement specific interface(s):
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassImplements(\Stringable::class))); // discover all interfaces that implement the Stringable interface.
Discover all classes that implement all of specific interfaces:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassImplements([\Stringable::class, \ArrayAccess::class])));
Discover all classes that implement one of specific interfaces:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassImplements(interfaces: [\Stringable::class, \ArrayAccess::class], implementsAll: false)));
Class Extends
Discover all classes that extend specific class:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassExtends; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassExtends(\Exception::class)));
Class Uses
Discover all classes that use specific trait. This rule recursively searches for the desired trait among the parent traits and among the traits of traits:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassUses; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassUses(SomeTrait::class)));
Class Has Attributes
Discover all classes that use specific attributes.
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassHasAttributes; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover(new ClassHasAttributes(SomeAttribute::class)));
All
Discover all classes that satisfy list of rules:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\All; use Kafkiansky\Discovery\Rules\ClassExtends; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover( new All(new ClassImplements(\Stringable::class), new ClassExtends(\Exception::class)) ));
Any
Discover all classes that satisfy at least one rule from list:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\Any; use Kafkiansky\Discovery\Rules\ClassExtends; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover( new Any(new ClassImplements(\Stringable::class), new ClassExtends(\Exception::class)) ));
None
Discover all classes that do not satisfy any rule:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\None; use Kafkiansky\Discovery\Rules\ClassExtends; use Kafkiansky\Discovery\Rules\ClassImplements; $discovery = new Discovery(new ComposerClassmapClassLoader(__DIR__)); var_dump($discovery->discover( new None(new ClassImplements(\Stringable::class), new ClassExtends(\Exception::class)) ));
Loader constraints
If you want to specify which classes can be loaded, you can use the LoaderConstraint
interface implementation.
Out of the box comes the Kafkiansky\Discovery\CodeLocation\Composer\LoadOnlyApplicationCode
class that load only classes that matches namespaces from autoload.psr4
section of your composer.json
file.
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassImplements; use Kafkiansky\Discovery\CodeLocation\Composer\LoadOnlyApplicationCode; $applicationRoot = __DIR__; $discovery = new Discovery( new ComposerClassmapClassLoader($applicationRoot, new LoadOnlyApplicationCode($applicationRoot)) ); var_dump($discovery->discover(new ClassImplements(\Stringable::class)));
Composer loader
By default, the ComposerClassmapClassLoader
search the autoload_classmap.php
in vendor/composer/autoload_classmap.php
path and require it.
If you want pass your own classmap loader, overload the default loader using withClassMapLoader
method:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassExtends; use Kafkiansky\Discovery\CodeLocation\Composer\LoadOnlyApplicationCode; use Kafkiansky\Discovery\FileNotFound; $applicationRoot = __DIR__; $loader = new ComposerClassmapClassLoader($applicationRoot); $loader = $loader->withClassMapLoader(function (string $path): array { return [FileNotFound::class => $path . '/src/FileNotFound.php']; }); $discovery = new Discovery($loader); var_dump($discovery->discover(new ClassExtends(\Exception::class)));
Array loader
For testing purposes you may use the Kafkiansky\Discovery\CodeLocation\ArrayClassLoader
:
<?php use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\Rules\ClassExtends; use Kafkiansky\Discovery\CodeLocation\ArrayClassLoader; use Kafkiansky\Discovery\FileNotFound; $discovery = new Discovery(new ArrayClassLoader([ FileNotFound::class, ])); var_dump($discovery->discover(new ClassExtends(\Exception::class)));
Performance optimization
In production, you will want to cache classes that match the rules to avoid unnecessary checks. Then you must use DiscoveryWithCache
:
<?php use Kafkiansky\Discovery\Cache\DiscoveryWithCache; use Kafkiansky\Discovery\Cache\Adapter\FilesystemCache; use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\CodeLocation\Composer\LoadOnlyApplicationCode; use Kafkiansky\Discovery\Rules\ClassImplements; $appPath = __DIR__; $cacheDir = __DIR__.'/cache'; $discovery = new DiscoveryWithCache( new Discovery(new ComposerClassmapClassLoader($appPath), new LoadOnlyApplicationCode($appPath)), new FilesystemCache($cacheDir) ); $discovery->discover(new ClassImplements(\Stringable::class));
Or if you want to cache conditionally, use cacheIf
callable to specify when to cache and when not:
<?php use Kafkiansky\Discovery\Cache\DiscoveryWithCache; use Kafkiansky\Discovery\Cache\Adapter\FilesystemCache; use Kafkiansky\Discovery\Discovery; use Kafkiansky\Discovery\CodeLocation\Composer\ComposerClassmapClassLoader; use Kafkiansky\Discovery\CodeLocation\Composer\LoadOnlyApplicationCode; use Kafkiansky\Discovery\Rules\ClassImplements; $appPath = __DIR__; $cacheDir = __DIR__.'/cache'; $discovery = new DiscoveryWithCache( new Discovery(new ComposerClassmapClassLoader($appPath), new LoadOnlyApplicationCode($appPath)), new FilesystemCache(directory: $cacheDir, cacheIf: function (): bool { return \get_env('APP_ENV') === 'production'; }); ); $discovery->discover(new ClassImplements(\Stringable::class));
Testing
$ composer test
License
The MIT License (MIT). See License File for more information.