11
11
12
12
namespace Symfony \Component \PropertyAccess ;
13
13
14
+ use Doctrine \Common \Annotations \AnnotationReader ;
14
15
use Symfony \Component \PropertyAccess \Exception \AccessException ;
15
16
use Symfony \Component \PropertyAccess \Exception \NoSuchPropertyException ;
16
17
use Symfony \Component \PropertyAccess \Exception \NoSuchIndexException ;
@@ -114,17 +115,23 @@ class PropertyAccessor implements PropertyAccessorInterface
114
115
*/
115
116
private $ writePropertyCache = array ();
116
117
118
+ /**
119
+ * @var AnnotationReader
120
+ */
121
+ private $ reader ;
122
+
117
123
/**
118
124
* Should not be used by application code. Use
119
125
* {@link PropertyAccess::createPropertyAccessor()} instead.
120
126
*
121
127
* @param bool $magicCall
122
128
* @param bool $throwExceptionOnInvalidIndex
123
129
*/
124
- public function __construct ($ magicCall = false , $ throwExceptionOnInvalidIndex = false )
130
+ public function __construct ($ magicCall = false , $ throwExceptionOnInvalidIndex = false , AnnotationReader $ reader = null )
125
131
{
126
132
$ this ->magicCall = $ magicCall ;
127
133
$ this ->ignoreInvalidIndices = !$ throwExceptionOnInvalidIndex ;
134
+ $ this ->reader = $ reader ;
128
135
}
129
136
130
137
/**
@@ -460,17 +467,30 @@ private function getReadAccessInfo($object, $property)
460
467
if (isset ($ this ->readPropertyCache [$ key ])) {
461
468
$ access = $ this ->readPropertyCache [$ key ];
462
469
} else {
470
+ $ annotation = null ;
471
+
463
472
$ access = array ();
464
473
465
474
$ reflClass = new \ReflectionClass ($ object );
466
- $ access [self ::ACCESS_HAS_PROPERTY ] = $ reflClass ->hasProperty ($ property );
475
+ $ hasProperty = $ reflClass ->hasProperty ($ property );
476
+ $ access [self ::ACCESS_HAS_PROPERTY ] = $ hasProperty ;
477
+
478
+ if ($ hasProperty && $ this ->reader ) {
479
+ $ annotation = $ this ->reader ->getPropertyAnnotation ($ reflClass ->getProperty ($ property ),
480
+ 'Symfony\Component\PropertyAccess\Annotation\PropertyAccessor ' );
481
+
482
+ }
483
+
467
484
$ camelProp = $ this ->camelize ($ property );
468
485
$ getter = 'get ' .$ camelProp ;
469
486
$ getsetter = lcfirst ($ camelProp ); // jQuery style, e.g. read: last(), write: last($item)
470
487
$ isser = 'is ' .$ camelProp ;
471
488
$ hasser = 'has ' .$ camelProp ;
472
489
473
- if ($ reflClass ->hasMethod ($ getter ) && $ reflClass ->getMethod ($ getter )->isPublic ()) {
490
+ if ($ annotation && $ annotation ->getGetter ()) {
491
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
492
+ $ access [self ::ACCESS_NAME ] = $ annotation ->getGetter ();
493
+ } elseif ($ reflClass ->hasMethod ($ getter ) && $ reflClass ->getMethod ($ getter )->isPublic ()) {
474
494
$ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
475
495
$ access [self ::ACCESS_NAME ] = $ getter ;
476
496
} elseif ($ reflClass ->hasMethod ($ getsetter ) && $ reflClass ->getMethod ($ getsetter )->isPublic ()) {
@@ -676,56 +696,83 @@ private function getWriteAccessInfo($object, $property, $value)
676
696
if (isset ($ this ->writePropertyCache [$ key ])) {
677
697
$ access = $ this ->writePropertyCache [$ key ];
678
698
} else {
699
+ $ annotation = null ;
700
+
679
701
$ access = array ();
680
702
681
703
$ reflClass = new \ReflectionClass ($ object );
682
- $ access [self ::ACCESS_HAS_PROPERTY ] = $ reflClass ->hasProperty ($ property );
683
- $ camelized = $ this ->camelize ($ property );
684
- $ singulars = (array ) StringUtil::singularify ($ camelized );
704
+ $ hasProperty = $ reflClass ->hasProperty ($ property );
705
+ $ access [self ::ACCESS_HAS_PROPERTY ] = $ hasProperty ;
706
+
707
+ $ transversable = is_array ($ value ) || $ value instanceof \Traversable;
708
+ $ done = false ;
709
+
710
+ if ($ hasProperty && $ this ->reader ) {
711
+ $ annotation = $ this ->reader ->getPropertyAnnotation ($ reflClass ->getProperty ($ property ),
712
+ 'Symfony\Component\PropertyAccess\Annotation\PropertyAccessor ' );
713
+
714
+ if ($ annotation ) {
715
+ if ($ transversable && $ annotation ->getAdder () && $ annotation ->getRemover ()) {
716
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_ADDER_AND_REMOVER ;
717
+ $ access [self ::ACCESS_ADDER ] = $ annotation ->getAdder ();
718
+ $ access [self ::ACCESS_REMOVER ] = $ annotation ->getRemover ();
719
+ $ done = true ;
720
+ } elseif ($ annotation ->getSetter ()) {
721
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
722
+ $ access [self ::ACCESS_NAME ] = $ annotation ->getSetter ();
723
+ $ done = true ;
724
+ }
725
+ }
726
+ }
685
727
686
- if (is_array ($ value ) || $ value instanceof \Traversable) {
687
- $ methods = $ this ->findAdderAndRemover ($ reflClass , $ singulars );
728
+ if (!$ done ) {
729
+ $ camelized = $ this ->camelize ($ property );
730
+ $ singulars = (array ) StringUtil::singularify ($ camelized );
688
731
689
- if (null !== $ methods ) {
690
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_ADDER_AND_REMOVER ;
691
- $ access [self ::ACCESS_ADDER ] = $ methods [0 ];
692
- $ access [self ::ACCESS_REMOVER ] = $ methods [1 ];
732
+ if ($ transversable ) {
733
+ $ methods = $ this ->findAdderAndRemover ($ reflClass , $ singulars );
734
+
735
+ if (null !== $ methods ) {
736
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_ADDER_AND_REMOVER ;
737
+ $ access [self ::ACCESS_ADDER ] = $ methods [0 ];
738
+ $ access [self ::ACCESS_REMOVER ] = $ methods [1 ];
739
+ }
693
740
}
694
- }
695
741
696
- if (!isset ($ access [self ::ACCESS_TYPE ])) {
697
- $ setter = 'set ' .$ camelized ;
698
- $ getsetter = lcfirst ($ camelized ); // jQuery style, e.g. read: last(), write: last($item)
699
-
700
- if ($ this ->isMethodAccessible ($ reflClass , $ setter , 1 )) {
701
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
702
- $ access [self ::ACCESS_NAME ] = $ setter ;
703
- } elseif ($ this ->isMethodAccessible ($ reflClass , $ getsetter , 1 )) {
704
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
705
- $ access [self ::ACCESS_NAME ] = $ getsetter ;
706
- } elseif ($ this ->isMethodAccessible ($ reflClass , '__set ' , 2 )) {
707
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_PROPERTY ;
708
- $ access [self ::ACCESS_NAME ] = $ property ;
709
- } elseif ($ access [self ::ACCESS_HAS_PROPERTY ] && $ reflClass ->getProperty ($ property )->isPublic ()) {
710
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_PROPERTY ;
711
- $ access [self ::ACCESS_NAME ] = $ property ;
712
- } elseif ($ this ->magicCall && $ this ->isMethodAccessible ($ reflClass , '__call ' , 2 )) {
713
- // we call the getter and hope the __call do the job
714
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_MAGIC ;
715
- $ access [self ::ACCESS_NAME ] = $ setter ;
716
- } else {
717
- $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_NOT_FOUND ;
718
- $ access [self ::ACCESS_NAME ] = sprintf (
719
- 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", ' .
720
- '"__set()" or "__call()" exist and have public access in class "%s". ' ,
721
- $ property ,
722
- implode ('' , array_map (function ($ singular ) {
723
- return '"add ' .$ singular .'()"/"remove ' .$ singular .'()", ' ;
724
- }, $ singulars )),
725
- $ setter ,
726
- $ getsetter ,
727
- $ reflClass ->name
728
- );
742
+ if (!isset ($ access [self ::ACCESS_TYPE ])) {
743
+ $ setter = 'set ' .$ camelized ;
744
+ $ getsetter = lcfirst ($ camelized ); // jQuery style, e.g. read: last(), write: last($item)
745
+
746
+ if ($ this ->isMethodAccessible ($ reflClass , $ setter , 1 )) {
747
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
748
+ $ access [self ::ACCESS_NAME ] = $ setter ;
749
+ } elseif ($ this ->isMethodAccessible ($ reflClass , $ getsetter , 1 )) {
750
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_METHOD ;
751
+ $ access [self ::ACCESS_NAME ] = $ getsetter ;
752
+ } elseif ($ this ->isMethodAccessible ($ reflClass , '__set ' , 2 )) {
753
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_PROPERTY ;
754
+ $ access [self ::ACCESS_NAME ] = $ property ;
755
+ } elseif ($ access [self ::ACCESS_HAS_PROPERTY ] && $ reflClass ->getProperty ($ property )->isPublic ()) {
756
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_PROPERTY ;
757
+ $ access [self ::ACCESS_NAME ] = $ property ;
758
+ } elseif ($ this ->magicCall && $ this ->isMethodAccessible ($ reflClass , '__call ' , 2 )) {
759
+ // we call the getter and hope the __call do the job
760
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_MAGIC ;
761
+ $ access [self ::ACCESS_NAME ] = $ setter ;
762
+ } else {
763
+ $ access [self ::ACCESS_TYPE ] = self ::ACCESS_TYPE_NOT_FOUND ;
764
+ $ access [self ::ACCESS_NAME ] = sprintf (
765
+ 'Neither the property "%s" nor one of the methods %s"%s()", "%s()", ' .
766
+ '"__set()" or "__call()" exist and have public access in class "%s". ' ,
767
+ $ property ,
768
+ implode ('' , array_map (function ($ singular ) {
769
+ return '"add ' .$ singular .'()"/"remove ' .$ singular .'()", ' ;
770
+ }, $ singulars )),
771
+ $ setter ,
772
+ $ getsetter ,
773
+ $ reflClass ->name
774
+ );
775
+ }
729
776
}
730
777
}
731
778
0 commit comments