99use Doctrine \Common \Annotations \AnnotationRegistry ;
1010use Doctrine \Common \Annotations \CachedReader ;
1111use Doctrine \Common \Cache \ApcuCache ;
12+ use function error_log ;
13+ use Mouf \Composer \ClassNameMapper ;
14+ use Psr \SimpleCache \CacheInterface ;
15+ use Symfony \Component \Cache \Simple \ApcuCache as SymfonyApcuCache ;
16+ use Symfony \Component \Cache \Simple \PhpFilesCache as SymfonyPhpFilesCache ;
1217use function function_exists ;
1318use GraphQL \Type \Definition \InputObjectType ;
1419use GraphQL \Type \Definition \ObjectType ;
1520use Psr \Container \ContainerInterface ;
1621use ReflectionClass ;
22+ use ReflectionMethod ;
1723use function str_replace ;
1824use function strpos ;
1925use Symfony \Component \DependencyInjection \Compiler \CompilerPassInterface ;
2026use Symfony \Component \DependencyInjection \ContainerBuilder ;
2127use Symfony \Component \DependencyInjection \Definition ;
2228use Symfony \Component \DependencyInjection \Reference ;
29+ use TheCodingMachine \CacheUtils \ClassBoundCache ;
30+ use TheCodingMachine \CacheUtils \ClassBoundCacheContract ;
31+ use TheCodingMachine \CacheUtils \ClassBoundCacheContractInterface ;
32+ use TheCodingMachine \CacheUtils \ClassBoundMemoryAdapter ;
33+ use TheCodingMachine \CacheUtils \FileBoundCache ;
34+ use TheCodingMachine \ClassExplorer \Glob \GlobClassExplorer ;
2335use TheCodingMachine \GraphQLite \AnnotationReader ;
36+ use TheCodingMachine \GraphQLite \Annotations \AbstractRequest ;
37+ use TheCodingMachine \GraphQLite \Annotations \Field ;
2438use TheCodingMachine \GraphQLite \Annotations \Mutation ;
2539use TheCodingMachine \GraphQLite \Annotations \Query ;
2640use TheCodingMachine \Graphqlite \Bundle \QueryProviders \ControllerQueryProvider ;
@@ -68,6 +82,11 @@ public function process(ContainerBuilder $container)
6882 $ globTtl = 2 ;
6983 }
7084
85+ /**
86+ * @var array<string, array<int, string>>
87+ */
88+ $ classToServicesMap = [];
89+
7190 foreach ($ container ->getDefinitions () as $ id => $ definition ) {
7291 if ($ definition ->isAbstract () || $ definition ->getClass () === null ) {
7392 continue ;
@@ -90,18 +109,29 @@ public function process(ContainerBuilder $container)
90109 }
91110 if ($ typeAnnotation !== null || $ this ->getAnnotationReader ()->getExtendTypeAnnotation ($ reflectionClass ) !== null ) {
92111 $ definition ->setPublic (true );
93- } else {
94- foreach ($ reflectionClass ->getMethods () as $ method ) {
95- $ factory = $ reader ->getFactoryAnnotation ($ method );
96- if ($ factory !== null ) {
97- $ definition ->setPublic (true );
98- }
112+ }
113+ foreach ($ reflectionClass ->getMethods () as $ method ) {
114+ $ factory = $ reader ->getFactoryAnnotation ($ method );
115+ if ($ factory !== null ) {
116+ $ definition ->setPublic (true );
99117 }
100118 }
101119 }
102120 }
103121 }
104122
123+ foreach ($ controllersNamespaces as $ controllersNamespace ) {
124+ foreach ($ this ->getClassList ($ controllersNamespace ) as $ className => $ refClass ) {
125+ $ this ->makePublicInjectedServices ($ refClass , $ reader , $ container );
126+ }
127+ }
128+
129+ foreach ($ typesNamespaces as $ typeNamespace ) {
130+ foreach ($ this ->getClassList ($ typeNamespace ) as $ className => $ refClass ) {
131+ $ this ->makePublicInjectedServices ($ refClass , $ reader , $ container );
132+ }
133+ }
134+
105135 foreach ($ container ->findTaggedServiceIds ('graphql.annotated.controller ' ) as $ id => $ tag ) {
106136 $ definition = $ container ->findDefinition ($ id );
107137 $ class = $ definition ->getClass ();
@@ -185,6 +215,45 @@ public function process(ContainerBuilder $container)
185215 }
186216 }
187217
218+ private function makePublicInjectedServices (ReflectionClass $ refClass , AnnotationReader $ reader , ContainerBuilder $ container ): void
219+ {
220+ $ services = $ this ->getCodeCache ()->get ($ refClass , function () use ($ refClass , $ reader , $ container ) {
221+ $ services = [];
222+ foreach ($ refClass ->getMethods () as $ method ) {
223+ $ field = $ reader ->getRequestAnnotation ($ method , AbstractRequest::class);
224+ if ($ field !== null ) {
225+ $ services += $ this ->getListOfInjectedServices ($ method , $ container );
226+ }
227+ }
228+ return $ services ;
229+ });
230+
231+ foreach ($ services as $ service ) {
232+ $ container ->getDefinition ($ service )->setPublic (true );
233+ }
234+
235+ }
236+
237+ /**
238+ * @param ReflectionMethod $method
239+ * @param ContainerBuilder $container
240+ * @return array<string, string> key = value = service name
241+ */
242+ private function getListOfInjectedServices (ReflectionMethod $ method , ContainerBuilder $ container ): array
243+ {
244+ $ services = [];
245+ foreach ($ method ->getParameters () as $ parameter ) {
246+ $ type = $ parameter ->getType ();
247+ if ($ type !== null ) {
248+ $ fqcn = $ type ->getName ();
249+ if ($ container ->has ($ fqcn )) {
250+ $ services [$ fqcn ] = $ fqcn ;
251+ }
252+ }
253+ }
254+ return $ services ;
255+ }
256+
188257 /**
189258 * @param object $controller
190259 */
@@ -211,4 +280,59 @@ private function getAnnotationReader(): AnnotationReader
211280 }
212281 return $ this ->annotationReader ;
213282 }
283+
284+ /**
285+ * @var CacheInterface
286+ */
287+ private $ cache ;
288+
289+ private function getPsr16Cache (): CacheInterface
290+ {
291+ if ($ this ->cache === null ) {
292+ if (function_exists ('apcu_fetch ' )) {
293+ $ this ->cache = new SymfonyApcuCache ('graphqlite_bundle ' );
294+ } else {
295+ $ this ->cache = new SymfonyPhpFilesCache ('graphqlite_bundle ' );
296+ }
297+ }
298+ return $ this ->cache ;
299+ }
300+
301+ /**
302+ * @var ClassBoundCacheContractInterface
303+ */
304+ private $ codeCache ;
305+
306+ private function getCodeCache (): ClassBoundCacheContractInterface
307+ {
308+ if ($ this ->codeCache === null ) {
309+ $ this ->codeCache = new ClassBoundCacheContract (new ClassBoundMemoryAdapter (new ClassBoundCache (new FileBoundCache ($ this ->getPsr16Cache ()))));
310+ }
311+ return $ this ->codeCache ;
312+ }
313+
314+ /**
315+ * Returns the array of globbed classes.
316+ * Only instantiable classes are returned.
317+ *
318+ * @return array<string,ReflectionClass> Key: fully qualified class name
319+ */
320+ private function getClassList (string $ namespace , int $ globTtl = 2 , bool $ recursive = true ): array
321+ {
322+ $ explorer = new GlobClassExplorer ($ namespace , $ this ->getPsr16Cache (), $ globTtl , ClassNameMapper::createFromComposerFile (null , null , true ), $ recursive );
323+ $ allClasses = $ explorer ->getClasses ();
324+ foreach ($ allClasses as $ className ) {
325+ if (! class_exists ($ className )) {
326+ continue ;
327+ }
328+ $ refClass = new ReflectionClass ($ className );
329+ if (! $ refClass ->isInstantiable ()) {
330+ continue ;
331+ }
332+ $ classes [$ className ] = $ refClass ;
333+ }
334+
335+ return $ classes ;
336+ }
337+
214338}
0 commit comments