|
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.'()",
341A
9;; |
| 770 | + }, $singulars)), |
| 771 | + $setter, |
| 772 | + $getsetter, |
| 773 | + $reflClass->name |
| 774 | + ); |
| 775 | + } |
729 | 776 | }
|
730 | 777 | }
|
731 | 778 |
|
|
0 commit comments