spatie/php-structure-discoverer

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

2.2.0 2024-08-29 10:43 UTC

This package is auto-updated.

Last update: 2024-09-20 13:23:07 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();

现在,该包将仅发现类或枚举结构。

您还可以创建更复杂的操作,如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();

两者 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 对象的数组,而不是字符串数组。让我们看看不同的类型

发现的类

表示一个类,$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

变更日志

有关最近更改的更多信息,请参阅 变更日志

贡献

有关详细信息,请参阅 贡献指南

安全漏洞

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

鸣谢

许可

MIT 许可证 (MIT)。请参阅 许可文件 了解更多信息。