This repository has been archived on 2023-08-20. You can view files and clone it, but cannot push or open issues or pull requests.
symfony/src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

342 lines
9.8 KiB
PHP
Raw Normal View History

2015-09-23 11:26:27 +01:00
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\PropertyInfo\Extractor;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
/**
* Extracts PHP informations using the reflection API.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ReflectionExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
{
/**
* @internal
*
* @var string[]
*/
public static $mutatorPrefixes = array('add', 'remove', 'set');
/**
* @internal
*
* @var string[]
*/
public static $accessorPrefixes = array('is', 'can', 'get');
/**
* @internal
*
* @var array[]
*/
public static $arrayMutatorPrefixes = array('add', 'remove');
/**
* {@inheritdoc}
*/
public function getProperties($class, array $context = array())
{
try {
$reflectionClass = new \ReflectionClass($class);
} catch (\ReflectionException $reflectionException) {
return;
}
$properties = array();
foreach ($reflectionClass->getProperties(\ReflectionProperty::IS_PUBLIC) as $reflectionProperty) {
$properties[$reflectionProperty->name] = true;
}
foreach ($reflectionClass->getMethods(\ReflectionMethod::IS_PUBLIC) as $reflectionMethod) {
$propertyName = $this->getPropertyName($reflectionMethod->name);
if ($propertyName) {
$properties[$propertyName] = true;
}
}
return array_keys($properties);
}
/**
* {@inheritdoc}
*/
public function getTypes($class, $property, array $context = array())
{
if ($fromMutator = $this->extractFromMutator($class, $property)) {
return $fromMutator;
}
if ($fromAccessor = $this->extractFromAccessor($class, $property)) {
return $fromAccessor;
}
}
/**
* {@inheritdoc}
*/
public function isReadable($class, $property, array $context = array())
{
if ($this->isPublicProperty($class, $property)) {
return true;
}
list($reflectionMethod) = $this->getAccessorMethod($class, $property);
return null !== $reflectionMethod;
}
/**
* {@inheritdoc}
*/
public function isWritable($class, $property, array $context = array())
{
if ($this->isPublicProperty($class, $property)) {
return true;
}
list($reflectionMethod) = $this->getMutatorMethod($class, $property);
return null !== $reflectionMethod;
}
/**
* Tries to extract type information from mutators.
*
* @param string $class
* @param string $property
*
* @return Type[]|null
*/
private function extractFromMutator($class, $property)
{
list($reflectionMethod, $prefix) = $this->getMutatorMethod($class, $property);
if (null === $reflectionMethod) {
return;
}
$reflectionParameters = $reflectionMethod->getParameters();
$reflectionParameter = $reflectionParameters[0];
$arrayMutator = in_array($prefix, self::$arrayMutatorPrefixes);
if (method_exists($reflectionParameter, 'getType') && $reflectionType = $reflectionParameter->getType()) {
$fromReflectionType = $this->extractFromReflectionType($reflectionType);
if (!$arrayMutator) {
return array($fromReflectionType);
}
$phpType = Type::BUILTIN_TYPE_ARRAY;
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
$collectionValueType = $fromReflectionType;
}
if ($reflectionParameter->isArray()) {
$phpType = Type::BUILTIN_TYPE_ARRAY;
$collection = true;
}
if ($arrayMutator) {
$collection = true;
$nullable = false;
$collectionNullable = $reflectionParameter->allowsNull();
} else {
$nullable = $reflectionParameter->allowsNull();
$collectionNullable = false;
}
if (!isset($collection)) {
$collection = false;
}
if (method_exists($reflectionParameter, 'isCallable') && $reflectionParameter->isCallable()) {
$phpType = Type::BUILTIN_TYPE_CALLABLE;
}
if ($typeHint = $reflectionParameter->getClass()) {
if ($collection) {
$phpType = Type::BUILTIN_TYPE_ARRAY;
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
$collectionValueType = new Type(Type::BUILTIN_TYPE_OBJECT, $collectionNullable, $typeHint->name);
} else {
$phpType = Type::BUILTIN_TYPE_OBJECT;
$typeClass = $typeHint->name;
}
}
// Nothing useful extracted
if (!isset($phpType)) {
return;
}
return array(
new Type(
$phpType,
$nullable,
isset($typeClass) ? $typeClass : null,
$collection,
isset($collectionKeyType) ? $collectionKeyType : null,
isset($collectionValueType) ? $collectionValueType : null
),
);
}
/**
* Tries to extract type information from accessors.
*
* @param string $class
* @param string $property
*
* @return Type[]|null
*/
private function extractFromAccessor($class, $property)
{
list($reflectionMethod, $prefix) = $this->getAccessorMethod($class, $property);
if (null === $reflectionMethod) {
return;
}
if (method_exists($reflectionMethod, 'getReturnType') && $reflectionType = $reflectionMethod->getReturnType()) {
return array($this->extractFromReflectionType($reflectionType));
}
if (in_array($prefix, array('is', 'can'))) {
return array(new Type(Type::BUILTIN_TYPE_BOOL));
}
}
/**
* Extracts data from the PHP 7 reflection type.
*
* @param \ReflectionType $reflectionType
*
* @return Type
*/
private function extractFromReflectionType(\ReflectionType $reflectionType)
{
$phpTypeOrClass = (string) $reflectionType;
$nullable = $reflectionType->allowsNull();
if ($reflectionType->isBuiltin()) {
if (Type::BUILTIN_TYPE_ARRAY === $phpTypeOrClass) {
$type = new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true);
} else {
$type = new Type($phpTypeOrClass, $nullable);
}
} else {
$type = new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $phpTypeOrClass);
}
return $type;
}
/**
* Does the class have the given public property?
*
* @param string $class
* @param string $property
*
* @return bool
*/
private function isPublicProperty($class, $property)
{
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
return $reflectionProperty->isPublic();
} catch (\ReflectionException $reflectionExcetion) {
// Return false if the property doesn't exist
}
return false;
}
/**
* Gets the accessor method.
*
* Returns an array with a the instance of \ReflectionMethod as first key
* and the prefix of the method as second or null if not found.
*
* @param string $class
* @param string $property
*
* @return array|null
*/
private function getAccessorMethod($class, $property)
{
$ucProperty = ucfirst($property);
foreach (self::$accessorPrefixes as $prefix) {
try {
$reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty);
if (0 === $reflectionMethod->getNumberOfRequiredParameters()) {
return array($reflectionMethod, $prefix);
}
} catch (\ReflectionException $reflectionException) {
// Return null if the property doesn't exist
}
}
return;
}
/**
* Gets the mutator method.
*
* Returns an array with a the instance of \ReflectionMethod as first key
* and the prefix of the method as second or null if not found.
*
* @param string $class
* @param string $property
*
* @return array
*/
private function getMutatorMethod($class, $property)
{
$ucProperty = ucfirst($property);
foreach (self::$mutatorPrefixes as $prefix) {
try {
$reflectionMethod = new \ReflectionMethod($class, $prefix.$ucProperty);
// Parameter can be optional to allow things like: method(array $foo = null)
if ($reflectionMethod->getNumberOfParameters() >= 1) {
return array($reflectionMethod, $prefix);
}
} catch (\ReflectionException $reflectionException) {
// Try the next prefix if the method doesn't exist
}
}
}
/**
* Extracts a property name from a method name.
*
* @param string $methodName
*
* @return string
*/
private function getPropertyName($methodName)
{
$pattern = implode('|', array_merge(self::$accessorPrefixes, self::$mutatorPrefixes));
if (preg_match('/^('.$pattern.')(.+)$/i', $methodName, $matches)) {
return $matches[2];
}
}
}