@@ -36,6 +36,50 @@ public function __construct(Document $collection, ?Document $currentDocument = n
3636 }
3737 }
3838
39+ /**
40+ * Check if a value is a valid relationship reference (string ID or Document)
41+ *
42+ * @param mixed $item
43+ * @return bool
44+ */
45+ private function isValidRelationshipValue (mixed $ item ): bool
46+ {
47+ return \is_string ($ item ) || $ item instanceof Document;
48+ }
49+
50+ /**
51+ * Check if a relationship attribute represents a "many" side (returns array of documents)
52+ *
53+ * @param Document|array<string, mixed> $attribute
54+ * @return bool
55+ */
56+ private function isRelationshipArray (Document |array $ attribute ): bool
57+ {
58+ $ options = $ attribute instanceof Document
59+ ? $ attribute ->getAttribute ('options ' , [])
60+ : ($ attribute ['options ' ] ?? []);
61+
62+ $ relationType = $ options ['relationType ' ] ?? '' ;
63+ $ side = $ options ['side ' ] ?? '' ;
64+
65+ // Many-to-many is always an array on both sides
66+ if ($ relationType === Database::RELATION_MANY_TO_MANY ) {
67+ return true ;
68+ }
69+
70+ // One-to-many: array on parent side, single on child side
71+ if ($ relationType === Database::RELATION_ONE_TO_MANY && $ side === Database::RELATION_SIDE_PARENT ) {
72+ return true ;
73+ }
74+
75+ // Many-to-one: array on child side, single on parent side
76+ if ($ relationType === Database::RELATION_MANY_TO_ONE && $ side === Database::RELATION_SIDE_CHILD ) {
77+ return true ;
78+ }
79+
80+ return false ;
81+ }
82+
3983 /**
4084 * Get Description
4185 *
@@ -165,7 +209,19 @@ private function validateOperatorForAttribute(
165209 break ;
166210 case DatabaseOperator::TYPE_ARRAY_APPEND :
167211 case DatabaseOperator::TYPE_ARRAY_PREPEND :
168- if (!$ isArray ) {
212+ // For relationships, check if it's a "many" side
213+ if ($ type === Database::VAR_RELATIONSHIP ) {
214+ if (!$ this ->isRelationshipArray ($ attribute )) {
215+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
216+ return false ;
217+ }
218+ foreach ($ values as $ item ) {
219+ if (!$ this ->isValidRelationshipValue ($ item )) {
220+ $ this ->message = "Cannot apply {$ method } operator: relationship values must be document IDs (strings) or Document objects " ;
221+ return false ;
222+ }
223+ }
224+ } elseif (!$ isArray ) {
169225 $ this ->message = "Cannot apply {$ method } operator to non-array field ' {$ operator ->getAttribute ()}' " ;
170226 return false ;
171227 }
@@ -182,14 +238,24 @@ private function validateOperatorForAttribute(
182238
183239 break ;
184240 case DatabaseOperator::TYPE_ARRAY_UNIQUE :
185- if (!$ isArray ) {
241+ if ($ type === Database::VAR_RELATIONSHIP ) {
242+ if (!$ this ->isRelationshipArray ($ attribute )) {
243+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
244+ return false ;
245+ }
246+ } elseif (!$ isArray ) {
186247 $ this ->message = "Cannot apply {$ method } operator to non-array field ' {$ operator ->getAttribute ()}' " ;
187248 return false ;
188249 }
189250
190251 break ;
191252 case DatabaseOperator::TYPE_ARRAY_INSERT :
192- if (!$ isArray ) {
253+ if ($ type === Database::VAR_RELATIONSHIP ) {
254+ if (!$ this ->isRelationshipArray ($ attribute )) {
255+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
256+ return false ;
257+ }
258+ } elseif (!$ isArray ) {
193259 $ this ->message = "Cannot apply {$ method } operator to non-array field ' {$ operator ->getAttribute ()}' " ;
194260 return false ;
195261 }
@@ -206,6 +272,14 @@ private function validateOperatorForAttribute(
206272 }
207273
208274 $ insertValue = $ values [1 ];
275+
276+ if ($ type === Database::VAR_RELATIONSHIP ) {
277+ if (!$ this ->isValidRelationshipValue ($ insertValue )) {
278+ $ this ->message = "Cannot apply {$ method } operator: relationship values must be document IDs (strings) or Document objects " ;
279+ return false ;
280+ }
281+ }
282+
209283 if ($ type === Database::VAR_INTEGER && \is_numeric ($ insertValue )) {
210284 if ($ insertValue > Database::MAX_INT || $ insertValue < Database::MIN_INT ) {
211285 $ this ->message = "Cannot apply {$ method } operator: array items must be between " . Database::MIN_INT . " and " . Database::MAX_INT ;
@@ -228,7 +302,19 @@ private function validateOperatorForAttribute(
228302
229303 break ;
230304 case DatabaseOperator::TYPE_ARRAY_REMOVE :
231- if (!$ isArray ) {
305+ if ($ type === Database::VAR_RELATIONSHIP ) {
306+ if (!$ this ->isRelationshipArray ($ attribute )) {
307+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
308+ return false ;
309+ }
310+ $ toValidate = \is_array ($ values [0 ]) ? $ values [0 ] : $ values ;
311+ foreach ($ toValidate as $ item ) {
312+ if (!$ this ->isValidRelationshipValue ($ item )) {
313+ $ this ->message = "Cannot apply {$ method } operator: relationship values must be document IDs (strings) or Document objects " ;
314+ return false ;
315+ }
316+ }
317+ } elseif (!$ isArray ) {
232318 $ this ->message = "Cannot apply {$ method } operator to non-array field ' {$ operator ->getAttribute ()}' " ;
233319 return false ;
234320 }
@@ -240,7 +326,12 @@ private function validateOperatorForAttribute(
240326
241327 break ;
242328 case DatabaseOperator::TYPE_ARRAY_INTERSECT :
243- if (!$ isArray ) {
329+ if ($ type === Database::VAR_RELATIONSHIP ) {
330+ if (!$ this ->isRelationshipArray ($ attribute )) {
331+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
332+ return false ;
333+ }
334+ } elseif (!$ isArray ) {
244335 $ this ->message = "Cannot use {$ method } operator on non-array attribute ' {$ operator ->getAttribute ()}' " ;
245336 return false ;
246337 }
@@ -250,16 +341,41 @@ private function validateOperatorForAttribute(
250341 return false ;
251342 }
252343
344+ if ($ type === Database::VAR_RELATIONSHIP ) {
345+ foreach ($ values as $ item ) {
346+ if (!$ this ->isValidRelationshipValue ($ item )) {
347+ $ this ->message = "Cannot apply {$ method } operator: relationship values must be document IDs (strings) or Document objects " ;
348+ return false ;
349+ }
350+ }
351+ }
352+
253353 break ;
254354 case DatabaseOperator::TYPE_ARRAY_DIFF :
255- if (!$ isArray ) {
355+ if ($ type === Database::VAR_RELATIONSHIP ) {
356+ if (!$ this ->isRelationshipArray ($ attribute )) {
357+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
358+ return false ;
359+ }
360+ foreach ($ values as $ item ) {
361+ if (!$ this ->isValidRelationshipValue ($ item )) {
362+ $ this ->message = "Cannot apply {$ method } operator: relationship values must be document IDs (strings) or Document objects " ;
363+ return false ;
364+ }
365+ }
366+ } elseif (!$ isArray ) {
256367 $ this ->message = "Cannot use {$ method } operator on non-array attribute ' {$ operator ->getAttribute ()}' " ;
257368 return false ;
258369 }
259370
260371 break ;
261372 case DatabaseOperator::TYPE_ARRAY_FILTER :
262- if (!$ isArray ) {
373+ if ($ type === Database::VAR_RELATIONSHIP ) {
374+ if (!$ this ->isRelationshipArray ($ attribute )) {
375+ $ this ->message = "Cannot apply {$ method } operator to single-value relationship ' {$ operator ->getAttribute ()}' " ;
376+ return false ;
377+ }
378+ } elseif (!$ isArray ) {
263379 $ this ->message = "Cannot apply {$ method } operator to non-array field ' {$ operator ->getAttribute ()}' " ;
264380 return false ;
265381 }
0 commit comments