spatie / laravel-auto-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();
现在,该包将只发现类或枚举结构。
您还可以创建更复杂的操作,如or of and's
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
对象的数组,而不是字符串数组。让我们来看看不同的类型。
DiscoveredClass
表示一个类,$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, ) { } }
DiscoveredInterface
表示一个类,$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, ) { }
DiscoveredEnum
表示一个枚举,$implements
属性处理枚举的直接继承。而$implementsChain
属性包含整个继承链中的所有实现。此外,$type
属性是一个枚举,描述类型:Unit
、String
和Int
。
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, ) { } }
DiscoveredTrait
表示应用程序中发现的特性。
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();
需要帮助?我的结构找不到!
这个包的内部将扫描目录中的所有文件,并尝试创建一个虚拟映射,将所有结构及其继承、使用和实现联系起来。
由于这个文件扫描,如果引用的结构没有被扫描,这个映射是不完整的。
例如,我们在app目录中搜索所有扩展Laravel的Model
的类,找到了很多模型,但缺少了User
模型。
这种情况发生的原因是
- 这个包在app目录中搜索扩展
Model
的类 User
扩展了Authenticatable
,而它本身扩展了Model
Authenticatable
存储在vendor/laravel/...
目录中,而这个目录没有被扫描- 这个包不知道
Authenticatable
扩展了Model
User
将无法找到
解决这个问题的一种方法是将在扫描过程中包括laravel
目录。
测试
composer test
变更日志
请参阅变更日志以获取有关最近更改的更多信息。
贡献
请参阅贡献指南以获取详细信息。
安全漏洞
请参阅我们的安全策略以了解如何报告安全漏洞。
鸣谢
- Ruben Van Assche
- Construct Finder对这个包产生了重大影响
- 所有贡献者
许可
MIT许可(MIT)。请参阅许可文件以获取更多信息。