diff --git a/lib/Doctrine/ORM/Mapping/Reflection/ReflectionPropertiesGetter.php b/lib/Doctrine/ORM/Mapping/Reflection/ReflectionPropertiesGetter.php new file mode 100644 index 000000000..5da1e9a54 --- /dev/null +++ b/lib/Doctrine/ORM/Mapping/Reflection/ReflectionPropertiesGetter.php @@ -0,0 +1,161 @@ +. + */ + +namespace Doctrine\ORM\Mapping\Reflection; + +use Doctrine\Common\Persistence\Mapping\ReflectionService; +use ReflectionClass; +use ReflectionProperty; + +/** + * Utility class to retrieve all reflection instance properties of a given class, including + * private inherited properties and transient properties. + * + * @author Marco Pivetta + */ +final class ReflectionPropertiesGetter +{ + /** + * @var ReflectionProperty[][] indexed by class name and property internal name + */ + private $properties = []; + + /** + * @var ReflectionService + */ + private $reflectionService; + + /** + * @param ReflectionService $reflectionService + */ + public function __construct(ReflectionService $reflectionService) + { + $this->reflectionService = $reflectionService; + } + + /** + * @param $className + * + * @return ReflectionProperty[] indexed by property internal name + */ + public function getProperties($className) + { + if (isset($this->properties[$className])) { + return $this->properties[$className]; + } + + return $this->properties[$className] = call_user_func_array( + 'array_merge', + // first merge because `array_merge` expects >= 1 params + array_merge( + [[]], + array_map( + [$this, 'getClassProperties'], + $this->getHierarchyClasses($className) + ) + ) + ); + } + + /** + * @param string $className + * + * @return ReflectionClass[] + */ + private function getHierarchyClasses($className) + { + $classes = []; + $parentClassName = $className; + + while ($parentClassName && $currentClass = $this->reflectionService->getClass($parentClassName)) { + $classes[] = $currentClass; + $parentClassName = null; + + if ($parentClass = $currentClass->getParentClass()) { + $parentClassName = $parentClass->getName(); + } + } + + return $classes; + } + + /** + * @param ReflectionClass $reflectionClass + * + * @return ReflectionProperty[] + */ + private function getClassProperties(ReflectionClass $reflectionClass) + { + $properties = $reflectionClass->getProperties(); + + return array_filter( + array_filter(array_map( + [$this, 'getAccessibleProperty'], + array_combine( + array_map([$this, 'getLogicalName'], $properties), + $properties + ) + )), + [$this, 'isInstanceProperty'] + ); + } + + /** + * @param ReflectionProperty $reflectionProperty + * + * @return bool + */ + private function isInstanceProperty(ReflectionProperty $reflectionProperty) + { + return ! $reflectionProperty->isStatic(); + } + + /** + * @param ReflectionProperty $property + * + * @return null|ReflectionProperty + */ + private function getAccessibleProperty(ReflectionProperty $property) + { + return $this->reflectionService->getAccessibleProperty( + $property->getDeclaringClass()->getName(), + $property->getName() + ); + } + + /** + * @param ReflectionProperty $property + * + * @return string + */ + private function getLogicalName(ReflectionProperty $property) + { + $propertyName = $property->getName(); + + if ($property->isPublic()) { + return $propertyName; + } + + if ($property->isProtected()) { + return "\0*\0" . $propertyName; + } + + return "\0" . $property->getDeclaringClass()->getName() . "\0" . $propertyName; + } +}