如何在Symfony项目中查找所有使用特定注解的类?
Symfony 5.3 全局扫描自定义注解的标准实现方案
核心逻辑
你猜测的完全正确,Doctrine扫描@Entity注解的逻辑就是在缓存预热/容器编译阶段遍历指定目录的类,读取注解后将结果缓存,Symfony生态下有标准的实现流程,不需要运行时遍历所有类,性能无影响。
具体实现步骤
1. 依赖准备
确保你已经安装了所需组件:
symfony/finder:Symfony自带的文件查找工具,用于定位类文件doctrine/annotations:注解阅读器(如果使用PHP8+原生属性可以不需要)
composer require symfony/finder doctrine/annotations
2. 实现容器编译期扫描逻辑
Symfony容器在编译时会执行所有注册的CompilerPass,这一阶段的执行结果会被缓存到容器中,只有清空缓存时才会重新执行,完美解决扫描效率问题。
新建扫描用的CompilerPass:
// src/DependencyInjection/Compiler/MyAnnotationScanPass.php namespace App\DependencyInjection\Compiler; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\Finder\Finder; use Doctrine\Common\Annotations\AnnotationReader; use App\Annotation\MyAnnotation; // 替换成你自定义的注解类 class MyAnnotationScanPass implements CompilerPassInterface { public function process(ContainerBuilder $container) { $annotationReader = new AnnotationReader(); $annotatedClasses = []; // 获取所有需要扫描的目录:项目src目录 + 所有已安装Bundle的源码目录 $scanDirs = [ $container->getParameter('kernel.project_dir') . '/src', ]; foreach ($container->getParameter('kernel.bundles_metadata') as $bundleMeta) { $scanDirs[] = $bundleMeta['path']; } // 查找目录下所有PHP类文件 $finder = (new Finder())->files()->in($scanDirs)->name('*.php'); foreach ($finder as $file) { $className = $this->convertPathToClassName($file->getRealPath(), $scanDirs); if (!class_exists($className)) { continue; } // 读取类注解 $reflectionClass = new \ReflectionClass($className); $annotation = $annotationReader->getClassAnnotation($reflectionClass, MyAnnotation::class); // PHP8+ 原生属性写法(不需要doctrine/annotations) // $annotation = $reflectionClass->getAttributes(MyAnnotation::class)[0] ?? null; if ($annotation) { $annotatedClasses[] = $className; } } // 将扫描结果存入容器参数,后续可直接注入使用 $container->setParameter('app.my_annotated_classes', $annotatedClasses); } /** * 将文件路径转换为符合PSR-4规范的类名 */ private function convertPathToClassName(string $filePath, array $scanDirs): string { foreach ($scanDirs as $dir) { $dir = rtrim($dir, '/'); if (str_starts_with($filePath, $dir)) { $relativePath = substr($filePath, strlen($dir) + 1, -4); return str_replace('/', '\\', ucfirst($relativePath)); } } return ''; } }
3. 注册CompilerPass
在Kernel中注册刚才写的扫描Pass:
// src/Kernel.php use App\DependencyInjection\Compiler\MyAnnotationScanPass; class Kernel extends BaseKernel { use MicroKernelTrait; protected function build(ContainerBuilder $container) { $container->addCompilerPass(new MyAnnotationScanPass()); } }
4. 使用扫描结果
清空一次缓存让容器重新编译后,就可以在任意服务/控制器中注入扫描结果使用:
// 控制器中使用示例 use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\DependencyInjection\Attribute\Autowire; class TestController extends AbstractController { public function index(#[Autowire('%app.my_annotated_classes%')] array $annotatedClasses) { // $annotatedClasses 就是所有使用了@MyAnnotation注解的类名数组 dd($annotatedClasses); } }
常见优化说明
- 可以按需缩小扫描目录范围,比如只扫描
Controller、Entity等特定目录,不需要全量扫描所有源码,速度更快 - 如果自定义注解只作用于方法/属性,修改读取注解的逻辑为
getMethodAnnotation/getPropertyAnnotation即可 - 扫描逻辑和Doctrine查找
@Entity注解的逻辑完全一致,Doctrine同样是基于容器编译+目录扫描+结果缓存的机制实现全局注解发现
内容的提问来源于stack exchange,提问作者FLichter
相关产品推荐
相关产品推荐

