1313namespace Twig \Node \Expression ;
1414
1515use Twig \Compiler ;
16+ use Twig \Environment ;
1617use Twig \Extension \SandboxExtension ;
1718use Twig \Template ;
1819use Twig \TypeHint \ArrayType ;
1920use Twig \TypeHint \ObjectType ;
21+ use Twig \TypeHint \TypeInterface ;
22+ use Twig \TypeHint \UnionType ;
2023
2124class GetAttrExpression extends AbstractExpression
2225{
@@ -41,63 +44,23 @@ public function compile(Compiler $compiler): void
4144 $ type = $ this ->getNode ('node ' )->getAttribute ('typeHint ' );
4245 }
4346
44- if ($ type instanceof ArrayType) {
45- $ compiler
46- ->raw ('(( ' )
47- ->subcompile ($ this ->getNode ('node ' ))
48- ->raw (')[ ' )
49- ->subcompile ($ this ->getNode ('attribute ' ))
50- ->raw ('] ?? null) ' )
51- ;
47+ if ($ type instanceof TypeInterface) {
48+ $ sourceCompiler = $ this ->createNodeSourceCompiler ();
49+ $ accessCompiler = $ this ->createAccessCompiler ($ type , $ env );
5250
53- return ;
54- } else if ($ type instanceof ObjectType && $ this ->getNode ('attribute ' ) instanceof ConstantExpression) {
55- $ attributeName = $ this ->getNode ('attribute ' )->getAttribute ('value ' );
56-
57- if ($ type ->getPropertyType ($ attributeName ) !== null ) {
58- $ compiler
59- ->raw ('(( ' )
60- ->subcompile ($ this ->getNode ('node ' ))
61- ->raw (')?-> ' )
62- ->raw ($ attributeName )
63- ->raw (') ' )
64- ;
65-
66- return ;
51+ if (true || $ accessCompiler ['condition ' ] === null ) {
52+ $ accessCompiler ['accessor ' ]($ compiler , $ sourceCompiler );
53+ } else {
54+ $ compiler ->raw ('( ' );
55+ $ accessCompiler ['condition ' ]($ compiler , $ sourceCompiler );
56+ $ compiler ->raw (' ? ' );
57+ $ accessCompiler ['accessor ' ]($ compiler , $ sourceCompiler );
58+ $ compiler ->raw (' : ' );
59+ $ this ->createGuessingAccessCompiler ($ env ->hasExtension (SandboxExtension::class))['accessor ' ]($ compiler , $ sourceCompiler );
60+ $ compiler ->raw (') ' );
6761 }
6862
69- /** Keep similar to @see \Twig\TypeHint\ObjectType::getAttributeType */
70- $ methodNames = [
71- $ attributeName ,
72- 'get ' . $ attributeName ,
73- 'is ' . $ attributeName ,
74- 'has ' . $ attributeName ,
75- ];
76-
77- foreach ($ methodNames as $ methodName ) {
78- if ($ type ->getMethodType ($ methodName ) !== null ) {
79- $ compiler
80- ->raw ('(( ' )
81- ->subcompile ($ this ->getNode ('node ' ))
82- ->raw (')?-> ' )
83- ->raw ($ methodName )
84- ->raw ('( ' )
85- ;
86-
87- if ($ this ->hasNode ('arguments ' ) && $ this ->getNode ('arguments ' ) instanceof ArrayExpression && $ this ->getNode ('arguments ' )->count () > 0 ) {
88- for ($ argIndex = 0 ; $ argIndex < $ this ->getNode ('arguments ' )->count (); $ argIndex += 2 ) {
89- if ($ argIndex > 0 ) {
90- $ compiler ->raw (', ' );
91- }
92-
93- $ compiler ->subcompile ($ this ->getNode ('arguments ' )->getNode ($ argIndex + 1 ));
94- }
95- }
96-
97- $ compiler ->raw (')) ' );
98- return ;
99- }
100- }
63+ return ;
10164 }
10265 }
10366
@@ -126,31 +89,264 @@ public function compile(Compiler $compiler): void
12689 return ;
12790 }
12891
129- $ compiler ->raw ('twig_get_attribute($this->env, $this->source, ' );
92+ $ this ->createGuessingAccessCompiler ($ env ->hasExtension (SandboxExtension::class))['accessor ' ]($ compiler , $ this ->createNodeSourceCompiler ());
93+ }
94+
95+ /**
96+ * @return array{
97+ * condition: \Closure(Compiler, \Closure(Compiler): void): void|null,
98+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
99+ * }
100+ */
101+ private function createAccessCompiler (TypeInterface $ type , Environment $ env ): array
102+ {
103+ if ($ type instanceof UnionType) {
104+ return $ this ->createUnionAccessCompiler ($ type , $ env );
105+ }
106+
107+ if ($ type instanceof ArrayType) {
108+ return $ this ->createArrayAccessCompiler ();
109+ }
110+
111+ if ($ type instanceof ObjectType && $ this ->getNode ('attribute ' ) instanceof ConstantExpression) {
112+ $ attributeName = $ this ->getNode ('attribute ' )->getAttribute ('value ' );
113+
114+ if ($ type ->getPropertyType ($ attributeName ) !== null ) {
115+ return $ this ->createObjectPropertyAccessCompiler ($ type , $ attributeName );
116+ }
117+
118+ /** Keep similar to @see \Twig\TypeHint\ObjectType::getAttributeType */
119+ $ methodNames = [
120+ $ attributeName ,
121+ 'get ' . $ attributeName ,
122+ 'is ' . $ attributeName ,
123+ 'has ' . $ attributeName ,
124+ ];
125+
126+ foreach ($ methodNames as $ methodName ) {
127+ if ($ type ->getMethodType ($ methodName ) === null ) {
128+ continue ;
129+ }
130130
131- if ( $ this ->getAttribute ( ' ignore_strict_check ' )) {
132- $ this -> getNode ( ' node ' )-> setAttribute ( ' ignore_strict_check ' , true );
131+ return $ this ->createObjectMethodAccessCompiler ( $ type , $ methodName );
132+ }
133133 }
134134
135- $ compiler
136- ->subcompile ($ this ->getNode ('node ' ))
137- ->raw (', ' )
138- ->subcompile ($ this ->getNode ('attribute ' ))
139- ;
135+ return $ this ->createGuessingAccessCompiler ($ env ->hasExtension (SandboxExtension::class));
136+ }
137+
138+ /**
139+ * @return array{
140+ * condition: null,
141+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
142+ * }
143+ */
144+ private function createGuessingAccessCompiler (bool $ isSandboxed ): array
145+ {
146+ return [
147+ 'condition ' => null ,
148+ 'accessor ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ isSandboxed ): void {
149+ $ compiler ->raw ('twig_get_attribute($this->env, $this->source, ' );
150+
151+ if ($ this ->getAttribute ('ignore_strict_check ' )) {
152+ $ this ->getNode ('node ' )->setAttribute ('ignore_strict_check ' , true );
153+ }
154+
155+ $ sourceCompiler ($ compiler );
156+
157+ $ compiler
158+ ->raw (', ' )
159+ ->subcompile ($ this ->getNode ('attribute ' ))
160+ ;
161+
162+ if ($ this ->hasNode ('arguments ' )) {
163+ $ compiler ->raw (', ' )->subcompile ($ this ->getNode ('arguments ' ));
164+ } else {
165+ $ compiler ->raw (', [] ' );
166+ }
167+
168+ $ compiler ->raw (', ' )
169+ ->repr ($ this ->getAttribute ('type ' ))
170+ ->raw (', ' )->repr ($ this ->getAttribute ('is_defined_test ' ))
171+ ->raw (', ' )->repr ($ this ->getAttribute ('ignore_strict_check ' ))
172+ ->raw (', ' )->repr ($ isSandboxed )
173+ ->raw (', ' )->repr ($ this ->getNode ('node ' )->getTemplateLine ())
174+ ->raw (') ' )
175+ ;
176+ },
177+ ];
178+ }
179+
180+ /**
181+ * @return array{
182+ * condition: \Closure(Compiler, \Closure(Compiler): void): void,
183+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
184+ * }
185+ */
186+ private function createObjectMethodAccessCompiler (ObjectType $ type , string $ attributeName ): array
187+ {
188+ return [
189+ 'condition ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ type ): void {
190+ $ sourceCompiler ($ compiler );
191+ $ compiler ->raw (' instanceof \\' )->raw ($ type ->getType ());
192+ },
193+ 'accessor ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ attributeName ): void {
194+ $ compiler ->raw ('( ' );
195+
196+ $ sourceCompiler ($ compiler );
140197
141- if ($ this ->hasNode ('arguments ' )) {
142- $ compiler ->raw (', ' )->subcompile ($ this ->getNode ('arguments ' ));
143- } else {
144- $ compiler ->raw (', [] ' );
198+ $ compiler ->raw ('?-> ' )->raw ($ attributeName )->raw ('( ' );
199+
200+ if ($ this ->hasNode ('arguments ' ) && $ this ->getNode ('arguments ' ) instanceof ArrayExpression && $ this ->getNode ('arguments ' )->count () > 0 ) {
201+ for ($ argIndex = 0 ; $ argIndex < $ this ->getNode ('arguments ' )->count (); $ argIndex += 2 ) {
202+ if ($ argIndex > 0 ) {
203+ $ compiler ->raw (', ' );
204+ }
205+
206+ $ compiler ->subcompile ($ this ->getNode ('arguments ' )->getNode ($ argIndex + 1 ));
207+ }
208+ }
209+
210+ $ compiler ->raw (')) ' );
211+ },
212+ ];
213+ }
214+
215+ /**
216+ * @return array{
217+ * condition: \Closure(Compiler, \Closure(Compiler): void): void,
218+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
219+ * }
220+ */
221+ private function createObjectPropertyAccessCompiler (ObjectType $ type , string $ attributeName ): array
222+ {
223+ return [
224+ 'condition ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ type ): void {
225+ $ sourceCompiler ($ compiler );
226+ $ compiler ->raw (' instanceof \\' )->raw ($ type ->getType ());
227+ },
228+ 'accessor ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ attributeName ): void {
229+ $ sourceCompiler ($ compiler );
230+ $ compiler
231+ ->raw ('?-> ' )
232+ ->raw ($ attributeName );
233+ },
234+ ];
235+ }
236+
237+ /**
238+ * @return array{
239+ * condition: null,
240+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
241+ * }
242+ */
243+ private function createUnionAccessCompiler (UnionType $ type , Environment $ env ): array
244+ {
245+ $ accessors = [];
246+
247+ foreach ($ type ->getTypes () as $ innerType ) {
248+ $ accessors [] = $ this ->createAccessCompiler ($ innerType , $ env );
145249 }
146250
147- $ compiler ->raw (', ' )
148- ->repr ($ this ->getAttribute ('type ' ))
149- ->raw (', ' )->repr ($ this ->getAttribute ('is_defined_test ' ))
150- ->raw (', ' )->repr ($ this ->getAttribute ('ignore_strict_check ' ))
151- ->raw (', ' )->repr ($ env ->hasExtension (SandboxExtension::class))
152- ->raw (', ' )->repr ($ this ->getNode ('node ' )->getTemplateLine ())
153- ->raw (') ' )
154- ;
251+ return [
252+ 'condition ' => null ,
253+ 'accessor ' => function (Compiler $ compiler , \Closure $ sourceCompiler ) use ($ accessors ) {
254+ $ compiler ->raw ('match ([ ' );
255+ $ compiler ->indent ();
256+ $ sourceCompiler ($ compiler );
257+ $ compiler ->raw (", true][1]) { \n" );
258+
259+ foreach ($ accessors as $ accessor ) {
260+ if ($ accessor ['condition ' ] === null ) {
261+ $ compiler ->raw ('default ' );
262+ } else {
263+ $ accessor ['condition ' ]($ compiler , $ sourceCompiler );
264+ }
265+
266+ $ compiler ->raw (' => ' );
267+ $ accessor ['accessor ' ]($ compiler , $ sourceCompiler );
268+ $ compiler ->raw ("; \n" );
269+ }
270+
271+ $ compiler ->outdent ();
272+ $ compiler ->raw ('} ' );
273+ }
274+ ];
275+ }
276+
277+ /**
278+ * @return array{
279+ * condition: \Closure(Compiler, \Closure(Compiler): void): void,
280+ * accessor: \Closure(Compiler, \Closure(Compiler): void): void
281+ * }
282+ */
283+ private function createArrayAccessCompiler (): array
284+ {
285+ return [
286+ 'condition ' => function (Compiler $ compiler , \Closure $ sourceCompiler ): void {
287+ $ compiler ->raw ('(\is_array( ' );
288+ $ sourceCompiler ($ compiler );
289+ $ compiler ->raw (') || ' );
290+ $ sourceCompiler ($ compiler );
291+ $ compiler ->raw (' instanceof \\ArrayAccess) ' );
292+ },
293+ 'accessor ' => function (Compiler $ compiler , \Closure $ sourceCompiler ): void {
294+ $ compiler ->raw ('( ' );
295+ $ sourceCompiler ($ compiler );
296+ $ compiler
297+ ->raw ('[ ' )
298+ ->subcompile ($ this ->getNode ('attribute ' ))
299+ ->raw ('] ?? null) ' );
300+ },
301+ ];
302+ }
303+
304+ /**
305+ * @return \Closure(Compiler): void
306+ */
307+ private function createAutoInlineSourceCompiler (): \Closure
308+ {
309+ $ varName = null ;
310+ $ sourceCompiler = $ this ->createNodeSourceCompiler ();
311+
312+ return function (Compiler $ compiler ) use (&$ varName , &$ sourceCompiler ): void {
313+ if ($ varName === null ) {
314+ $ varName = $ compiler ->getVarName ();
315+ $ newSourceCompiler = $ this ->createVarNameSourceCompiler ($ varName );
316+
317+ $ compiler ->raw ('( ' );
318+ $ newSourceCompiler ($ compiler );
319+ $ compiler ->raw (' = ' );
320+ $ sourceCompiler ($ compiler );
321+ $ compiler ->raw (') ' );
322+
323+ $ sourceCompiler = $ newSourceCompiler ;
324+ } else {
325+ $ sourceCompiler ($ compiler );
326+ }
327+ };
328+ }
329+
330+ /**
331+ * @return \Closure(Compiler): void
332+ */
333+ private function createNodeSourceCompiler (): \Closure
334+ {
335+ return function (Compiler $ compiler ): void {
336+ $ compiler ->subcompile ($ this ->getNode ('node ' ));
337+ };
338+ }
339+
340+ /**
341+ * @return \Closure(Compiler): void
342+ */
343+ private function createVarNameSourceCompiler (string $ varName ): \Closure
344+ {
345+ return function (Compiler $ compiler ) use ($ varName ): void {
346+ $ compiler
347+ ->raw ('$ ' )
348+ ->raw ($ varName )
349+ ;
350+ };
155351 }
156352}
0 commit comments