11
11
12
12
namespace Symfony \Component \PropertyAccess ;
13
13
14
+ use Symfony \Component \PropertyAccess \Exception \InvalidArgumentException ;
14
15
use Symfony \Component \PropertyAccess \Exception \NoSuchPropertyException ;
15
16
use Symfony \Component \PropertyAccess \Exception \NoSuchIndexException ;
16
17
use Symfony \Component \PropertyAccess \Exception \UnexpectedTypeException ;
@@ -53,7 +54,12 @@ public function getValue($objectOrArray, $propertyPath)
53
54
if (is_string ($ propertyPath )) {
54
55
$ propertyPath = new PropertyPath ($ propertyPath );
55
56
} elseif (!$ propertyPath instanceof PropertyPathInterface) {
56
- throw new UnexpectedTypeException ($ propertyPath , 'string or Symfony\Component\PropertyAccess\PropertyPathInterface ' );
57
+ throw new InvalidArgumentException (sprintf (
58
+ 'The property path should be a string or an instance of ' .
59
+ '"Symfony\Component\PropertyAccess\PropertyPathInterface". ' .
60
+ 'Got: "%s" ' ,
61
+ is_object ($ propertyPath ) ? get_class ($ propertyPath ) : gettype ($ propertyPath )
62
+ ));
57
63
}
58
64
59
65
$ propertyValues =& $ this ->readPropertiesUntil ($ objectOrArray , $ propertyPath , $ propertyPath ->getLength (), $ this ->ignoreInvalidIndices );
@@ -69,7 +75,12 @@ public function setValue(&$objectOrArray, $propertyPath, $value)
69
75
if (is_string ($ propertyPath )) {
70
76
$ propertyPath = new PropertyPath ($ propertyPath );
71
77
} elseif (!$ propertyPath instanceof PropertyPathInterface) {
72
- throw new UnexpectedTypeException ($ propertyPath , 'string or Symfony\Component\PropertyAccess\PropertyPathInterface ' );
78
+ throw new InvalidArgumentException (sprintf (
79
+ 'The property path should be a string or an instance of ' .
80
+ '"Symfony\Component\PropertyAccess\PropertyPathInterface". ' .
81
+ 'Got: "%s" ' ,
82
+ is_object ($ propertyPath ) ? get_class ($ propertyPath ) : gettype ($ propertyPath )
83
+ ));
73
84
}
74
85
75
86
$ propertyValues =& $ this ->readPropertiesUntil ($ objectOrArray , $ propertyPath , $ propertyPath ->getLength () - 1 );
@@ -90,13 +101,11 @@ public function setValue(&$objectOrArray, $propertyPath, $value)
90
101
}
91
102
92
103
$ property = $ propertyPath ->getElement ($ i );
93
- //$singular = $propertyPath->singulars[$i];
94
- $ singular = null ;
95
104
96
105
if ($ propertyPath ->isIndex ($ i )) {
97
106
$ this ->writeIndex ($ objectOrArray , $ property , $ value );
98
107
} else {
99
- $ this ->writeProperty ($ objectOrArray , $ property , $ singular , $ value );
108
+ $ this ->writeProperty ($ objectOrArray , $ property , $ value );
100
109
}
101
110
}
102
111
@@ -113,7 +122,12 @@ public function isReadable($objectOrArray, $propertyPath)
113
122
if (is_string ($ propertyPath )) {
114
123
$ propertyPath = new PropertyPath ($ propertyPath );
115
124
} elseif (!$ propertyPath instanceof PropertyPathInterface) {
116
- throw new UnexpectedTypeException ($ propertyPath , 'string or Symfony\Component\PropertyAccess\PropertyPathInterface ' );
125
+ throw new InvalidArgumentException (sprintf (
126
+ 'The property path should be a string or an instance of ' .
127
+ '"Symfony\Component\PropertyAccess\PropertyPathInterface". ' .
128
+ 'Got: "%s" ' ,
129
+ is_object ($ propertyPath ) ? get_class ($ propertyPath ) : gettype ($ propertyPath )
130
+ ));
117
131
}
118
132
119
133
try {
@@ -132,12 +146,17 @@ public function isReadable($objectOrArray, $propertyPath)
132
146
/**
133
147
* {@inheritdoc}
134
148
*/
135
- public function isWritable ($ objectOrArray , $ propertyPath, $ value )
149
+ public function isWritable ($ objectOrArray , $ propertyPath )
136
150
{
137
151
if (is_string ($ propertyPath )) {
138
152
$ propertyPath = new PropertyPath ($ propertyPath );
139
153
} elseif (!$ propertyPath instanceof PropertyPathInterface) {
140
- throw new UnexpectedTypeException ($ propertyPath , 'string or Symfony\Component\PropertyAccess\PropertyPathInterface ' );
154
+ throw new InvalidArgumentException (sprintf (
155
+ 'The property path should be a string or an instance of ' .
156
+ '"Symfony\Component\PropertyAccess\PropertyPathInterface". ' .
157
+ 'Got: "%s" ' ,
158
+ is_object ($ propertyPath ) ? get_class ($ propertyPath ) : gettype ($ propertyPath )
159
+ ));
141
160
}
142
161
143
162
try {
@@ -165,13 +184,12 @@ public function isWritable($objectOrArray, $propertyPath, $value)
165
184
return false ;
166
185
}
167
186
} else {
168
- if (!$ this ->isPropertyWritable ($ objectOrArray , $ property, $ value )) {
187
+ if (!$ this ->isPropertyWritable ($ objectOrArray , $ property )) {
169
188
return false ;
170
189
}
171
190
}
172
191
}
173
192
174
- $ value = $ objectOrArray ;
175
193
$ overwrite = !$ propertyValues [$ i ][self ::IS_REF ];
176
194
}
177
195
@@ -346,7 +364,7 @@ private function &readProperty(&$object, $property)
346
364
}
347
365
348
366
/**
349
- * Sets the value of the property at the given index in the path
367
+ * Sets the value of an index in a given array-accessible value.
350
368
*
351
369
* @param \ArrayAccess|array $array An array or \ArrayAccess object to write to
352
370
* @param string|integer $index The index to write at
@@ -364,74 +382,33 @@ private function writeIndex(&$array, $index, $value)
364
382
}
365
383
366
384
/**
367
- * Sets the value of the property at the given index in the path
385
+ * Sets the value of a property in the given object
368
386
*
369
- * @param object|array $object The object or array to write to
370
- * @param string $property The property to write
371
- * @param string|null $singular The singular form of the property name or null
372
- * @param mixed $value The value to write
387
+ * @param object $object The object to write to
388
+ * @param string $property The property to write
389
+ * @param mixed $value The value to write
373
390
*
374
391
* @throws NoSuchPropertyException If the property does not exist or is not
375
392
* public.
376
393
*/
377
- private function writeProperty (&$ object , $ property , $ singular , $ value )
394
+ private function writeProperty (&$ object , $ property , $ value )
378
395
{
379
- $ guessedAdders = '' ;
380
-
381
396
if (!is_object ($ object )) {
382
397
throw new NoSuchPropertyException (sprintf ('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead? ' , $ property , $ property ));
383
398
}
384
399
385
400
$ reflClass = new \ReflectionClass ($ object );
386
401
$ plural = $ this ->camelize ($ property );
387
-
388
- // Any of the two methods is required, but not yet known
389
- $ singulars = null !== $ singular ? array ($ singular ) : (array ) StringUtil::singularify ($ plural );
402
+ $ singulars = (array ) StringUtil::singularify ($ plural );
390
403
391
404
if (is_array ($ value ) || $ value instanceof \Traversable) {
392
405
$ methods = $ this ->findAdderAndRemover ($ reflClass , $ singulars );
393
406
407
+ // Use addXxx() and removeXxx() to write the collection
394
408
if (null !== $ methods ) {
395
- // At this point the add and remove methods have been found
396
- // Use iterator_to_array() instead of clone in order to prevent side effects
397
- // see https://github.com/symfony/symfony/issues/4670
398
- $ itemsToAdd = is_object ($ value ) ? iterator_to_array ($ value ) : $ value ;
399
- $ itemToRemove = array ();
400
- $ propertyValue = $ this ->readProperty ($ object , $ property );
401
- $ previousValue = $ propertyValue [self ::VALUE ];
402
-
403
- if (is_array ($ previousValue ) || $ previousValue instanceof \Traversable) {
404
- foreach ($ previousValue as $ previousItem ) {
405
- foreach ($ value as $ key => $ item ) {
406
- if ($ item === $ previousItem ) {
407
- // Item found, don't add
408
- unset($ itemsToAdd [$ key ]);
409
-
410
- // Next $previousItem
411
- continue 2 ;
412
- }
413
- }
414
-
415
- // Item not found, add to remove list
416
- $ itemToRemove [] = $ previousItem ;
417
- }
418
- }
419
-
420
- foreach ($ itemToRemove as $ item ) {
421
- call_user_func (array ($ object , $ methods [1 ]), $ item );
422
- }
423
-
424
- foreach ($ itemsToAdd as $ item ) {
425
- call_user_func (array ($ object , $ methods [0 ]), $ item );
426
- }
409
+ $ this ->writeCollection ($ object , $ property , $ value , $ methods [0 ], $ methods [1 ]);
427
410
428
411
return ;
429
- } else {
430
- // It is sufficient to include only the adders in the error
431
- // message. If the user implements the adder but not the remover,
432
- // an exception will be thrown in findAdderAndRemover() that
433
- // the remover has to be implemented as well.
434
- $ guessedAdders = '"add ' .implode ('()", "add ' , $ singulars ).'()", ' ;
435
412
}
436
413
}
437
414
@@ -459,43 +436,98 @@ private function writeProperty(&$object, $property, $singular, $value)
459
436
'Neither the property "%s" nor one of the methods %s"%s()", ' .
460
437
'"__set()" or "__call()" exist and have public access in class "%s". ' ,
461
438
$ property ,
462
- $ guessedAdders ,
439
+ implode ('' , array_map (function ($ singular ) {
440
+ return '"add ' .$ singular .'()"/"remove ' .$ singular .'()", ' ;
441
+ }, $ singulars )),
463
442
$ setter ,
464
443
$ reflClass ->name
465
444
));
466
445
}
467
446
}
468
447
469
- private function isPropertyWritable ($ object , $ property , $ value )
448
+ /**
449
+ * Adjusts a collection-valued property by calling add*() and remove*()
450
+ * methods.
451
+ *
452
+ * @param object $object The object to write to
453
+ * @param string $property The property to write
454
+ * @param array|\Traversable $collection The collection to write
455
+ * @param string $addMethod The add*() method
456
+ * @param string $removeMethod The remove*() method
457
+ */
458
+ private function writeCollection ($ object , $ property , $ collection , $ addMethod , $ removeMethod )
470
459
{
471
- if (!is_object ($ object )) {
472
- throw new NoSuchPropertyException (sprintf ('Cannot write property "%s" to an array. Maybe you should write the property path as "[%s]" instead? ' , $ property , $ property ));
460
+ // At this point the add and remove methods have been found
461
+ // Use iterator_to_array() instead of clone in order to prevent side effects
462
+ // see https://github.com/symfony/symfony/issues/4670
463
+ $ itemsToAdd = is_object ($ collection ) ? iterator_to_array ($ collection ) : $ collection ;
464
+ $ itemToRemove = array ();
465
+ $ propertyValue = $ this ->readProperty ($ object , $ property );
466
+ $ previousValue = $ propertyValue [self ::VALUE ];
467
+
468
+ if (is_array ($ previousValue ) || $ previousValue instanceof \Traversable) {
469
+ foreach ($ previousValue as $ previousItem ) {
470
+ foreach ($ collection as $ key => $ item ) {
471
+ if ($ item === $ previousItem ) {
472
+ // Item found, don't add
473
+ unset($ itemsToAdd [$ key ]);
474
+
475
+ // Next $previousItem
476
+ continue 2 ;
477
+ }
478
+ }
479
+
480
+ // Item not found, add to remove list
481
+ $ itemToRemove [] = $ previousItem ;
482
+ }
473
483
}
474
484
475
- $ reflClass = new \ReflectionClass ($ object );
476
- $ plural = $ this ->camelize ($ property );
485
+ foreach ($ itemToRemove as $ item ) {
486
+ call_user_func (array ($ object , $ removeMethod ), $ item );
487
+ }
477
488
478
- // Any of the two methods is required, but not yet known
479
- $ singulars = (array ) StringUtil::singularify ($ plural );
489
+ foreach ($ itemsToAdd as $ item ) {
490
+ call_user_func (array ($ object , $ addMethod ), $ item );
491
+ }
492
+ }
480
493
481
- if (is_array ($ value ) || $ value instanceof \Traversable) {
482
- try {
483
- if (null !== $ this ->findAdderAndRemover ($ reflClass , $ singulars )) {
484
- return true ;
485
- }
486
- } catch (NoSuchPropertyException $ e ) {
487
- return false ;
488
- }
494
+ /**
495
+ * Returns whether a property is writable in the given object.
496
+ *
497
+ * @param object $object The object to write to
498
+ * @param string $property The property to write
499
+ *
500
+ * @return Boolean Whether the property is writable
501
+ */
502
+ private function isPropertyWritable ($ object , $ property )
503
+ {
504
+ if (!is_object ($ object )) {
505
+ return false ;
489
506
}
490
507
508
+ $ reflClass = new \ReflectionClass ($ object );
509
+
491
510
$ setter = 'set ' .$ this ->camelize ($ property );
492
511
$ classHasProperty = $ reflClass ->hasProperty ($ property );
493
512
494
- return $ this ->isMethodAccessible ($ reflClass , $ setter , 1 )
513
+ if ( $ this ->isMethodAccessible ($ reflClass , $ setter , 1 )
495
514
|| $ this ->isMethodAccessible ($ reflClass , '__set ' , 2 )
496
515
|| ($ classHasProperty && $ reflClass ->getProperty ($ property )->isPublic ())
497
516
|| (!$ classHasProperty && property_exists ($ object , $ property ))
498
- || ($ this ->magicCall && $ this ->isMethodAccessible ($ reflClass , '__call ' , 2 ));
517
+ || ($ this ->magicCall && $ this ->isMethodAccessible ($ reflClass , '__call ' , 2 ))) {
518
+ return true ;
519
+ }
520
+
521
+ $ plural = $ this ->camelize ($ property );
522
+
523
+ // Any of the two methods is required, but not yet known
524
+ $ singulars = (array ) StringUtil::singularify ($ plural );
525
+
526
+ if (null !== $ this ->findAdderAndRemover ($ reflClass , $ singulars )) {
527
+ return true ;
528
+ }
529
+
530
+ return false ;
499
531
}
500
532
501
533
/**
@@ -517,8 +549,6 @@ private function camelize($string)
517
549
* @param array $singulars The singular form of the property name or null
518
550
*
519
551
* @return array|null An array containing the adder and remover when found, null otherwise
520
- *
521
- * @throws NoSuchPropertyException If the property does not exist
522
552
*/
523
553
private function findAdderAndRemover (\ReflectionClass $ reflClass , array $ singulars )
524
554
{
@@ -534,19 +564,6 @@ private function findAdderAndRemover(\ReflectionClass $reflClass, array $singula
534
564
if ($ addMethodFound && $ removeMethodFound ) {
535
565
return array ($ addMethod , $ removeMethod );
536
566
}
537
-
538
- if ($ addMethodFound xor $ removeMethodFound && null === $ exception ) {
539
- $ exception = new NoSuchPropertyException (sprintf (
540
- 'Found the public method "%s()", but did not find a public "%s()" on class %s ' ,
541
- $ addMethodFound ? $ addMethod : $ removeMethod ,
542
- $ addMethodFound ? $ removeMethod : $ addMethod ,
543
- $ reflClass ->name
544
- ));
545
- }
546
- }
547
-
548
- if (null !== $ exception ) {
549
- throw $ exception ;
550
567
}
551
568
552
569
return null ;
0 commit comments