66
77use ArrayAccess ;
88use Closure ;
9+ use Doctrine \Deprecations \Deprecation ;
910use RuntimeException ;
1011
1112use function array_all ;
3132 */
3233class ClosureExpressionVisitor extends ExpressionVisitor
3334{
35+ public function __construct (
36+ private readonly bool $ accessRawFieldValues = false ,
37+ ) {
38+ }
39+
3440 /**
3541 * Accesses the field of a given object. This field has to be public
3642 * directly or indirectly (through an accessor get*, is*, or a magic
@@ -40,19 +46,30 @@ class ClosureExpressionVisitor extends ExpressionVisitor
4046 *
4147 * @return mixed
4248 */
43- public static function getObjectFieldValue (object |array $ object , string $ field )
49+ public static function getObjectFieldValue (object |array $ object , string $ field, bool $ accessRawFieldValues = false )
4450 {
4551 if (str_contains ($ field , '. ' )) {
4652 [$ field , $ subField ] = explode ('. ' , $ field , 2 );
47- $ object = self ::getObjectFieldValue ($ object , $ field );
53+ $ object = self ::getObjectFieldValue ($ object , $ field, $ accessRawFieldValues );
4854
49- return self ::getObjectFieldValue ($ object , $ subField );
55+ return self ::getObjectFieldValue ($ object , $ subField, $ accessRawFieldValues );
5056 }
5157
5258 if (is_array ($ object )) {
5359 return $ object [$ field ];
5460 }
5561
62+ if ($ accessRawFieldValues ) {
63+ return self ::getNearestFieldValue ($ object , $ field );
64+ }
65+
66+ Deprecation::trigger (
67+ 'doctrine/collections ' ,
68+ 'https://github.com/doctrine/collections/pull/472 ' ,
69+ 'Not enabling raw field value access for %s is deprecated. Raw field access will be the only supported method in 3.0 ' ,
70+ __METHOD__ ,
71+ );
72+
5673 $ accessors = ['get ' , 'is ' , '' ];
5774
5875 foreach ($ accessors as $ accessor ) {
@@ -96,21 +113,42 @@ public static function getObjectFieldValue(object|array $object, string $field)
96113 return $ object ->$ field ;
97114 }
98115
116+ private static function getNearestFieldValue (object $ object , string $ field ): mixed
117+ {
118+ $ reflectionClass = new \ReflectionClass ($ object );
119+
120+ while ($ reflectionClass && !$ reflectionClass ->hasProperty ($ field )) {
121+ $ reflectionClass = $ reflectionClass ->getParentClass ();
122+ }
123+
124+ if (false === $ reflectionClass ) {
125+ throw new RuntimeException (sprintf ('Field "%s" does not exist in class %s ' , $ field , get_class ($ object )));
126+ }
127+
128+ return $ reflectionClass ->getProperty ($ field )->getValue ($ object );
129+ }
130+
99131 /**
100132 * Helper for sorting arrays of objects based on multiple fields + orientations.
101133 *
102134 * @return Closure
103135 */
104- public static function sortByField (string $ name , int $ orientation = 1 , Closure |null $ next = null )
136+ public static function sortByField (string $ name , int $ orientation = 1 , Closure |null $ next = null , bool $ accessRawFieldValues = false )
105137 {
138+ Deprecation::trigger (
139+ 'doctrine/collections ' ,
140+ 'https://github.com/doctrine/collections/pull/472 ' ,
141+ 'Not enabling raw field value access for %s is deprecated. Raw field access will be the only supported method in 3.0 ' ,
142+ __METHOD__ ,
143+ );
144+
106145 if (! $ next ) {
107146 $ next = static fn (): int => 0 ;
108147 }
109148
110- return static function ($ a , $ b ) use ($ name , $ next , $ orientation ): int {
111- $ aValue = ClosureExpressionVisitor::getObjectFieldValue ($ a , $ name );
112-
113- $ bValue = ClosureExpressionVisitor::getObjectFieldValue ($ b , $ name );
149+ return static function ($ a , $ b ) use ($ name , $ next , $ orientation , $ accessRawFieldValues ): int {
150+ $ aValue = ClosureExpressionVisitor::getObjectFieldValue ($ a , $ name , $ accessRawFieldValues );
151+ $ bValue = ClosureExpressionVisitor::getObjectFieldValue ($ b , $ name , $ accessRawFieldValues );
114152
115153 if ($ aValue === $ bValue ) {
116154 return $ next ($ a , $ b );
@@ -129,34 +167,34 @@ public function walkComparison(Comparison $comparison)
129167 $ value = $ comparison ->getValue ()->getValue ();
130168
131169 return match ($ comparison ->getOperator ()) {
132- Comparison::EQ => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) === $ value ,
133- Comparison::NEQ => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) !== $ value ,
134- Comparison::LT => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) < $ value ,
135- Comparison::LTE => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) <= $ value ,
136- Comparison::GT => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) > $ value ,
137- Comparison::GTE => static fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field ) >= $ value ,
138- Comparison::IN => static function ($ object ) use ($ field , $ value ): bool {
139- $ fieldValue = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field );
170+ Comparison::EQ => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) === $ value ,
171+ Comparison::NEQ => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) !== $ value ,
172+ Comparison::LT => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) < $ value ,
173+ Comparison::LTE => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) <= $ value ,
174+ Comparison::GT => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) > $ value ,
175+ Comparison::GTE => fn ($ object ): bool => self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ) >= $ value ,
176+ Comparison::IN => function ($ object ) use ($ field , $ value ): bool {
177+ $ fieldValue = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues );
140178
141179 return in_array ($ fieldValue , $ value , is_scalar ($ fieldValue ));
142180 },
143- Comparison::NIN => static function ($ object ) use ($ field , $ value ): bool {
144- $ fieldValue = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field );
181+ Comparison::NIN => function ($ object ) use ($ field , $ value ): bool {
182+ $ fieldValue = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues );
145183
146184 return ! in_array ($ fieldValue , $ value , is_scalar ($ fieldValue ));
147185 },
148- Comparison::CONTAINS => static fn ($ object ): bool => str_contains ((string ) self ::getObjectFieldValue ($ object , $ field ), (string ) $ value ),
149- Comparison::MEMBER_OF => static function ($ object ) use ($ field , $ value ): bool {
150- $ fieldValues = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field );
186+ Comparison::CONTAINS => fn ($ object ): bool => str_contains ((string ) self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ), (string ) $ value ),
187+ Comparison::MEMBER_OF => function ($ object ) use ($ field , $ value ): bool {
188+ $ fieldValues = ClosureExpressionVisitor::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues );
151189
152190 if (! is_array ($ fieldValues )) {
153191 $ fieldValues = iterator_to_array ($ fieldValues );
154192 }
155193
156194 return in_array ($ value , $ fieldValues , true );
157195 },
158- Comparison::STARTS_WITH => static fn ($ object ): bool => str_starts_with ((string ) self ::getObjectFieldValue ($ object , $ field ), (string ) $ value ),
159- Comparison::ENDS_WITH => static fn ($ object ): bool => str_ends_with ((string ) self ::getObjectFieldValue ($ object , $ field ), (string ) $ value ),
196+ Comparison::STARTS_WITH => fn ($ object ): bool => str_starts_with ((string ) self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ), (string ) $ value ),
197+ Comparison::ENDS_WITH => fn ($ object ): bool => str_ends_with ((string ) self ::getObjectFieldValue ($ object , $ field, $ this -> accessRawFieldValues ), (string ) $ value ),
160198 default => throw new RuntimeException ('Unknown comparison operator: ' . $ comparison ->getOperator ()),
161199 };
162200 }
0 commit comments