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 ;
@@ -61,13 +75,21 @@ public function process(ContainerBuilder $container)
6175 $ controllersNamespaces = $ container ->getParameter ('graphqlite.namespace.controllers ' );
6276 $ typesNamespaces = $ container ->getParameter ('graphqlite.namespace.types ' );
6377
78+ $ autowireByParameterName = $ container ->getParameter ('graphqlite.autowire.by_parameter_name ' );
79+ $ autowireByClassName = $ container ->getParameter ('graphqlite.autowire.by_class_name ' );
80+
6481 // 2 seconds of TTL in environment mode. Otherwise, let's cache forever!
6582 $ env = $ container ->getParameter ('kernel.environment ' );
6683 $ globTtl = null ;
6784 if ($ env === 'dev ' ) {
6885 $ globTtl = 2 ;
6986 }
7087
88+ /**
89+ * @var array<string, array<int, string>>
90+ */
91+ $ classToServicesMap = [];
92+
7193 foreach ($ container ->getDefinitions () as $ id => $ definition ) {
7294 if ($ definition ->isAbstract () || $ definition ->getClass () === null ) {
7395 continue ;
@@ -90,18 +112,31 @@ public function process(ContainerBuilder $container)
90112 }
91113 if ($ typeAnnotation !== null || $ this ->getAnnotationReader ()->getExtendTypeAnnotation ($ reflectionClass ) !== null ) {
92114 $ 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- }
115+ }
116+ foreach ($ reflectionClass ->getMethods () as $ method ) {
117+ $ factory = $ reader ->getFactoryAnnotation ($ method );
118+ if ($ factory !== null ) {
119+ $ definition ->setPublic (true );
99120 }
100121 }
101122 }
102123 }
103124 }
104125
126+ if ($ autowireByParameterName || $ autowireByClassName ) {
127+ foreach ($ controllersNamespaces as $ controllersNamespace ) {
128+ foreach ($ this ->getClassList ($ controllersNamespace ) as $ className => $ refClass ) {
129+ $ this ->makePublicInjectedServices ($ refClass , $ reader , $ container , $ autowireByClassName , $ autowireByParameterName );
130+ }
131+ }
132+
133+ foreach ($ typesNamespaces as $ typeNamespace ) {
134+ foreach ($ this ->getClassList ($ typeNamespace ) as $ className => $ refClass ) {
135+ $ this ->makePublicInjectedServices ($ refClass , $ reader , $ container , $ autowireByClassName , $ autowireByParameterName );
136+ }
137+ }
138+ }
139+
105140 foreach ($ container ->findTaggedServiceIds ('graphql.annotated.controller ' ) as $ id => $ tag ) {
106141 $ definition = $ container ->findDefinition ($ id );
107142 $ class = $ definition ->getClass ();
@@ -185,6 +220,53 @@ public function process(ContainerBuilder $container)
185220 }
186221 }
187222
223+ private function makePublicInjectedServices (ReflectionClass $ refClass , AnnotationReader $ reader , ContainerBuilder $ container , bool $ autowireByClassName , bool $ autowireByParameterName ): void
224+ {
225+ $ services = $ this ->getCodeCache ()->get ($ refClass , function () use ($ refClass , $ reader , $ container , $ autowireByClassName , $ autowireByParameterName ) {
226+ $ services = [];
227+ foreach ($ refClass ->getMethods () as $ method ) {
228+ $ field = $ reader ->getRequestAnnotation ($ method , AbstractRequest::class);
229+ if ($ field !== null ) {
230+ $ services += $ this ->getListOfInjectedServices ($ method , $ container , $ autowireByClassName , $ autowireByParameterName );
231+ }
232+ }
233+ return $ services ;
234+ });
235+
236+ foreach ($ services as $ service ) {
237+ $ container ->getDefinition ($ service )->setPublic (true );
238+ }
239+
240+ }
241+
242+ /**
243+ * @param ReflectionMethod $method
244+ * @param ContainerBuilder $container
245+ * @return array<string, string> key = value = service name
246+ */
247+ private function getListOfInjectedServices (ReflectionMethod $ method , ContainerBuilder $ container , bool $ autowireByClassName , bool $ autowireByParameterName ): array
248+ {
249+ $ services = [];
250+ foreach ($ method ->getParameters () as $ parameter ) {
251+ if ($ autowireByParameterName ) {
252+ $ parameterName = $ parameter ->getName ();
253+ if ($ container ->has ($ parameterName )) {
254+ $ services [$ parameterName ] = $ parameterName ;
255+ }
256+ }
257+ if ($ autowireByClassName ) {
258+ $ type = $ parameter ->getType ();
259+ if ($ type !== null ) {
260+ $ fqcn = $ type ->getName ();
261+ if ($ container ->has ($ fqcn )) {
262+ $ services [$ fqcn ] = $ fqcn ;
263+ }
264+ }
265+ }
266+ }
267+ return $ services ;
268+ }
269+
188270 /**
189271 * @param object $controller
190272 */
@@ -211,4 +293,60 @@ private function getAnnotationReader(): AnnotationReader
211293 }
212294 return $ this ->annotationReader ;
213295 }
296+
297+ /**
298+ * @var CacheInterface
299+ */
300+ private $ cache ;
301+
302+ private function getPsr16Cache (): CacheInterface
303+ {
304+ if ($ this ->cache === null ) {
305+ if (function_exists ('apcu_fetch ' )) {
306+ $ this ->cache = new SymfonyApcuCache ('graphqlite_bundle ' );
307+ } else {
308+ $ this ->cache = new SymfonyPhpFilesCache ('graphqlite_bundle ' );
309+ }
310+ }
311+ return $ this ->cache ;
312+ }
313+
314+ /**
315+ * @var ClassBoundCacheContractInterface
316+ */
317+ private $ codeCache ;
318+
319+ private function getCodeCache (): ClassBoundCacheContractInterface
320+ {
321+ if ($ this ->codeCache === null ) {
322+ $ this ->codeCache = new ClassBoundCacheContract (new ClassBoundMemoryAdapter (new ClassBoundCache (new FileBoundCache ($ this ->getPsr16Cache ()))));
323+ }
324+ return $ this ->codeCache ;
325+ }
326+
327+ /**
328+ * Returns the array of globbed classes.
329+ * Only instantiable classes are returned.
330+ *
331+ * @return array<string,ReflectionClass> Key: fully qualified class name
332+ */
333+ private function getClassList (string $ namespace , int $ globTtl = 2 , bool $ recursive = true ): array
334+ {
335+ $ explorer = new GlobClassExplorer ($ namespace , $ this ->getPsr16Cache (), $ globTtl , ClassNameMapper::createFromComposerFile (null , null , true ), $ recursive );
336+ $ allClasses = $ explorer ->getClasses ();
337+ $ classes = [];
338+ foreach ($ allClasses as $ className ) {
339+ if (! class_exists ($ className )) {
340+ continue ;
341+ }
342+ $ refClass = new ReflectionClass ($ className );
343+ if (! $ refClass ->isInstantiable ()) {
344+ continue ;
345+ }
346+ $ classes [$ className ] = $ refClass ;
347+ }
348+
349+ return $ classes ;
350+ }
351+
214352}
0 commit comments