|
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 | + |
38BA
| 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<
38BA
/td> | | - $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
D306
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.'()"/"r
D306
emove'.$singular.'()", '; |
| 770 | + }, $singulars)), |
| 771 | + $setter, |
| 772 | + $getsetter, |
| 773 | + $reflClass->name |
| 774 | + ); |
| 775 | + } |
729 | 776 | } |
730 | 777 | } |
731 | 778 |
|
|
0 commit comments