Skip to content

Commit 59ee12c

Browse files
committed
feature #15858 [PropertyInfo] Import the component (dunglas)
This PR was squashed before being merged into the 2.8 branch (closes #15858). Discussion ---------- [PropertyInfo] Import the component | Q | A | ------------- | --- | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | n/a | License | MIT | Doc PR | symfony/symfony-docs#5717 As discussed with @fabpot (see #14844), this PR moves [dunglas/php-property-info](https://github.com/dunglas/php-property-info) under the Symfony umbrella. Rationale behind this new component (extracted from README.md): PHP doesn't support explicit type definition. This is annoying, especially when doing meta programming. Various libraries including but not limited to Doctrine ORM and the Symfony Validator provide their own type managing system. This library extracts various information including the type and documentation from PHP class property from metadata of popular sources: * Setter method with type hint * PHPDoc DocBlock * Doctrine ORM mapping (annotation, XML, YML or custom format) * PHP 7 scalar typehint and return type * Serializer metadata **Usage:** ```php <?php // Use Composer autoload require 'vendor/autoload.php'; use Doctrine\ORM\EntityManager; use Doctrine\ORM\Tools\Setup; use Doctrine\ORM\Mapping\Column; use Doctrine\ORM\Mapping\Entity; use Doctrine\ORM\Mapping\Id; use Symfony\Component\PropertyInfo\Extractors\DoctrineExtractor; use Symfony\Component\PropertyInfo\Extractors\PhpDocExtractor; use Symfony\Component\PropertyInfo\Extractors\ReflectionExtractor; use Symfony\Component\PropertyInfo\PropertyInfo; /** * @entity */ class MyTestClass { /** * @id * @column(type="integer") */ public $id; /** * This is a date (short description). * * With a long description. * * @var \DateTime */ public $foo; private $bar; public function setBar(\SplFileInfo $bar) { $this->bar = $bar; } } // Doctrine initialization (necessary only to use the Doctrine Extractor) $config = Setup::createAnnotationMetadataConfiguration([__DIR__], true); $entityManager = EntityManager::create([ 'driver' => 'pdo_sqlite', // ... ], $config); $doctrineExtractor = new DoctrineExtractor($entityManager->getMetadataFactory()); $phpDocExtractor = new PhpDocExtractor(); $reflectionExtractor = new ReflectionExtractor(); $propertyInfo = new PropertyInfo( array($reflectionExtractor), array($doctrineExtractor, $phpDocExtractor, $reflectionExtractor), array($phpDocExtractor), array($reflectionExtractor) ); var_dump($propertyInfo->getProperties('MyTestClass')); var_dump($propertyInfo->getTypes('MyTestClass', 'foo')); var_dump($propertyInfo->getTypes('MyTestClass', 'id')); var_dump($propertyInfo->getTypes('MyTestClass', 'bar')); var_dump($propertyInfo->isReadable('MyTestClass', 'id')); var_dump($propertyInfo->isReadable('MyTestClass', 'bar')); var_dump($propertyInfo->isWritable('MyTestClass', 'foo')); var_dump($propertyInfo->isWritable('MyTestClass', 'bar')); var_dump($propertyInfo->getShortDescription('MyTestClass', 'foo')); var_dump($propertyInfo->getLongDescription('MyTestClass', 'foo')); ``` Output: ``` array(3) { [0] => string(2) "id" [1] => string(3) "foo" [2] => string(3) "Bar" } array(1) { [0] => class Symfony\Component\PropertyInfo\Type#36 (6) { private $builtinType => string(6) "object" private $nullable => bool(false) private $class => string(8) "DateTime" private $collection => bool(false) private $collectionKeyType => NULL private $collectionValueType => NULL } } array(1) { [0] => class Symfony\Component\PropertyInfo\Type#36 (6) { private $builtinType => string(3) "int" private $nullable => bool(false) private $class => NULL private $collection => bool(false) private $collectionKeyType => NULL private $collectionValueType => NULL } } array(1) { [0] => class Symfony\Component\PropertyInfo\Type#245 (6) { private $builtinType => string(6) "object" private $nullable => bool(false) private $class => string(11) "SplFileInfo" private $collection => bool(false) private $collectionKeyType => NULL private $collectionValueType => NULL } } bool(true) bool(false) bool(true) bool(true) string(35) "This is a date (short description)." string(24) "With a long description." ``` Commits ------- f1eb185 [PropertyInfo] Import the component
2 parents 8209754 + f1eb185 commit 59ee12c

29 files changed

+2306
-1
lines changed

composer.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"symfony/options-resolver": "self.version",
4949
"symfony/process": "self.version",
5050
"symfony/property-access": "self.version",
51+
"symfony/property-info": "self.version",
5152
"symfony/proxy-manager-bridge": "self.version",
5253
"symfony/routing": "self.version",
5354
"symfony/security": "self.version",
@@ -76,7 +77,11 @@
7677
"monolog/monolog": "~1.11",
7778
"ircmaxell/password-compat": "~1.0",
7879
"ocramius/proxy-manager": "~0.4|~1.0",
79-
"egulias/email-validator": "~1.2"
80+
"egulias/email-validator": "~1.2",
81+
"phpdocumentor/reflection": "^1.0.7"
82+
},
83+
"conflict": {
84+
"phpdocumentor/reflection": "<1.0.7"
8085
},
8186
"autoload": {
8287
"psr-4": {
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\PropertyInfo;
13+
14+
use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
15+
use Doctrine\Common\Persistence\Mapping\MappingException;
16+
use Doctrine\ORM\Mapping\ClassMetadataInfo;
17+
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
18+
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
19+
use Symfony\Component\PropertyInfo\Type;
20+
21+
/**
22+
* Extracts data using Doctrine ORM and ODM metadata.
23+
*
24+
* @author Kévin Dunglas <dunglas@gmail.com>
25+
*/
26+
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface
27+
{
28+
/**
29+
* @var ClassMetadataFactory
30+
*/
31+
private $classMetadataFactory;
32+
33+
public function __construct(ClassMetadataFactory $classMetadataFactory)
34+
{
35+
$this->classMetadataFactory = $classMetadataFactory;
36+
}
37+
38+
/**
39+
* {@inheritdoc}
40+
*/
41+
public function getProperties($class, array $context = array())
42+
{
43+
try {
44+
$metadata = $this->classMetadataFactory->getMetadataFor($class);
45+
} catch (MappingException $exception) {
46+
return;
47+
}
48+
49+
return array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
50+
}
51+
52+
/**
53+
* {@inheritdoc}
54+
*/
55+
public function getTypes($class, $property, array $context = array())
56+
{
57+
try {
58+
$metadata = $this->classMetadataFactory->getMetadataFor($class);
59+
} catch (MappingException $exception) {
60+
return;
61+
}
62+
63+
if ($metadata->hasAssociation($property)) {
64+
$class = $metadata->getAssociationTargetClass($property);
65+
66+
if ($metadata->isSingleValuedAssociation($property)) {
67+
if ($metadata instanceof ClassMetadataInfo) {
68+
$nullable = isset($metadata->discriminatorColumn['nullable']) ? $metadata->discriminatorColumn['nullable'] : false;
69+
} else {
70+
$nullable = false;
71+
}
72+
73+
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, $class));
74+
}
75+
76+
return array(new Type(
77+
Type::BUILTIN_TYPE_OBJECT,
78+
false,
79+
'Doctrine\Common\Collections\Collection',
80+
true,
81+
new Type(Type::BUILTIN_TYPE_INT),
82+
new Type(Type::BUILTIN_TYPE_OBJECT, false, $class)
83+
));
84+
}
85+
86+
if ($metadata->hasField($property)) {
87+
$typeOfField = $metadata->getTypeOfField($property);
88+
$nullable = $metadata instanceof ClassMetadataInfo && $metadata->isNullable($property);
89+
90+
switch ($typeOfField) {
91+
case 'date':
92+
case 'datetime':
93+
case 'datetimetz':
94+
case 'time':
95+
return array(new Type(Type::BUILTIN_TYPE_OBJECT, $nullable, 'DateTime'));
96+
97+
case 'array':
98+
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
99+
100+
case 'simple_array':
101+
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)));
102+
103+
case 'json_array':
104+
return array(new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true));
105+
106+
default:
107+
return array(new Type($this->getPhpType($typeOfField), $nullable));
108+
}
109+
}
110+
}
111+
112+
/**
113+
* Gets the corresponding built-in PHP type.
114+
*
115+
* @param string $doctrineType
116+
*
117+
* @return string
118+
*/
119+
private function getPhpType($doctrineType)
120+
{
121+
switch ($doctrineType) {
122+
case 'smallint':
123+
// No break
124+
case 'bigint':
125+
// No break
126+
case 'integer':
127+
return Type::BUILTIN_TYPE_INT;
128+
129+
case 'decimal':
130+
return Type::BUILTIN_TYPE_FLOAT;
131+
132+
case 'text':
133+
// No break
134+
case 'guid':
135+
return Type::BUILTIN_TYPE_STRING;
136+
137+
case 'boolean':
138+
return Type::BUILTIN_TYPE_BOOL;
139+
140+
case 'blob':
141+
// No break
142+
case 'binary':
143+
return Type::BUILTIN_TYPE_RESOURCE;
144+
145+
default:
146+
return $doctrineType;
147+
}
148+
}
149+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\PropertyInfo\Tests;
13+
14+
use Doctrine\ORM\EntityManager;
15+
use Doctrine\ORM\Tools\Setup;
16+
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
17+
use Symfony\Component\PropertyInfo\Type;
18+
19+
/**
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
*/
22+
class DoctrineExtractorTest extends \PHPUnit_Framework_TestCase
23+
{
24+
/**
25+
* @var DoctrineExtractor
26+
*/
27+
private $extractor;
28+
29+
public function setUp()
30+
{
31+
$config = Setup::createAnnotationMetadataConfiguration(array(__DIR__.DIRECTORY_SEPARATOR.'Fixtures'), true);
32+
$entityManager = EntityManager::create(array('driver' => 'pdo_sqlite'), $config);
33+
34+
$this->extractor = new DoctrineExtractor($entityManager->getMetadataFactory());
35+
}
36+
37+
public function testGetProperties()
38+
{
39+
$this->assertEquals(
40+
array(
41+
'id',
42+
'guid',
43+
'time',
44+
'json',
45+
'simpleArray',
46+
'bool',
47+
'binary',
48+
'foo',
49+
'bar',
50+
),
51+
$this->extractor->getProperties('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy')
52+
);
53+
}
54+
55+
/**
56+
* @dataProvider typesProvider
57+
*/
58+
public function testExtract($property, array $type = null)
59+
{
60+
$this->assertEquals($type, $this->extractor->getTypes('Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineDummy', $property, array()));
61+
}
62+
63+
public function typesProvider()
64+
{
65+
return array(
66+
array('id', array(new Type(Type::BUILTIN_TYPE_INT))),
67+
array('guid', array(new Type(Type::BUILTIN_TYPE_STRING))),
68+
array('bool', array(new Type(Type::BUILTIN_TYPE_BOOL))),
69+
array('binary', array(new Type(Type::BUILTIN_TYPE_RESOURCE))),
70+
array('json', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true))),
71+
array('foo', array(new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation'))),
72+
array('bar', array(new Type(
73+
Type::BUILTIN_TYPE_OBJECT,
74+
false,
75+
'Doctrine\Common\Collections\Collection',
76+
true,
77+
new Type(Type::BUILTIN_TYPE_INT),
78+
new Type(Type::BUILTIN_TYPE_OBJECT, false, 'Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineRelation')
79+
))),
80+
array('simpleArray', array(new Type(Type::BUILTIN_TYPE_ARRAY, false, null, true, new Type(Type::BUILTIN_TYPE_INT), new Type(Type::BUILTIN_TYPE_STRING)))),
81+
array('notMapped', null),
82+
);
83+
}
84+
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
13+
14+
use Doctrine\ORM\Mapping\Column;
15+
use Doctrine\ORM\Mapping\Entity;
16+
use Doctrine\ORM\Mapping\Id;
17+
use Doctrine\ORM\Mapping\ManyToMany;
18+
use Doctrine\ORM\Mapping\ManyToOne;
19+
20+
/**
21+
* @Entity
22+
*
23+
* @author Kévin Dunglas <dunglas@gmail.com>
24+
*/
25+
class DoctrineDummy
26+
{
27+
/**
28+
* @Id
29+
* @Column(type="smallint")
30+
*/
31+
public $id;
32+
33+
/**
34+
* @ManyToOne(targetEntity="DoctrineRelation")
35+
*/
36+
public $foo;
37+
38+
/**
39+
* @ManyToMany(targetEntity="DoctrineRelation")
40+
*/
41+
public $bar;
42+
43+
/**
44+
* @Column(type="guid")
45+
*/
46+
protected $guid;
47+
48+
/**
49+
* @Column(type="time")
50+
*/
51+
private $time;
52+
53+
/**
54+
* @Column(type="json_array")
55+
*/
56+
private $json;
57+
58+
/**
59+
* @Column(type="simple_array")
60+
*/
61+
private $simpleArray;
62+
63+
/**
64+
* @Column(type="boolean")
65+
*/
66+
private $bool;
67+
68+
/**
69+
* @Column(type="binary")
70+
*/
71+
private $binary;
72+
73+
public $notMapped;
74+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures;
13+
14+
use Doctrine\ORM\Mapping\Column;
15+
use Doctrine\ORM\Mapping\Id;
16+
17+
/**
18+
* @Entity
19+
*
20+
* @author Kévin Dunglas <dunglas@gmail.com>
21+
*/
22+
class DoctrineRelation
23+
{
24+
/**
25+
* @Id
26+
* @Column(type="smallint")
27+
*/
28+
public $id;
29+
}

src/Symfony/Bridge/Doctrine/composer.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
"symfony/form": "~2.8|~3.0.0",
2727
"symfony/http-kernel": "~2.2|~3.0.0",
2828
"symfony/property-access": "~2.3|~3.0.0",
29+
"symfony/property-info": "~2.8|3.0",
2930
"symfony/security": "~2.2|~3.0.0",
3031
"symfony/expression-language": "~2.2|~3.0.0",
3132
"symfony/validator": "~2.5,>=2.5.5|~3.0.0",
@@ -37,6 +38,7 @@
3738
"suggest": {
3839
"symfony/form": "",
3940
"symfony/validator": "",
41+
"symfony/property-info": "",
4042
"doctrine/data-fixtures": "",
4143
"doctrine/dbal": "",
4244
"doctrine/orm": ""

0 commit comments

Comments
 (0)