@@ -130,17 +130,121 @@ public static function traversableProvider(): iterable
130130 ];
131131 }
132132
133- public function testNotTraversable (): void
133+ #[DataProvider('nullSafeProvider ' )]
134+ public function testNullSafeCollectionPath (object $ object , string $ propertyPath , mixed $ expectedResult ): void
134135 {
136+ self ::assertTrue ($ this ->purgatoryPropertyAccessor ->isReadable ($ object , $ propertyPath ));
137+ self ::assertSame (
138+ expected: $ expectedResult ,
139+ actual: $ this ->purgatoryPropertyAccessor ->getValue ($ object , $ propertyPath ),
140+ );
141+ }
142+
143+ public static function nullSafeProvider (): iterable
144+ {
145+ yield 'null-safe parent short-circuits to null ' => [
146+ 'object ' => new Foo (
147+ id: 100 ,
148+ children: new ArrayCollection ([]),
149+ linked: null ,
150+ ),
151+ 'propertyPath ' => 'linked?.children[*].id ' ,
152+ 'expectedResult ' => null ,
153+ ];
154+
155+ yield 'null-safe parent present with populated collection yields values ' => [
156+ 'object ' => new Foo (
157+ id: 100 ,
158+ children: new ArrayCollection ([]),
159+ linked: new Foo (
160+ id: 1 ,
161+ children: new ArrayCollection ([
162+ new Foo (id: 10 , children: new ArrayCollection ([])),
163+ new Foo (id: 11 , children: new ArrayCollection ([])),
164+ ]),
165+ ),
166+ ),
167+ 'propertyPath ' => 'linked?.children[*].id ' ,
168+ 'expectedResult ' => [10 , 11 ],
169+ ];
170+
171+ yield 'null-safe collection element resolving to null short-circuits to null ' => [
172+ 'object ' => new Foo (
173+ id: 100 ,
174+ children: new ArrayCollection ([]),
175+ nullableChildren: null ,
176+ ),
177+ 'propertyPath ' => 'nullableChildren?[*].id ' ,
178+ 'expectedResult ' => null ,
179+ ];
180+
181+ yield 'nested null-safe short-circuit contributes a null entry, consistent with a scalar leaf ' => [
182+ 'object ' => new Foo (
183+ id: 0 ,
184+ children: new ArrayCollection ([
185+ new Foo (id: 1 , children: new ArrayCollection ([]), linked: null ),
186+ new Foo (
187+ id: 2 ,
188+ children: new ArrayCollection ([]),
189+ linked: new Foo (
190+ id: 20 ,
191+ children: new ArrayCollection ([
192+ new Foo (id: 100 , children: new ArrayCollection ([])),
193+ new Foo (id: 101 , children: new ArrayCollection ([])),
194+ ]),
195+ ),
196+ ),
197+ ]),
198+ ),
199+ 'propertyPath ' => 'children[*].linked?.children[*].id ' ,
200+ 'expectedResult ' => [null , 100 , 101 ],
201+ ];
202+ }
203+
204+ #[DataProvider('notTraversableProvider ' )]
205+ public function testNotTraversableThrows (object $ object , string $ propertyPath , string $ expectedMessage ): void
206+ {
207+ self ::assertFalse ($ this ->purgatoryPropertyAccessor ->isReadable ($ object , $ propertyPath ));
208+
135209 $ this ->expectException (ValueNotIterableException::class);
136- $ this ->expectExceptionMessage (' Expected an iterable, "int" given at property path "id[*]". ' );
210+ $ this ->expectExceptionMessage ($ expectedMessage );
137211
138- $ this ->purgatoryPropertyAccessor ->getValue (
139- objectOrArray: new Foo (
212+ $ this ->purgatoryPropertyAccessor ->getValue ($ object , $ propertyPath );
213+ }
214+
215+ public static function notTraversableProvider (): iterable
216+ {
217+ yield 'scalar value is not iterable ' => [
218+ 'object ' => new Foo (
140219 id: 1 ,
141220 children: new ArrayCollection ([]),
142221 ),
143- propertyPath: 'id[*].id ' ,
144- );
222+ 'propertyPath ' => 'id[*].id ' ,
223+ 'expectedMessage ' => 'Expected an iterable, "int" given at property path "id[*]". ' ,
224+ ];
225+
226+ yield 'non-null-safe collection resolving to null ' => [
227+ 'object ' => new Foo (
228+ id: 100 ,
229+ children: new ArrayCollection ([]),
230+ nullableChildren: null ,
231+ ),
232+ 'propertyPath ' => 'nullableChildren[*].id ' ,
233+ 'expectedMessage ' => 'Expected an iterable, "null" given at property path "nullableChildren[*]". ' ,
234+ ];
235+
236+ yield 'null-safe parent present but non-null-safe collection resolving to null ' => [
237+ 'object ' => new Foo (
238+ id: 100 ,
239+ children: new ArrayCollection ([]),
240+ linked: new Foo (
241+ id: 1 ,
242+ children: new ArrayCollection ([]),
243+ nullableChildren: null ,
244+ ),
245+ ),
246+ 'propertyPath ' => 'linked?.nullableChildren[*].id ' ,
247+ 'expectedMessage ' => 'Expected an iterable, "null" given at property path "linked?.nullableChildren[*]". ' ,
248+ ];
145249 }
146250}
0 commit comments