feature #31486 [Doctrine][PropertyInfo] Detect if the ID is writeable (dunglas)

This PR was merged into the 4.3 branch.

Discussion
----------

[Doctrine][PropertyInfo] Detect if the ID is writeable

| Q             | A
| ------------- | ---
| Branch?       | 4.3
| Bug fix?      | yes
| New feature?  | yes <!-- please update src/**/CHANGELOG.md files -->
| BC breaks?    | no     <!-- see https://symfony.com/bc -->
| Deprecations? | yes/no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Tests pass?   | yes    <!-- please add some, will be required by reviewers -->
| Fixed tickets | n/a
| License       | MIT
| Doc PR        | n/a

Companion of #31481. Allows to detect that ids with a generated value aren't writable (because the DBMS will generate the ID by itself). It could be considered as a bug fix or as a new feature. I prefer to not merge in in 3.4. However, it becomes necessary for autovalidation to work with such entities, so it should be in 4.3:

```php
/**
 * @Entity
 */
class Foo
{
    /**
     * @Id
     * @GeneratedValue(strategy="AUTO")
     * @Column(type="integer")
     */
    public $id;
}
```

Commits
-------

4598235192 [Doctrine][PropertyInfo] Detect if the ID is writeable
This commit is contained in:
Fabien Potencier 2019-05-13 08:41:44 +02:00
commit 0d196c46a5
3 changed files with 87 additions and 13 deletions

View File

@ -15,8 +15,10 @@ use Doctrine\Common\Persistence\Mapping\ClassMetadataFactory;
use Doctrine\Common\Persistence\Mapping\MappingException;
use Doctrine\DBAL\Types\Type as DBALType;
use Doctrine\ORM\EntityManagerInterface;
use Doctrine\ORM\Mapping\ClassMetadata;
use Doctrine\ORM\Mapping\ClassMetadataInfo;
use Doctrine\ORM\Mapping\MappingException as OrmMappingException;
use Symfony\Component\PropertyInfo\PropertyAccessExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyListExtractorInterface;
use Symfony\Component\PropertyInfo\PropertyTypeExtractorInterface;
use Symfony\Component\PropertyInfo\Type;
@ -26,7 +28,7 @@ use Symfony\Component\PropertyInfo\Type;
*
* @author Kévin Dunglas <dunglas@gmail.com>
*/
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface
class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeExtractorInterface, PropertyAccessExtractorInterface
{
private $entityManager;
private $classMetadataFactory;
@ -51,12 +53,8 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
*/
public function getProperties($class, array $context = [])
{
try {
$metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
} catch (MappingException $exception) {
return;
} catch (OrmMappingException $exception) {
return;
if (null === $metadata = $this->getMetadata($class)) {
return null;
}
$properties = array_merge($metadata->getFieldNames(), $metadata->getAssociationNames());
@ -77,12 +75,8 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
*/
public function getTypes($class, $property, array $context = [])
{
try {
$metadata = $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
} catch (MappingException $exception) {
return;
} catch (OrmMappingException $exception) {
return;
if (null === $metadata = $this->getMetadata($class)) {
return null;
}
if ($metadata->hasAssociation($property)) {
@ -176,6 +170,39 @@ class DoctrineExtractor implements PropertyListExtractorInterface, PropertyTypeE
}
}
/**
* {@inheritdoc}
*/
public function isReadable($class, $property, array $context = [])
{
return null;
}
/**
* {@inheritdoc}
*/
public function isWritable($class, $property, array $context = [])
{
if (
null === ($metadata = $this->getMetadata($class))
|| ClassMetadata::GENERATOR_TYPE_NONE === $metadata->generatorType
|| !\in_array($property, $metadata->getIdentifierFieldNames(), true)
) {
return null;
}
return false;
}
private function getMetadata(string $class): ?ClassMetadata
{
try {
return $this->entityManager ? $this->entityManager->getClassMetadata($class) : $this->classMetadataFactory->getMetadataFor($class);
} catch (MappingException | OrmMappingException $exception) {
return null;
}
}
/**
* Determines whether an association is nullable.
*

View File

@ -16,6 +16,7 @@ use Doctrine\ORM\EntityManager;
use Doctrine\ORM\Tools\Setup;
use PHPUnit\Framework\TestCase;
use Symfony\Bridge\Doctrine\PropertyInfo\DoctrineExtractor;
use Symfony\Bridge\Doctrine\Tests\PropertyInfo\Fixtures\DoctrineGeneratedValue;
use Symfony\Component\PropertyInfo\Type;
/**
@ -223,4 +224,13 @@ class DoctrineExtractorTest extends TestCase
{
$this->assertNull($this->createExtractor($legacy)->getTypes('Not\Exist', 'baz'));
}
public function testGeneratedValueNotWritable()
{
$extractor = $this->createExtractor();
$this->assertFalse($extractor->isWritable(DoctrineGeneratedValue::class, 'id'));
$this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'id'));
$this->assertNull($extractor->isWritable(DoctrineGeneratedValue::class, 'foo'));
$this->assertNull($extractor->isReadable(DoctrineGeneratedValue::class, 'foo'));
}
}

View File

@ -0,0 +1,37 @@
<?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\GeneratedValue;
use Doctrine\ORM\Mapping\Id;
/**
* @author Kévin Dunglas <dunglas@gmail.com>
*
* @Entity
*/
class DoctrineGeneratedValue
{
/**
* @Id
* @GeneratedValue(strategy="AUTO")
* @Column(type="integer")
*/
public $id;
/**
* @Column
*/
public $foo;
}