Tokenizer 是一个关键组件,它提供了丰富的功能,极大地增强了 Spiral 应用的开发体验。其主要作用是扫描指定的目录,使开发者能够轻松地管理和组织他们的代码库。该工具特别擅长根据特定的接口或属性识别和使用类,从而促进各种实际应用。
自动路由注册: 一个经典的应用程序是识别控制器动作上的路由属性。通过这样做,它自动化了在应用程序中注册路由的过程,利用在控制器中定义的属性。此功能显著减少了手动开销并简化了路由机制。
模块化结构支持: 在模块化架构至关重要的项目中,Tokenizer 表现出色。它可以自动检测和注册实现特定接口的模块(例如,作为 composer 包添加的模块)。此功能确保了应用程序内各种模块的无缝集成和管理。
基于属性的配置: Tokenizer 可以扫描代码库中的特定属性,从而实现基于属性的配置和行为定义。这种方法符合现代编码实践,其中属性用于定义依赖注入、配置设置等各个方面。
定位器就像你代码的探照灯,它们帮助你找到特定的代码片段。
如果你想定位类,请使用 Spiral\Tokenizer\ClassesInterface
。使用它,你可以根据类的名称、它们实现的接口或它们包含的 traits 来搜索类。
这里有一个简单的例子。假设你想定位所有实现 \Psr\Http\Server\MiddlewareInterface
接口的类:
use Spiral\Tokenizer\ClassesInterfac;
public function findClasses(ClassesInterface $classes): void
{
foreach ($classes->getClasses(\Psr\Http\Server\MiddlewareInterface::class) as $middleware) {
dump($middleware->getFileName());
}
}
getClasses
方法将返回一个表示找到的类的 ReflectionClass
对象数组。
如果你正在寻找枚举,那么 Spiral\Tokenizer\EnumsInterface
就是你所需要的。它带有一个 getEnums
方法来帮助你进行搜索:
use Spiral\Tokenizer\EnumsInterface;
public function findEnums(EnumsInterface $enums): void
{
foreach ($enums->getEnums() as $enum) {
dump($enum->getFileName());
}
}
如果你想找到特定的接口,Spiral\Tokenizer\InterfacesInterface
可以满足你的需求。像这样使用它的 getInterfaces
方法:
use Spiral\Tokenizer\InterfacesInterface;
public function findEnums(InterfacesInterface $interfaces): void
{
foreach ($interfaces->getInterfaces() as $interface) {
dump($interface->getFileName());
}
}
警告 Tokenizer 将忽略所有包含
include
或require
语句的文件。这是因为要求这样的反射是不安全的。请不要在你的代码中使用它们。
默认情况下,Tokenizer 在 app 目录中进行搜索。但是,你可能经常希望它也考虑其他目录。幸运的是,使用 Spiral\Bootloader\TokenizerBootloader
可以轻松地自定义这一点。
以下是指定其他目录的方法:
use Spiral\Bootloader\TokenizerBootloader;
use Spiral\Boot\DirectoriesInterface;
class AppBootloader extends Bootloader
{
public function init(DirectoriesInterface $directories, TokenizerBootloader $tokenizer): void
{
$tokenizer->addDirectory($directories->get('vendor') . 'spiral/validator/src');
}
}
你可能还想从 Tokenizer 的搜索中排除特定目录。方法如下:
return [
'directories' => [
//...
],
'exclude' => [
directory('resources'),
directory('config'),
'tests',
'migrations',
],
];
注意 请记住,扩展用于类搜索的目录会减慢进程。建议仅添加对你来说必不可少的目录。
当处理庞大的目录时,Tokenizer 可能会稍微变慢。但是,你可以通过使用作用域类定位器来提高它的速度。此工具允许你通过设置特定的搜索区域(我们称之为“作用域”)来分而治之。
如果你需要在大量目录中搜索类,Tokenizer 组件的性能可能会很差。在这种情况下,你可以使用作用域类定位器来提高性能。
使用作用域类定位器,你可以定义要在命名的作用域内搜索的目录。这允许你选择性地仅搜索与你当前任务相关的目录。
要使用作用域类定位器,你需要定义你的作用域:
作用域可以在 app\config\tokenizer.php
配置文件中定义。
以下是如何定义名为 scopeName
的作用域,该作用域搜索 app/Directory
目录的示例:
return [
'scopes' => [
'scopeName' => [
'directories' => [
directory('app') . 'Directory',
],
'exclude' => [
directory('app') . 'Directory/Excluded',
]
],
]
];
注意
exclude
参数是有原因的。如果你知道目录的某些部分你不需要,只需告诉 Tokenizer 跳过它们即可。它会使事情变得更快!
准备好作用域后,你就可以指示 Tokenizer 仅在选定的作用域内搜索。这就像告诉它去哪个部门一样!
Spiral\Tokenizer\ScopedClassesInterface
允许你使用其 getScopedClasses
方法来完成此操作。只需将作用域的名称传递给它,它将返回在该区域中找到的所有类。
要使用此方法,你需要将 scope
的名称作为参数传递。然后该方法将返回一个 ReflectionClass
对象数组,表示在该作用域内找到的类。
use Spiral\Tokenizer\ScopedClassesInterface;
final class SomeLocator
{
public function __construct(
private readonly ScopedClassesInterface $locator
) {
}
public function findDeclarations(): array
{
foreach ($this->locator->getScopedClasses('scopeName') as $class) {
// ...
}
}
}
对于大型代码库,使用 Tokenizer 的定位器进行常规扫描可能会减慢速度,尤其是在你经常搜索类、枚举或接口时。类监听器提供了一种更智能的方法,允许你在不重复扫描目录的情况下监听和响应类发现。
想象一下,每次想要一本书时都必须搜索一个非常大的图书馆。这是很多工作。现在,想象一下,有人每次图书馆里有新书到达时都会告诉你。这类似于类监听器的工作方式。你不会每次都搜索图书馆,而是在新书(类)到达时得到提醒。这在应用程序引导期间特别有用,在应用程序引导期间会进行初始扫描,之后监听器会保持在循环中。
默认情况下,监听器侧重于类,但你可以轻松地将它们配置为扩大它们的范围,以涵盖枚举和接口。为此,你需要在 app\config\tokenizer.php
文件中添加以下配置:
return [
'load' => [
'classes' => true, // 搜索类
'enums' => true, // 搜索枚举
'interfaces' => true, // 搜索接口
],
];
注意 请记住,你不必同时启用这三个。根据你项目的需要进行调整。你启用的越多,该过程就会越慢。
要使用此功能,你需要在引导程序列表的顶部包含 Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader
引导程序:
public function defineSystemBootloaders(): array
{
return [
\Spiral\Tokenizer\Bootloader\TokenizerListenerBootloader::class,
// ...
];
}
在 Framework — Bootloaders 部分阅读有关引导程序的更多信息。
下一步是创建一个监听器类。这个类应该实现 Spiral\Tokenizer\TokenizationListenerInterface
,它要求两个方法:
listen(\ReflectionClass $class)
: 每次发现一个类时都会调用此方法。你可以添加逻辑来处理或存储来自此类的信息。finalize()
: 把它想象成闭幕式。一旦扫描和处理了所有类,就会调用此方法。它是根据发现的类包装事物或完成操作的完美位置。这是一个监听器的示例:
use Spiral\Attributes\ReaderInterface;
final class RouteAttributeListener implements TokenizationListenerInterface
{
private array $attributes = [];
public function __construct(
private readonly ReaderInterface $reader,
private readonly RouterInterface $router
) {
}
public function listen(\ReflectionClass $class): void
{
foreach ($class->getMethods() as $method) {
$route = $this->reader->firstFunctionMetadata($method, Route::class);
if ($route === null) {
continue;
}
$this->attributes[] = [$method, $route];
}
}
public function finalize(): void
{
foreach ($this->attributes as [$method, $route]) {
$this->router->addRoute(...);
}
}
}
例如,此监听器侦听具有特定路由属性的类,并在扫描完成后将它们添加到路由器。
为了提高应用程序的性能,你可以使用 Spiral\Tokenizer\Attribute\TargetAttribute
和 Spiral\Tokenizer\Attribute\TargetClass
属性来过滤由监听器处理的类和属性。这允许你通过过滤由监听器处理的类和属性来提高代码的性能。
当你使用属性来过滤由监听器处理的类时,组件会在应用程序的第一次引导后将过滤后的类缓存在 runtime/cache/listeners
目录中。
缓存过滤后的类为你的应用程序提供了几个好处。它大大减少了处理代码库所需的时间,因为类定位器可以从缓存中加载过滤后的类,而不是在每次应用程序启动时重新扫描你的代码库。这有助于提高应用程序的性能并减少应用程序引导所需的时间。
默认情况下,禁用过滤类的缓存。如果要启用缓存,可以将 TOKENIZER_CACHE_TARGETS
环境变量设置为 true
。
TOKENIZER_CACHE_TARGETS=true
它允许你根据类的属性过滤类。 当指定目标属性时,类定位器将仅处理具有该属性的类。 如果你有一个只需要分析特定类型类(例如具有特定路由属性的控制器类)的监听器,这可能很有用。
这是一个如何使用它的示例:
use Spiral\Tokenizer\Attribute\TargetAttribute;
#[TargetAttribute(Route::class, useAnnotations: true)]
final class RouteLocatorListener implements TokenizationListenerInterface
{
// ...
}
在此示例中,RouteAttributeListener
将仅处理具有 Route
属性的类。
这意味着如果类定位器找到了一个没有此属性的类,
它不会调用此监听器的 listen
方法。
你可以向你的监听器类添加多个属性:
use Spiral\Tokenizer\Attribute\TargetAttribute;
use Spiral\Tokenizer\TokenizationListenerInterface;
#[TargetAttribute(Route::class)]
#[TargetAttribute(SymfonyRoute::class)]
class RouteLocatorListener implements TokenizationListenerInterface
{
public function listen(\ReflectionClass $class): void
{
// 对具有 Route 或 SymfonyRoute 属性的类执行某些操作
}
}
你还可以将第二个参数 useAnnotations: true
传递给该属性,以指定 Tokenizer 也应该在类注释中查找目标属性。
使用 scanParents: true
来定位在其父类或接口中具有目标属性的类。
它的工作方式与 TargetAttribute
类似,但它不是根据属性过滤类,而是根据其类型过滤类。如果你有一个只需要分析特定类型类(例如控制器、实现特定接口或扩展特定类的类)的监听器,这很有用。
这是一个如何使用的示例
use Spiral\Tokenizer\Attribute\TargetClass;
#[TargetClass(SymfonyCommand::class)]
final class CommandLocatorListener implements TokenizationListenerInterface
{
// ...
}
在此示例中,监听器将处理所有扩展 SymfonyCommand
的类。这意味着如果类定位器找到一个扩展它的类,它将调用此监听器的 listen
方法。
注意 你可以向你的监听器类添加多个属性。
要注册你的监听器,你需要使用 Spiral\Tokenizer\TokenizerListenerRegistryInterface
。
这是一个如何注册监听器的示例:
use Spiral\Tokenizer\TokenizerListenerRegistryInterface;
class AppBootloader extends Bootloader
{
public function init(
TokenizerListenerRegistryInterface $listenerRegistry,
RouteAttributeListener $listener
): void {
$listenerRegistry->addListener($listener);
}
}
警告 为了确保正确调用你的监听器,请确保在应用程序 Kernel 的
LOAD
部分内的引导程序中注册它们。如果你在APP
kernel 部分内注册它们,则不会调用监听器。
想知道 Tokenizer 是如何设置的吗? 使用 tokenizer:info
命令。
只需运行以下命令:
php app.php tokenizer:info
包含的目录:+------------------------------------+-------+| 目录 | 范围 |+------------------------------------+-------+| /vendor/intruforce/grpc-shared/src | || app/ | |+------------------------------------+-------+排除的目录:+-----------+-------+| 目录 | 范围 |+-----------+-------+加载器:+------------+------------------------------------------------------------------------------+| 加载器 | 状态 |+------------+------------------------------------------------------------------------------+| 类 | 已启用 || 枚举 | 已禁用。要启用,请将 "TOKENIZER_LOAD_ENUMS=true" 添加到你的 .env 文件。 || 接口 | 已禁用。要启用,请将 "TOKENIZER_LOAD_INTERFACES=true" 添加到你的 .env 文件。 |+------------+------------------------------------------------------------------------------+Tokenizer 缓存:已禁用要启用缓存,请将 "TOKENIZER_CACHE_TARGETS=true" 添加到你的 .env 文件。