[PropertyInfo] Import the component

This commit is contained in:
Kévin Dunglas 2015-09-23 12:26:27 +02:00 committed by Fabien Potencier
parent d1ae400cb1
commit f1eb185236
29 changed files with 2306 additions and 1 deletions

View File

@ -48,6 +48,7 @@
"symfony/options-resolver": "self.version",
"symfony/process": "self.version",
"symfony/property-access": "self.version",
"symfony/property-info": "self.version",
"symfony/proxy-manager-bridge": "self.version",
"symfony/routing": "self.version",
"symfony/security": "self.version",
@ -76,7 +77,11 @@
"monolog/monolog": "~1.11",
"ircmaxell/password-compat": "~1.0",
"ocramius/proxy-manager": "~0.4|~1.0",
"egulias/email-validator": "~1.2"
"egulias/email-validator": "~1.2",
"phpdocumentor/reflection": "^1.0.7"
},
"conflict": {
"phpdocumentor/reflection": "<1.0.7"
},
"autoload": {
"psr-4": {

View File

@ -0,0 +1,149 @@
<?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\Bridge\Doctrine\PropertyInfo;
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
/**
* Extracts data using Doctrine ORM and ODM metadata.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface
{
/**
* @var ClassMetadataFactory
*/
private $classMetadataFactory;
public function __construct(ClassMetadataFactory $classMetadataFactory)
{
$this->classMetadataFactory = $classMetadataFactory;
}
/**
* {@inheritdoc}
*/
public function getProperties($class, array $context = array())
{
try {
$metadata = $this->classMetadataFactory->getMetadataFor($class);
} catch (MappingException $exception) {
return;
}
return array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
}
/**
* {@inheritdoc}
*/
public function getTypes($class, $property, array $context = array())
{
try {
$metadata = $this->classMetadataFactory->getMetadataFor($class);
} catch (MappingException $exception) {
return;
}
if ($metadata->hasAssociation($property)) {
$class = $metadata->getAssociationTargetClass($property);
if ($metadata->isSingleValuedAssociation($property)) {
if ($metadata instanceof ClassMetadataInfo) {
$nullable = isset($metadata->discriminatorColumn['nullable']) ? $metadata->discriminatorColumn['nullable'] : false;
} else {
$nullable = false;
}
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class));
}
return array(new Type(
Type::BUILTIN_TYPE_OBJECT,
false,
'Doctrine\Common\Collections\Collection',
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_OBJECT, false, $class)
));
}
if ($metadata->hasField($property)) {
$typeOfField = $metadata->getTypeOfField($property);
$nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
switch ($typeOfField) {
case 'date':
case 'datetime':
case 'datetimetz':
case 'time':
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime'));
case 'array':
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
case 'simple_array':
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)));
case 'json_array':
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
default:
return array(new Type($this->getPhpType($typeOfField), $nullable));
}
}
}
/**
* Gets the corresponding built-in PHP type.
*
* @param string $doctrineType
*
* @return string
*/
private function getPhpType($doctrineType)
{
switch ($doctrineType) {
case 'smallint':
// No break
case 'bigint':
// No break
case 'integer':
return Type::BUILTIN_TYPE_INT;
case 'decimal':
return Type::BUILTIN_TYPE_FLOAT;
case 'text':
// No break
case 'guid':
return Type::BUILTIN_TYPE_STRING;
case 'boolean':
return Type::BUILTIN_TYPE_BOOL;
case 'blob':
// No break
case 'binary':
return Type::BUILTIN_TYPE_RESOURCE;
default:
return $doctrineType;
}
}
}

View File

@ -0,0 +1,84 @@
<?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\Bridge\Doctrine\PropertyInfo\Tests;
use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var DoctrineExtractor
*/
private $extractor;
public function setUp()
{
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'), true);
$entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config);
$this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory());
}
public function testGetProperties()
{
$this->assertEquals(
array(
'id',
'guid',
'time',
'json',
'simpleArray',
'bool',
'binary',
'foo',
'bar',
),
$this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy')
);
}
/**
* @dataProvider typesProvider
*/
public function testExtract($property, array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array()));
}
public function typesProvider()
{
return array(
array('id', array(new Type(Type::BUILTIN_TYPE_INT))),
array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))),
array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))),
array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))),
array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))),
array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))),
array('bar', array(new Type(
Type::BUILTIN_TYPE_OBJECT,
false,
'Doctrine\Common\Collections\Collection',
true,
new Type(Type::BUILTIN_TYPE_INT),
new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation')
))),
array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))),
array('notMapped', null),
);
}
}

View File

@ -0,0 +1,74 @@
<?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\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\Id;
use Doctrine\ORM\Mapping\ManyToMany;
use Doctrine\ORM\Mapping\ManyToOne;
/**
* @Entity
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineDummy
{
/**
* @Id
* @Column(type="smallint")
*/
public $id;
/**
* @ManyToOne(targetEntity="DoctrineRelation")
*/
public $foo;
/**
* @ManyToMany(targetEntity="DoctrineRelation")
*/
public $bar;
/**
* @Column(type="guid")
*/
protected $guid;
/**
* @Column(type="time")
*/
private $time;
/**
* @Column(type="json_array")
*/
private $json;
/**
* @Column(type="simple_array")
*/
private $simpleArray;
/**
* @Column(type="boolean")
*/
private $bool;
/**
* @Column(type="binary")
*/
private $binary;
public $notMapped;
}

View File

@ -0,0 +1,29 @@
<?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\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
use Doctrine\ORM\Mapping\Column;
use Doctrine\ORM\Mapping\Id;
/**
* @Entity
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineRelation
{
/**
* @Id
* @Column(type="smallint")
*/
public $id;
}

View File

@ -26,6 +26,7 @@
"symfony/form": "~2.8|~3.0.0",
"symfony/http-kernel": "~2.2|~3.0.0",
"symfony/property-access": "~2.3|~3.0.0",
"symfony/property-info": "~2.8|3.0",
"symfony/security": "~2.2|~3.0.0",
"symfony/expression-language": "~2.2|~3.0.0",
"symfony/validator": "~2.5,>=2.5.5|~3.0.0",
@ -37,6 +38,7 @@
"suggest": {
"symfony/form": "",
"symfony/validator": "",
"symfony/property-info": "",
"doctrine/data-fixtures": "",
"doctrine/dbal": "",
"doctrine/orm": ""

View File

@ -0,0 +1,392 @@
<?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 phpDocumentor\Reflection\ClassReflector;
use phpDocumentor\Reflection\DocBlock;
use phpDocumentor\Reflection\FileReflector;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
/**
* Extracts data using a PHPDoc parser.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PhpDocExtractor implements PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface
{
const PROPERTY = 0;
const ACCESSOR = 1;
const MUTATOR = 2;
/**
* @var FileReflector[]
*/
private $fileReflectors = array();
/**
* @var DocBlock[]
*/
private $docBlocks = array();
/**
* {@inheritdoc}
*/
public function getShortDescription($class, $property, array $context = array())
{
list($docBlock) = $this->getDocBlock($class, $property);
if (!$docBlock) {
return;
}
$shortDescription = $docBlock->getShortDescription();
if ($shortDescription) {
return $shortDescription;
}
foreach ($docBlock->getTagsByName('var') as $var) {
$parsedDescription = $var->getParsedDescription();
if (isset($parsedDescription[0]) && '' !== $parsedDescription[0]) {
return $parsedDescription[0];
}
}
}
/**
* {@inheritdoc}
*/
public function getLongDescription($class, $property, array $context = array())
{
list($docBlock) = $this->getDocBlock($class, $property);
if (!$docBlock) {
return;
}
$contents = $docBlock->getLongDescription()->getContents();
return '' === $contents ? null : $contents;
}
/**
* {@inheritdoc}
*/
public function getTypes($class, $property, array $context = array())
{
list($docBlock, $source, $prefix) = $this->getDocBlock($class, $property);
if (!$docBlock) {
return;
}
switch ($source) {
case self::PROPERTY:
$tag = 'var';
break;
case self::ACCESSOR:
$tag = 'return';
break;
case self::MUTATOR:
$tag = 'param';
break;
}
$types = array();
foreach ($docBlock->getTagsByName($tag) as $tag) {
$varTypes = $tag->getTypes();
// If null is present, all types are nullable
$nullKey = array_search(Type::BUILTIN_TYPE_NULL, $varTypes);
$nullable = false !== $nullKey;
// Remove the null type from the type if other types are defined
if ($nullable && count($varTypes) > 1) {
unset($varTypes[$nullKey]);
}
foreach ($varTypes as $varType) {
$type = $this->createType($varType, $nullable);
if (null !== $type) {
$types[] = $type;
}
}
}
if (!isset($types[0])) {
return;
}
if (!in_array($prefix, ReflectionExtractor::$arrayMutatorPrefixes)) {
return $types;
}
return array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), $types[0]));
}
/**
* Gets the FileReflector associated with the class.
*
* @param \ReflectionClass $reflectionClass
*
* @return FileReflector|null
*/
private function getFileReflector(\ReflectionClass $reflectionClass)
{
if (!($fileName = $reflectionClass->getFileName()) || 'hh' === pathinfo($fileName, PATHINFO_EXTENSION)) {
return;
}
if (isset($this->fileReflectors[$fileName])) {
return $this->fileReflectors[$fileName];
}
$this->fileReflectors[$fileName] = new FileReflector($fileName);
$this->fileReflectors[$fileName]->process();
return $this->fileReflectors[$fileName];
}
/**
* Gets the DocBlock for this property.
*
* @param string $class
* @param string $property
*
* @return array
*/
private function getDocBlock($class, $property)
{
$propertyHash = sprintf('%s::%s', $class, $property);
if (isset($this->docBlocks[$propertyHash])) {
return $this->docBlocks[$propertyHash];
}
$ucFirstProperty = ucfirst($property);
switch (true) {
case $docBlock = $this->getDocBlockFromProperty($class, $property):
$data = array($docBlock, self::PROPERTY, null);
break;
case list($docBlock) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::ACCESSOR):
$data = array($docBlock, self::ACCESSOR, null);
break;
case list($docBlock, $prefix) = $this->getDocBlockFromMethod($class, $ucFirstProperty, self::MUTATOR):
$data = array($docBlock, self::MUTATOR, $prefix);
break;
default:
$data = array(null, null);
}
return $this->docBlocks[$propertyHash] = $data;
}
/**
* Gets the DocBlock from a property.
*
* @param string $class
* @param string $property
*
* @return DocBlock|null
*/
private function getDocBlockFromProperty($class, $property)
{
// Use a ReflectionProperty instead of $class to get the parent class if applicable
try {
$reflectionProperty = new \ReflectionProperty($class, $property);
} catch (\ReflectionException $reflectionException) {
return;
}
$reflectionCLass = $reflectionProperty->getDeclaringClass();
$fileReflector = $this->getFileReflector($reflectionCLass);
if (!$fileReflector) {
return;
}
foreach ($fileReflector->getClasses() as $classReflector) {
$className = $this->getClassName($classReflector);
if ($className === $reflectionCLass->name) {
foreach ($classReflector->getProperties() as $propertyReflector) {
// strip the $ prefix
$propertyName = substr($propertyReflector->getName(), 1);
if ($propertyName === $property) {
return $propertyReflector->getDocBlock();
}
}
}
}
}
/**
* Gets DocBlock from accessor or mutator method.
*
* @param string $class
* @param string $ucFirstProperty
* @param int $type
*
* @return DocBlock|null
*/
private function getDocBlockFromMethod($class, $ucFirstProperty, $type)
{
$prefixes = $type === self::ACCESSOR ? ReflectionExtractor::$accessorPrefixes : ReflectionExtractor::$mutatorPrefixes;
foreach ($prefixes as $prefix) {
$methodName = $prefix.$ucFirstProperty;
try {
$reflectionMethod = new \ReflectionMethod($class, $methodName);
if (
(self::ACCESSOR === $type && 0 === $reflectionMethod->getNumberOfRequiredParameters()) ||
(self::MUTATOR === $type && $reflectionMethod->getNumberOfParameters() >= 1)
) {
break;
}
} catch (\ReflectionException $reflectionException) {
// Try the next prefix if the method doesn't exist
}
}
if (!isset($reflectionMethod)) {
return;
}
$reflectionClass = $reflectionMethod->getDeclaringClass();
$fileReflector = $this->getFileReflector($reflectionClass);
if (!$fileReflector) {
return;
}
foreach ($fileReflector->getClasses() as $classReflector) {
$className = $this->getClassName($classReflector);
if ($className === $reflectionClass->name) {
if ($methodReflector = $classReflector->getMethod($methodName)) {
return array($methodReflector->getDocBlock(), $prefix);
}
}
}
}
/**
* Gets the normalized class name (without trailing backslash).
*
* @param ClassReflector $classReflector
*
* @return string
*/
private function getClassName(ClassReflector $classReflector)
{
$className = $classReflector->getName();
if ('\\' === $className[0]) {
return substr($className, 1);
}
return $className;
}
/**
* Creates a {@see Type} from a PHPDoc type.
*
* @param string $docType
* @param bool $nullable
*
* @return Type|null
*/
private function createType($docType, $nullable)
{
// Cannot guess
if (!$docType || 'mixed' === $docType) {
return;
}
if ($collection = '[]' === substr($docType, -2)) {
$docType = substr($docType, 0, -2);
}
$docType = $this->normalizeType($docType);
list($phpType, $class) = $this->getPhpTypeAndClass($docType);
$array = 'array' === $docType;
if ($collection || $array) {
if ($array || 'mixed' === $docType) {
$collectionKeyType = null;
$collectionValueType = null;
} else {
$collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
$collectionValueType = new Type($phpType, false, $class);
}
return new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, $collectionKeyType, $collectionValueType);
}
return new Type($phpType, $nullable, $class);
}
/**
* Normalizes the type.
*
* @param string $docType
*
* @return string
*/
private function normalizeType($docType)
{
switch ($docType) {
case 'integer':
return 'int';
case 'boolean':
return 'bool';
// real is not part of the PHPDoc standard, so we ignore it
case 'double':
return 'float';
case 'callback':
return 'callable';
case 'void':
return 'null';
default:
return $docType;
}
}
/**
* Gets an array containing the PHP type and the class.
*
* @param string $docType
*
* @return array
*/
private function getPhpTypeAndClass($docType)
{
if (in_array($docType, Type::$builtinTypes)) {
return array($docType, null);
}
return array('object', substr($docType, 1));
}
}

View File

@ -0,0 +1,341 @@
<?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];
}
}
}

View File

@ -0,0 +1,58 @@
<?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\PropertyListExtractorInterface;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
/**
* Lists available properties using Symfony Serializer Component metadata.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class SerializerExtractor implements PropertyListExtractorInterface
{
/**
* @var ClassMetadataFactoryInterface
*/
private $classMetadataFactory;
public function __construct(ClassMetadataFactoryInterface $classMetadataFactory)
{
$this->classMetadataFactory = $classMetadataFactory;
}
/**
* {@inheritdoc}
*/
public function getProperties($class, array $context = array())
{
if (!isset($context['serializer_groups']) || !is_array($context['serializer_groups'])) {
return;
}
if (!$this->classMetadataFactory->getMetadataFor($class)) {
return;
}
$properties = array();
$serializerClassMetadata = $this->classMetadataFactory->getMetadataFor($class);
foreach ($serializerClassMetadata->getAttributesMetadata() as $serializerAttributeMetadata) {
if (count(array_intersect($context['serializer_groups'], $serializerAttributeMetadata->getGroups())) > 0) {
$properties[] = $serializerAttributeMetadata->getName();
}
}
return $properties;
}
}

View File

@ -0,0 +1,19 @@
Copyright (c) 2015 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@ -0,0 +1,42 @@
<?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;
/**
* Guesses if the property can be accessed or mutated.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyAccessExtractorInterface
{
/**
* Is the property readable?
*
* @param string $class
* @param string $property
* @param array $context
*
* @return bool|null
*/
public function isReadable($class, $property, array $context = array());
/**
* Is the property writable?
*
* @param string $class
* @param string $property
* @param array $context
*
* @return bool|null
*/
public function isWritable($class, $property, array $context = array());
}

View File

@ -0,0 +1,42 @@
<?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;
/**
* Description extractor Interface.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyDescriptionExtractorInterface
{
/**
* Gets the short description of the property.
*
* @param string $class
* @param string $property
* @param array $context
*
* @return string|null
*/
public function getShortDescription($class, $property, array $context = array());
/**
* Gets the long description of the property.
*
* @param string $class
* @param string $property
* @param array $context
*
* @return string|null
*/
public function getLongDescription($class, $property, array $context = array());
}

View File

@ -0,0 +1,121 @@
<?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;
/**
* Default {@see PropertyInfoExtractorInterface} implementation.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoExtractor implements PropertyInfoExtractorInterface
{
/**
* @var PropertyListExtractorInterface[]
*/
private $listExtractors;
/**
* @var PropertyTypeExtractorInterface[]
*/
private $typeExtractors;
/**
* @var PropertyDescriptionExtractorInterface[]
*/
private $descriptionExtractors;
/**
* @var PropertyAccessExtractorInterface[]
*/
private $accessExtractors;
/**
* @param PropertyListExtractorInterface[] $listExtractors
* @param PropertyTypeExtractorInterface[] $typeExtractors
* @param PropertyDescriptionExtractorInterface[] $descriptionExtractors
* @param PropertyAccessExtractorInterface[] $accessExtractors
*/
public function __construct(array $listExtractors = array(), array $typeExtractors = array(), array $descriptionExtractors = array(), array $accessExtractors = array())
{
$this->listExtractors = $listExtractors;
$this->typeExtractors = $typeExtractors;
$this->descriptionExtractors = $descriptionExtractors;
$this->accessExtractors = $accessExtractors;
}
/**
* {@inheritdoc}
*/
public function getProperties($class, array $context = array())
{
return $this->extract($this->listExtractors, 'getProperties', array($class, $context));
}
/**
* {@inheritdoc}
*/
public function getShortDescription($class, $property, array $context = array())
{
return $this->extract($this->descriptionExtractors, 'getShortDescription', array($class, $property, $context));
}
/**
* {@inheritdoc}
*/
public function getLongDescription($class, $property, array $context = array())
{
return $this->extract($this->descriptionExtractors, 'getLongDescription', array($class, $property, $context));
}
/**
* {@inheritdoc}
*/
public function getTypes($class, $property, array $context = array())
{
return $this->extract($this->typeExtractors, 'getTypes', array($class, $property, $context));
}
/**
* {@inheritdoc}
*/
public function isReadable($class, $property, array $context = array())
{
return $this->extract($this->accessExtractors, 'isReadable', array($class, $property, $context));
}
/**
* {@inheritdoc}
*/
public function isWritable($class, $property, array $context = array())
{
return $this->extract($this->accessExtractors, 'isWritable', array($class, $property, $context));
}
/**
* Iterates over registered extractors and return the first value found.
*
* @param array $extractors
* @param string $method
* @param array $arguments
*
* @return mixed
*/
private function extract(array $extractors, $method, array $arguments)
{
foreach ($extractors as $extractor) {
$value = call_user_func_array(array($extractor, $method), $arguments);
if (null !== $value) {
return $value;
}
}
}
}

View File

@ -0,0 +1,23 @@
<?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;
/**
* Gets info about PHP class properties.
*
* A convenient interface inheriting all specific info interfaces.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyInfoExtractorInterface extends PropertyTypeExtractorInterface, PropertyDescriptionExtractorInterface, PropertyAccessExtractorInterface, PropertyListExtractorInterface
{
}

View File

@ -0,0 +1,30 @@
<?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;
/**
* Extracts the list of properties available for the given class.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyListExtractorInterface
{
/**
* Gets the list of properties available for the given class.
*
* @param string $class
* @param array $context
*
* @return string[]|null
*/
public function getProperties($class, array $context = array());
}

View File

@ -0,0 +1,31 @@
<?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;
/**
* Type Extractor Interface.
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
interface PropertyTypeExtractorInterface
{
/**
* Gets types of a property.
*
* @param string $class
* @param string $property
* @param array $context
*
* @return Type[]|null
*/
public function getTypes($class, $property, array $context = array());
}

View File

@ -0,0 +1,14 @@
PropertyInfo Component
======================
PropertyInfo extracts information about PHP class' properties using metadata
of popular sources.
Resources
---------
You can run the unit tests with the following command:
$ cd path/to/Symfony/Component/PropertyInfo/
$ composer install
$ phpunit

View File

@ -0,0 +1,72 @@
<?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\Tests\PhpDocExtractors;
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PhpDocExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var PhpDocExtractor
*/
private $extractor;
public function setUp()
{
$this->extractor = new PhpDocExtractor();
}
/**
* @dataProvider typesProvider
*/
public function testExtract($property, array $type = null, $shortDescription, $longDescription)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
$this->assertSame($shortDescription, $this->extractor->getShortDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
$this->assertSame($longDescription, $this->extractor->getLongDescription('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property));
}
public function typesProvider()
{
return array(
array('foo', null, 'Short description.', 'Long description.'),
array('bar', array(new Type(Type::BUILTIN_TYPE_STRING)), 'This is bar.', null),
array('baz', array(new Type(Type::BUILTIN_TYPE_INT)), 'Should be used.', null),
array('foo2', array(new Type(Type::BUILTIN_TYPE_FLOAT)), null, null),
array('foo3', array(new Type(Type::BUILTIN_TYPE_CALLABLE)), null, null),
array('foo4', array(new Type(Type::BUILTIN_TYPE_NULL)), null, null),
array('foo5', null, null, null),
array(
'files',
array(
new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'SplFileInfo')),
new Type(Type::BUILTIN_TYPE_RESOURCE),
),
null,
null,
),
array('bal', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')), null, null),
array('parent', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), null, null),
array('collection', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null),
array('a', array(new Type(Type::BUILTIN_TYPE_INT)), 'A.', null),
array('b', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy')), 'B.', null),
array('c', array(new Type(Type::BUILTIN_TYPE_BOOL, true)), null, null),
array('d', array(new Type(Type::BUILTIN_TYPE_BOOL)), null, null),
array('e', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_RESOURCE))), null, null),
array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime'))), null, null),
);
}
}

View File

@ -0,0 +1,122 @@
<?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\Tests\Extractor;
use Symfony\Component\PropertyInfo\Extractor\ReflectionExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ReflectionExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var ReflectionExtractor
*/
private $extractor;
public function setUp()
{
$this->extractor = new ReflectionExtractor();
}
public function testGetProperties()
{
$this->assertEquals(
array(
'bal',
'parent',
'collection',
'foo',
'foo2',
'foo3',
'foo4',
'foo5',
'files',
'A',
'B',
'C',
'D',
'E',
'F',
),
$this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy')
);
}
/**
* @dataProvider typesProvider
*/
public function testExtractors($property, array $type = null)
{
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', $property, array()));
}
public function typesProvider()
{
return array(
array('a', null),
array('b', array(new Type(Type::BUILTIN_TYPE_OBJECT, true, 'Symfony\Component\PropertyInfo\Tests\Fixtures\ParentDummy'))),
array('c', array(new Type(Type::BUILTIN_TYPE_BOOL))),
array('d', array(new Type(Type::BUILTIN_TYPE_BOOL))),
array('e', null),
array('f', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_OBJECT, false, 'DateTime')))),
);
}
/**
* @dataProvider php7TypesProvider
*/
public function testExtractPhp7Type($property, array $type = null)
{
if (!method_exists('\ReflectionMethod', 'getReturnType')) {
$this->markTestSkipped('Available only with PHP 7 and superior.');
}
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Component\PropertyInfo\Tests\Fixtures\Php7Dummy', $property, array()));
}
public function php7TypesProvider()
{
return array(
array('foo', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))),
array('bar', array(new Type(Type::BUILTIN_TYPE_INT))),
array('baz', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))),
);
}
public function testIsReadable()
{
$this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'bar', array()));
$this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'baz', array()));
$this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'parent', array()));
$this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'a', array()));
$this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'b', array()));
$this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'c', array()));
$this->assertTrue($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'd', array()));
$this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'e', array()));
$this->assertFalse($this->extractor->isReadable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'f', array()));
}
public function testIsWritable()
{
$this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'bar', array()));
$this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'baz', array()));
$this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'parent', array()));
$this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'a', array()));
$this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'b', array()));
$this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'c', array()));
$this->assertFalse($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'd', array()));
$this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'e', array()));
$this->assertTrue($this->extractor->isWritable('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', 'f', array()));
}
}

View File

@ -0,0 +1,42 @@
<?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\PropertyInfo\Tests\Extractors;
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class SerializerExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var SerializerExtractor
*/
private $extractor;
public function setUp()
{
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$this->extractor = new SerializerExtractor($classMetadataFactory);
}
public function testGetProperties()
{
$this->assertEquals(
array('collection'),
$this->extractor->getProperties('Symfony\Component\PropertyInfo\Tests\Fixtures\Dummy', array('serializer_groups' => array('a')))
);
}
}

View File

@ -0,0 +1,66 @@
<?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\Tests\Fixtures;
use Symfony\Component\Serializer\Annotation\Groups;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Dummy extends ParentDummy
{
/**
* @var string This is bar.
*/
private $bar;
/**
* Should be used.
*
* @var int Should be ignored.
*/
protected $baz;
/**
* @var \DateTime
*/
public $bal;
/**
* @var ParentDummy
*/
public $parent;
/**
* @var \DateTime[]
* @Groups({"a", "b"})
*/
public $collection;
/**
* A.
*
* @return int
*/
public function getA()
{
}
/**
* B.
*
* @param ParentDummy|null $parent
*/
public function setB(ParentDummy $parent = null)
{
}
}

View File

@ -0,0 +1,72 @@
<?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\Tests\Fixtures;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DummyExtractor implements PropertyListExtractorInterface, PropertyDescriptionExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
{
/**
* {@inheritdoc}
*/
public function getShortDescription($class, $property, array $context = array())
{
return 'short';
}
/**
* {@inheritdoc}
*/
public function getLongDescription($class, $property, array $context = array())
{
return 'long';
}
/**
* {@inheritdoc}
*/
public function getTypes($class, $property, array $context = array())
{
return array(new Type(Type::BUILTIN_TYPE_INT));
}
/**
* {@inheritdoc}
*/
public function isReadable($class, $property, array $context = array())
{
return true;
}
/**
* {@inheritdoc}
*/
public function isWritable($class, $property, array $context = array())
{
return true;
}
/**
* {@inheritdoc}
*/
public function getProperties($class, array $context = array())
{
return array('a', 'b');
}
}

View File

@ -0,0 +1,78 @@
<?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\Tests\Fixtures;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class ParentDummy
{
/**
* Short description.
*
* Long description.
*/
public $foo;
/**
* @var float
*/
public $foo2;
/**
* @var callback
*/
public $foo3;
/**
* @var void
*/
public $foo4;
/**
* @var mixed
*/
public $foo5;
/**
* @var \SplFileInfo[]|resource
*/
public $files;
/**
* @return bool|null
*/
public function isC()
{
}
/**
* @return bool
*/
public function canD()
{
}
/**
* @param resource $e
*/
public function addE($e)
{
}
/**
* @param \DateTime $f
*/
public function removeF(\DateTime $f)
{
}
}

View File

@ -0,0 +1,30 @@
<?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\Tests\Fixtures;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Php7Dummy
{
public function getFoo(): array
{
}
public function setBar(int $bar)
{
}
public function addBaz(string $baz)
{
}
}

View File

@ -0,0 +1,71 @@
<?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\PropertyInfo\Tests;
use Symfony\Component\PropertyInfo\PropertyInfoExtractor;
use Symfony\Component\PropertyInfo\Tests\Fixtures\DummyExtractor;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class PropertyInfoExtractorTest extends \PHPUnit_Framework_TestCase
{
/**
* @var PropertyInfoExtractor
*/
private $propertyInfo;
public function setUp()
{
$extractors = array(new DummyExtractor());
$this->propertyInfo = new PropertyInfoExtractor($extractors, $extractors, $extractors, $extractors);
}
public function testInstanceOf()
{
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyInfoExtractorInterface', $this->propertyInfo);
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface', $this->propertyInfo);
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyDescriptionExtractorInterface', $this->propertyInfo);
$this->assertInstanceOf('Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface', $this->propertyInfo);
}
public function testGetShortDescription()
{
$this->assertSame('short', $this->propertyInfo->getShortDescription('Foo', 'bar', array()));
}
public function testGetLongDescription()
{
$this->assertSame('long', $this->propertyInfo->getLongDescription('Foo', 'bar', array()));
}
public function testGetTypes()
{
$this->assertEquals(array(new Type(Type::BUILTIN_TYPE_INT)), $this->propertyInfo->getTypes('Foo', 'bar', array()));
}
public function testIsReadable()
{
$this->assertTrue($this->propertyInfo->isReadable('Foo', 'bar', array()));
}
public function testIsWritable()
{
$this->assertTrue($this->propertyInfo->isWritable('Foo', 'bar', array()));
}
public function testGetProperties()
{
$this->assertEquals(array('a', 'b'), $this->propertyInfo->getProperties('Foo'));
}
}

View File

@ -0,0 +1,47 @@
<?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\PropertyInfo\Tests;
use Symfony\Component\PropertyInfo\Type;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class TypeTest extends \PHPUnit_Framework_TestCase
{
public function testConstruct()
{
$type = new Type('object', true, 'ArrayObject', true, new Type('int'), new Type('string'));
$this->assertEquals(Type::BUILTIN_TYPE_OBJECT, $type->getBuiltinType());
$this->assertTrue($type->isNullable());
$this->assertEquals('ArrayObject', $type->getClassName());
$this->assertTrue($type->isCollection());
$collectionKeyType = $type->getCollectionKeyType();
$this->assertInstanceOf('Symfony\Component\PropertyInfo\Type', $collectionKeyType);
$this->assertEquals(Type::BUILTIN_TYPE_INT, $collectionKeyType->getBuiltinType());
$collectionValueType = $type->getCollectionValueType();
$this->assertInstanceOf('Symfony\Component\PropertyInfo\Type', $collectionValueType);
$this->assertEquals(Type::BUILTIN_TYPE_STRING, $collectionValueType->getBuiltinType());
}
/**
* @expectedException \InvalidArgumentException
* @expectedExceptionMessage "foo" is not a valid PHP type.
*/
public function testInvalidType()
{
new Type('foo');
}
}

View File

@ -0,0 +1,169 @@
<?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;
/**
* Type value object (immutable).
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class Type
{
const BUILTIN_TYPE_INT = 'int';
const BUILTIN_TYPE_FLOAT = 'float';
const BUILTIN_TYPE_STRING = 'string';
const BUILTIN_TYPE_BOOL = 'bool';
const BUILTIN_TYPE_RESOURCE = 'resource';
const BUILTIN_TYPE_OBJECT = 'object';
const BUILTIN_TYPE_ARRAY = 'array';
const BUILTIN_TYPE_NULL = 'null';
const BUILTIN_TYPE_CALLABLE = 'callable';
/**
* List of PHP builtin types.
*
* @var string[]
*/
public static $builtinTypes = array(
self::BUILTIN_TYPE_INT,
self::BUILTIN_TYPE_FLOAT,
self::BUILTIN_TYPE_STRING,
self::BUILTIN_TYPE_BOOL,
self::BUILTIN_TYPE_RESOURCE,
self::BUILTIN_TYPE_OBJECT,
self::BUILTIN_TYPE_ARRAY,
self::BUILTIN_TYPE_CALLABLE,
self::BUILTIN_TYPE_NULL,
);
/**
* @var string
*/
private $builtinType;
/**
* @var bool
*/
private $nullable;
/**
* @var string|null
*/
private $class;
/**
* @var bool
*/
private $collection;
/**
* @var Type|null
*/
private $collectionKeyType;
/**
* @var Type|null
*/
private $collectionValueType;
/**
* @param string $builtinType
* @param bool $nullable
* @param string|null $class
* @param bool $collection
* @param Type|null $collectionKeyType
* @param Type|null $collectionValueType
*
* @throws \InvalidArgumentException
*/
public function __construct($builtinType, $nullable = false, $class = null, $collection = false, Type $collectionKeyType = null, Type $collectionValueType = null)
{
if (!in_array($builtinType, self::$builtinTypes)) {
throw new \InvalidArgumentException(sprintf('"%s" is not a valid PHP type.', $builtinType));
}
$this->builtinType = $builtinType;
$this->nullable = $nullable;
$this->class = $class;
$this->collection = $collection;
$this->collectionKeyType = $collectionKeyType;
$this->collectionValueType = $collectionValueType;
}
/**
* Gets built-in type.
*
* Can be bool, int, float, string, array, object, resource, null or callback.
*
* @return string
*/
public function getBuiltinType()
{
return $this->builtinType;
}
/**
* Allows null value?
*
* @return bool
*/
public function isNullable()
{
return $this->nullable;
}
/**
* Gets the class name.
*
* Only applicable if the built-in type is object.
*
* @return string|null
*/
public function getClassName()
{
return $this->class;
}
/**
* Is collection?
*
* @return bool
*/
public function isCollection()
{
return $this->collection;
}
/**
* Gets collection key type.
*
* Only applicable for a collection type.
*
* @return Type|null
*/
public function getCollectionKeyType()
{
return $this->collectionKeyType;
}
/**
* Gets collection value type.
*
* Only applicable for a collection type.
*
* @return Type|null
*/
public function getCollectionValueType()
{
return $this->collectionValueType;
}
}

View File

@ -0,0 +1,51 @@
{
"name": "symfony/property-info",
"type": "library",
"description": "Symfony Property Info Component",
"keywords": [
"property",
"type",
"PHPDoc",
"symfony",
"validator",
"doctrine"
],
"homepage": "https://symfony.com",
"license": "MIT",
"authors": [
{
"name": "Kévin Dunglas",
"email": "dunglas@gmail.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"require": {
"php": ">=5.3.9"
},
"require-dev": {
"symfony/phpunit-bridge": "~2.7",
"symfony/serializer": "~2.7",
"phpdocumentor/reflection": "^1.0.7",
"doctrine/annotations": "~1.0"
},
"conflict": {
"phpdocumentor/reflection": "<1.0.7"
},
"suggest": {
"symfony/doctrine-bridge": "To use Doctrine metadata",
"phpdocumentor/reflection": "To use the PHPDoc",
"symfony/serializer": "To use Serializer metadata"
},
"autoload": {
"psr-4": { "Symfony\\Component\\PropertyInfo\\": "" }
},
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "2.8-dev"
}
}
}

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
<php>
<ini name="error_reporting" value="-1" />
</php>
<testsuites>
<testsuite name="Symfony Property Info Component Test Suite">
<directory>./Tests/</directory>
</testsuite>
</testsuites>
<filter>
<whitelist>
<directory>./</directory>
<exclude>
<directory>./Resources</directory>
<directory>./Tests</directory>
<directory>./vendor</directory>
</exclude>
</whitelist>
</filter>
</phpunit>