1010
1111namespace Respect \FluentGen \Fluent ;
1212
13+ use InvalidArgumentException ;
1314use Nette \PhpGenerator \PhpNamespace ;
1415use ReflectionClass ;
1516use Respect \Fluent \Attributes \Composable ;
17+ use Respect \Fluent \Attributes \ComposableParameter ;
1618use Respect \FluentGen \CodeGenerator ;
1719use Respect \FluentGen \Config ;
1820use Respect \FluentGen \FileRenderer ;
2224use function array_keys ;
2325use function ctype_upper ;
2426use function ksort ;
25- use function lcfirst ;
27+ use function sprintf ;
2628use function str_starts_with ;
2729use function strlen ;
2830use function uksort ;
@@ -33,6 +35,7 @@ public function __construct(
3335 private Config $ config ,
3436 private NamespaceScanner $ scanner ,
3537 private string $ outputClassName ,
38+ private MethodBuilder $ methodBuilder = new MethodBuilder (),
3639 private FileRenderer $ renderer = new FileRenderer (),
3740 ) {
3841 }
@@ -45,9 +48,10 @@ public function generate(): array
4548 $ this ->config ->sourceNamespace ,
4649 );
4750 $ prefixes = $ this ->discoverPrefixes ($ nodes );
51+ $ fqcnToPrefixMap = $ this ->buildFqcnMap ($ nodes );
4852 $ composable = $ this ->buildComposable ($ nodes , $ prefixes );
4953 $ composableWithArgument = $ this ->buildComposableWithArgument ($ prefixes );
50- $ forbidden = $ this ->buildForbidden ($ nodes , $ prefixes );
54+ $ forbidden = $ this ->buildForbidden ($ nodes , $ prefixes, $ fqcnToPrefixMap );
5155
5256 $ namespace = new PhpNamespace ($ this ->config ->outputNamespace );
5357 $ class = $ namespace ->addClass ($ this ->outputClassName );
@@ -78,13 +82,25 @@ private function discoverPrefixes(array $nodes): array
7882 }
7983
8084 $ attr = $ attributes [0 ]->newInstance ();
81- if ($ attr ->prefix === '' ) {
85+ if ($ attr ->prefix === null ) {
8286 continue ;
8387 }
8488
85- $ prefixes [$ attr ->prefix ] = [
86- 'prefix ' => $ attr ->prefix ,
87- 'prefixParameter ' => $ attr ->prefixParameter ,
89+ $ hasPrefixParameter = false ;
90+ $ constructor = $ reflection ->getConstructor ();
91+ if ($ constructor !== null ) {
92+ foreach ($ constructor ->getParameters () as $ param ) {
93+ if ($ param ->getAttributes (ComposableParameter::class) !== []) {
94+ $ hasPrefixParameter = true ;
95+ break ;
96+ }
97+ }
98+ }
99+
100+ $ prefix = $ this ->methodBuilder ->classToPrefix ($ reflection ->getShortName ());
101+ $ prefixes [$ prefix ] = [
102+ 'prefix ' => $ prefix ,
103+ 'prefixParameter ' => $ hasPrefixParameter ,
88104 ];
89105 }
90106
@@ -107,7 +123,7 @@ private function buildComposable(array $nodes, array $prefixes): array
107123 $ composable [$ prefix ] = true ;
108124
109125 foreach (array_keys ($ nodes ) as $ name ) {
110- $ lcName = lcfirst ($ name );
126+ $ lcName = $ this -> methodBuilder -> classToPrefix ($ name );
111127 if ($ lcName === $ prefix ) {
112128 continue ;
113129 }
@@ -129,13 +145,30 @@ private function buildComposable(array $nodes, array $prefixes): array
129145 return $ composable ;
130146 }
131147
148+ /**
149+ * @param array<string, ReflectionClass<object>> $nodes
150+ *
151+ * @return array<class-string, string>
152+ */
153+ private function buildFqcnMap (array $ nodes ): array
154+ {
155+ $ map = [];
156+
157+ foreach ($ nodes as $ reflection ) {
158+ $ map [$ reflection ->getName ()] = $ this ->methodBuilder ->classToPrefix ($ reflection ->getShortName ());
159+ }
160+
161+ return $ map ;
162+ }
163+
132164 /**
133165 * @param array<string, ReflectionClass<object>> $nodes
134166 * @param array<string, array{prefix: string, prefixParameter: bool}> $prefixes
167+ * @param array<class-string, string> $fqcnToPrefixMap
135168 *
136169 * @return array<string, array<string, true>>
137170 */
138- private function buildForbidden (array $ nodes , array $ prefixes ): array
171+ private function buildForbidden (array $ nodes , array $ prefixes, array $ fqcnToPrefixMap ): array
139172 {
140173 $ forbidden = [];
141174 $ prefixNames = array_keys ($ prefixes );
@@ -148,7 +181,9 @@ private function buildForbidden(array $nodes, array $prefixes): array
148181
149182 $ attr = $ attributes [0 ]->newInstance ();
150183
151- $ blockedPrefixes = $ attr ->optIn ? array_diff ($ prefixNames , $ attr ->with ) : $ attr ->without ;
184+ $ resolvedWith = $ this ->resolveClassStrings ($ attr ->with , $ fqcnToPrefixMap , $ name );
185+ $ resolvedWithout = $ this ->resolveClassStrings ($ attr ->without , $ fqcnToPrefixMap , $ name );
186+ $ blockedPrefixes = $ attr ->optIn ? array_diff ($ prefixNames , $ resolvedWith ) : $ resolvedWithout ;
152187
153188 if ($ blockedPrefixes === []) {
154189 continue ;
@@ -189,4 +224,27 @@ private function buildComposableWithArgument(array $prefixes): array
189224
190225 return $ composableWithArgument ;
191226 }
227+
228+ /**
229+ * @param list<class-string> $classStrings
230+ * @param array<class-string, string> $fqcnToPrefixMap
231+ *
232+ * @return list<string>
233+ */
234+ private function resolveClassStrings (array $ classStrings , array $ fqcnToPrefixMap , string $ context ): array
235+ {
236+ $ resolved = [];
237+
238+ foreach ($ classStrings as $ fqcn ) {
239+ if (!isset ($ fqcnToPrefixMap [$ fqcn ])) {
240+ throw new InvalidArgumentException (
241+ sprintf ('Composable on %s references unknown class %s ' , $ context , $ fqcn ),
242+ );
243+ }
244+
245+ $ resolved [] = $ fqcnToPrefixMap [$ fqcn ];
246+ }
247+
248+ return $ resolved ;
249+ }
192250}
0 commit comments