spatie / php-structure-discoverer
自动发现PHP应用程序内的结构
Requires
- php: ^8.1
- amphp/amp: ^v3.0
- amphp/parallel: ^2.2
- illuminate/collections: ^10.0|^11.0
- spatie/laravel-package-tools: ^1.4.3
- symfony/finder: ^6.0|^7.0
Requires (Dev)
- illuminate/console: ^10.0|^11.0
- laravel/pint: ^1.0
- nunomaduro/collision: ^7.0|^8.0
- nunomaduro/larastan: ^2.0.1
- orchestra/testbench: ^7.0|^8.0|^9.0
- pestphp/pest: ^2.0
- pestphp/pest-plugin-laravel: ^2.0
- phpstan/extension-installer: ^1.1
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-phpunit: ^1.0
- phpunit/phpunit: ^9.5|^10.0
- spatie/laravel-ray: ^1.26
README
使用此包,您可以快速发现满足特定条件的PHP应用程序中的结构。例如,您可以搜索实现接口的类
use Spatie\StructureDiscoverer\Discover; // PostModel::class, Collection::class, ... Discover::in(__DIR__)->classes()->implementing(Arrayable::class)->get();
此外,它还内置了缓存功能,使整个生产过程中的操作更快。
该包不仅限于类,还可以找到枚举、接口和特性,并为每个结构提供额外的元数据。
支持我们
我们投入了大量资源来创建最佳类别的开源包。您可以通过购买我们的付费产品之一来支持我们。
我们非常感谢您从家乡寄给我们明信片,说明您正在使用我们的哪个包。您可以在我们的联系页面上找到我们的地址。我们将发布所有收到的明信片在我们的虚拟明信片墙上。
安装
您可以通过composer安装此包
composer require spatie/php-structure-discoverer
如果您正在使用Laravel,则还可以使用以下命令发布配置文件
php artisan vendor:publish --tag="structure-discoverer-config"
这是已发布的配置文件的内容
return [ /* * A list of files that should be ignored during the discovering process. */ 'ignored_files' => [ ], /** * The directories where the package should search for structure scouts */ 'structure_scout_directories' => [ app_path(), ], /* * Configure the cache driver for discoverers */ 'cache' => [ 'driver' => \Spatie\StructureDiscoverer\Cache\LaravelDiscoverCacheDriver::class, 'store' => null, ] ];
用法
您必须定义您想要搜索结构的目录
Discover::in(__DIR__)->...
可以查找多个目录,如下所示
Discover::in(app_path('models'), app_path('enums'))->...
您可以通过以下方式获取结构
Discover::in(__DIR__)->get();
这将返回一个包含FCQN类别的数组,因为没有添加条件,该包将返回所有类、枚举、接口和特性。
您只能这样发现类
Discover::in(__DIR__)->classes()->get();
接口如下
Discover::in(__DIR__)->interfaces()->get();
枚举如下
Discover::in(__DIR__)->enums()->get();
特性如下
Discover::in(__DIR__)->traits()->get();
当您想要包含特定命名的结构时,您可以这样做
Discover::in(__DIR__)->named('MyAwesomeClass')->get();
您可以这样发现扩展另一个类的类
Discover::in(__DIR__)->extending(Model::class)->get();
发现实现接口的类、接口或枚举可以这样做
Discover::in(__DIR__)->implementing(Arrayable::class)->get();
请注意,尽管接口扩展了另一个接口,但在这个上下文中,使用implements关键字似乎更符合逻辑来查找扩展了另一个接口的接口。对于此类筛选使用extends方法将不起作用!
使用属性的类、接口或特性可以这样被发现
Discover::in(__DIR__)->withAttribute(Cast::class)->get();
为了进行更细粒度的控制,您可以使用一个闭包,该闭包接收一个DiscoveredStructure
对象(稍后将详细介绍),并且如果结构应该被包含,则应返回true
Discover::in(__DIR__) ->custom(fn(DiscoveredStructure $structure) => $structure->namespace === 'App') ->get()
更复杂的自定义条件可以嵌入到类中
class AppDiscoverCondition extends DiscoverCondition { public function satisfies(DiscoveredStructure $discoveredData): bool { return $structure->namespace === 'App'; } };
现在可以使用此条件这样做
Discover::in(__DIR__) ->custom(new AppDiscoverCondition()) ->get()
组合条件
默认情况下,所有条件都将像AND操作一样工作,所以在这种情况下
Discover::in(__DIR__)->classes()->implementing(Arrayable::class)->get();
该包将仅查找同时是类并实现Arrayble
的结构。
您可以创建如下所示的OR条件组合
Discover::in(__DIR__) ->any( ConditionBuilder::create()->classes(), ConditionBuilder::create()->enums() ) ->get();
现在,该包将仅发现类或枚举结构。
您还可以创建更复杂的操作,如and的or
Discover::in(__DIR__) ->any( ConditionBuilder::create()->exact( ConditionBuilder::create()->classes(), ConditionBuilder::create()->implementing(Arrayble::class), ), ConditionBuilder::create()->exact( ConditionBuilder::create()->enums(), ConditionBuilder::create()->implementing(Stringable::class), ) ) ->get();
此示例可以更简洁地写成这样
Discover::in(__DIR__) ->any( ConditionBuilder::create()->exact( ConditionBuilder::create()->classes()->implementing(Arrayble::class), ), ConditionBuilder::create()->exact( ConditionBuilder::create()->enums()->implementing(Stringable::class), ) ) ->get();
排序
默认情况下,发现的结构的排序将根据操作系统的默认设置进行。
您可以更改排序方式,如下所示
use Spatie\StructureDiscoverer\Enums\Sort; Discover::in(__DIR__)->sortBy(Sort::Name)->get();
以下是所有可用的排序选项
use Spatie\StructureDiscoverer\Enums\Sort; Discover::in(__DIR__)->sortBy(Sort::Name); Discover::in(__DIR__)->sortBy(Sort::Size); Discover::in(__DIR__)->sortBy(Sort::Type); Discover::in(__DIR__)->sortBy(Sort::Extension); Discover::in(__DIR__)->sortBy(Sort::ChangedTime); Discover::in(__DIR__)->sortBy(Sort::ModifiedTime); Discover::in(__DIR__)->sortBy(Sort::AccessedTime); Discover::in(__DIR__)->sortBy(Sort::CaseInsensitiveName);
缓存
此包可以缓存所有发现的结构,因此在生产中不需要进行性能密集型操作。
开始缓存最快的方式是创建一个结构侦察兵,这是一个描述你想发现什么的类
class EnumsStructureScout extends StructureScout { protected function definition(): Discover|DiscoverConditionFactory { return Discover::in(__DIR__)->enums(); } public function cacheDriver(): DiscoverCacheDriver { return new FileDiscoverCacheDriver('/path/to/temp/directory'); } }
每个结构侦察兵都从 StructureScout
扩展,应该有
- 一个定义,其中你描述要发现什么以及在哪里。就像我们之前内联做的那样
- 一个用于缓存的驱动程序。当你使用Laravel时,这个方法是不必要的,因为它已经在配置文件中定义了
在你的应用程序中,你可以这样使用侦察兵
EnumsStructureScout::create()->get();
第一次调用此方法时,整个发现过程将会运行,花费的时间会稍长一些。第二次调用将跳过发现过程并使用缓存的版本,这使得对该方法的调用非常快速!
在生产环境中
当你部署到生产环境时,你可以这样预热所有的结构侦察兵缓存
StructureScoutManager::cache([__DIR__]);
你应该提供一个目录,用于存储结构侦察兵。
如果你使用Laravel,你可以运行以下命令:``
php artisan structure-scouts:cache
你也可以这样清除所有结构侦察兵的缓存
StructureScoutManager::clear([__DIR__]);
或者,如果你使用Laravel
php artisan structure-scouts:clear
对于包
由于单个用户定义了结构侦察兵可以找到的目录,因此包无法确保结构侦察兵将通过缓存命令被发现。
可以手动添加结构侦察兵,如下所示
StructureScoutManager::add(SettingsStructureScout::class);
在Laravel应用程序中,你通常在包的ServiceProvider中这样做。
缓存驱动程序
文件
FileDiscoverCacheDriver
允许你在文件中缓存发现的结构的。你应该提供一个 directory
参数,其中所有缓存文件都应该被存储。
Laravel
LaravelDiscoverCacheDriver
将使用默认的Laravel缓存。你可以提供一个可选的 store
参数来定义要使用的存储,以及一个可选的 prefix
参数用于缓存键。
空
NullDiscoverCacheDriver
不会缓存任何内容,可用于测试目的。
自己的
可以通过扩展 DiscoverCacheDriver
接口来构建缓存驱动程序
interface DiscoverCacheDriver { public function has(string $id): bool; public function get(string $id): array; public function put(string $id, array $discovered): void; public function forget(string $id): void; }
没有结构侦察兵
你还可以在不使用侦察兵的情况下内联使用缓存,请注意,在生产环境中预热这些缓存是不可能的
Discover::in(__DIR__) ->withCache( 'Some identifier', new FileDiscoverCacheDriver('/path/to/temp/directory); ) ->get();
并行
由于需要扫描许多文件,在大型的应用程序中获取所有结构可能会很慢。可以通过并行扫描来加快此过程。你可以这样启用它
Discover::in(__DIR__)->parallel()->get();
可以设置每个进程将扫描的文件数
Discover::in(__DIR__)->parallel(100)->get();
默认情况下,每个进程将扫描50个文件。
链
通常结构通过扩展和实现继承其他结构。在发现它们时,包会自动包括这些结构。例如
class Request { } class FormRequest extends Request { } class UserFormRequest extends FormRequest { }
当使用
Discover::in(__DIR__)->extending(Request::class)->get();
两者 FormRequest
和 UserFormRequest
都会被找到,尽管 UserFormRequest
不是 Request
的直接后代,但它通过 FormRequest
是的。
你可以这样禁用扩展的行为
Discover::in(__DIR__)->extendingWithoutChain(Request::class)
或实现的行为
Discover::in(__DIR__)->implementingWithoutChain(Request::class)
解决链是一个复杂且资源密集型的过程。可以完全禁用此行为
Discover::in(__DIR__)->withoutChains()->extending(Request::class)->get();
完整信息
输出将是结构发现时的引用字符串。内部包会跟踪更多有用的信息,你可以这样检索这些信息
Discover::in(__DIR__)->full()->get();
现在返回的是 DiscoveredStructure
对象的数组,而不是字符串数组。让我们看看不同的类型
发现的类
表示一个类,$extends
和 $implements
属性处理类的直接扩展和实现。$extendsChain
和 $implementsChain
属性包含完整继承链中的所有扩展和实现。
class DiscoveredClass extends DiscoveredStructure { public function __construct( string $name, string $file, string $namespace, public bool $isFinal, public bool $isAbstract, public bool $isReadonly, public ?string $extends, public array $implements, public array $attributes, public ?array $extendsChain = null, public ?array $implementsChain = null, ) { } }
发现的接口
表示一个类,$extends
属性处理接口的直接扩展。$extendsChain
属性包含整个继承链中的所有扩展。
class DiscoveredInterface extends DiscoveredStructure { public function __construct( string $name, string $file, string $namespace, public array $extends, public array $attributes, public ?array $extendsChain = null, ) { }
发现的枚举
表示一个枚举,$implements
属性处理枚举的直接扩展。 $implementsChain
属性包含整个继承链的所有实现。 $type
属性是一个枚举,描述类型:单元
、字符串
和 整数
。
class DiscoveredEnum extends DiscoveredStructure { public function __construct( public string $name, public string $namespace, public string $file, public DiscoveredEnumType $type, public array $implements, public array $attributes, public ?array $implementsChain = null, ) { } }
发现的特质
表示应用程序中的一个发现的特质。
class DiscoveredTrait extends DiscoveredStructure { public function __construct( public string $name, public string $namespace, public string $file, ) { } }
解析器
解析器负责解析文件并返回一个结构列表。该包附带两个解析器
PhpTokenStructureParser
:读取 PHP 文件,将其令牌化,并将令牌解析为结构。ReflectionStructureParser
:使用 PHP 反射 API 读取文件并将其解析为结构。
默认情况下,由于它更健壮,使用 PhpTokenStructureParser
,而 ReflectionStructureParser
相对更快,但可能会导致 PHP 进程完全失败。
您可以如下启用 ReflectionStructureParser
Discover::in(__DIR__) ->useReflection( basePath: '/path/to/project/root', rootNamespace: null ) ->get();
您可能需要将 basePath 设置为项目的根目录,以及可选的项目根命名空间,它将被添加到前面。
对于默认的 Laravel 项目,这将是
Discover::in(__DIR__) ->useReflection(basePath: base_path()) ->get();
帮助?我的结构找不到!
此包的内部将扫描目录中的所有文件,并尝试创建一个虚拟映射,将所有结构与它们的扩展、使用和实现链接起来。
由于此文件扫描,如果引用的结构没有被扫描,则此映射是不完整的。
例如,我们扫描应用目录中所有扩展 Laravel 的 Model
的类,找到了很多模型,但缺少 User
模型。
这种情况发生的原因是
- 该包在应用目录中搜索扩展
Model
的类 User
扩展了Authenticatable
,它自身扩展了Model
Authenticatable
存储在vendor/laravel/...
目录中,该目录没有被扫描- 该包不知道
Authenticatable
扩展了Model
User
将不会找到
解决这个问题的一个方法是包括 laravel
目录在扫描过程中。
测试
composer test
变更日志
有关最近更改的更多信息,请参阅 变更日志
贡献
有关详细信息,请参阅 贡献指南
安全漏洞
请查阅 我们的安全策略 了解如何报告安全漏洞。
鸣谢
- Ruben Van Assche
- Construct Finder 对此包产生了重大影响
- 所有贡献者
许可
MIT 许可证 (MIT)。请参阅 许可文件 了解更多信息。