spatie/laravel-auto-discoverer

自动发现PHP应用程序中的结构

2.2.0 2024-08-29 10:43 UTC

This package is auto-updated.

Last update: 2024-09-20 13:23:06 UTC


README

Latest Version on Packagist run-tests PHPStan Check & fix styling Total Downloads

使用此包,您可以快速发现满足特定条件的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();

两者 FormRequestUserFormRequest 都会被找到,尽管 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属性是一个枚举,描述类型:UnitStringInt

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

变更日志

请参阅变更日志以获取有关最近更改的更多信息。

贡献

请参阅贡献指南以获取详细信息。

安全漏洞

请参阅我们的安全策略以了解如何报告安全漏洞。

鸣谢

许可

MIT许可(MIT)。请参阅许可文件以获取更多信息。