Skip to content

Commit

Permalink
feature #30704 [PropertyInfo] Add accessor and mutator extractor inte…
Browse files Browse the repository at this point in the history
…rface and implementation on reflection (joelwurtz, Korbeil)

This PR was merged into the 5.1-dev branch.

Discussion
----------

[PropertyInfo] Add accessor and mutator extractor interface and implementation on reflection

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #30248, partially: #22190, #18016, #5013, #9336, #5219,
| License       | MIT
| Doc PR        | TODO

This PR brings accessor / mutator extraction on the PropertyInfo component,

There is no link to existing code, as IMO it should be in another PR as this will add a dependency on property access to the property info component and not sure this is something wanted (although, it will reduce a lot of code base on the property access component as a lot of code seems to be duplicated)

Code is extracted from #30248 also there is some new features (that can be removed if not wanted)

 * Allow extracting private accessor / mutator (will do a new PR that improve private extraction on reflection latter)
 * Allow extracting static accessor / mutators
 * Allow extracting constructor mutators

Current implementation try to be as close as the PropertyAccess implementation and i did not reuse some methods already available in the class as there is some differences in implementation, but maybe it will be a good time to make this consistent (Looking forward to your input) ?

Things that should be done in a new PR:

 * Linking property info to property access to remove a lot of duplicate code
 * Add a new system that allow adding Virtual Property based on this extractor

Commits
-------

0a92dab Rebase, fix tests, review & update CHANGELOG
fc25086 [PropertyInfo] Add accessor and mutator extractor interface and implementation on reflection
  • Loading branch information
lyrixx committed Jan 31, 2020
2 parents 9d7e622 + 0a92dab commit a1e4222
Show file tree
Hide file tree
Showing 13 changed files with 807 additions and 342 deletions.
5 changes: 5 additions & 0 deletions src/Symfony/Component/PropertyAccess/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

5.1.0
-----

* Linking to PropertyInfo extractor to remove a lot of duplicate code

4.4.0
-----

Expand Down
380 changes: 81 additions & 299 deletions src/Symfony/Component/PropertyAccess/PropertyAccessor.php

Large diffs are not rendered by default.

Expand Up @@ -188,7 +188,7 @@ public function testIsWritableReturnsFalseIfNoAdderNorRemoverExists()
public function testSetValueFailsIfAdderAndRemoverExistButValueIsNotTraversable()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->expectExceptionMessageRegExp('/Could not determine access type for property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*": The property "axes" in class "Symfony\\\\Component\\\\PropertyAccess\\\\Tests\\\\PropertyAccessorCollectionTest_Car[^"]*" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\\Traversable, "string" given./');
$this->expectExceptionMessageRegExp('/The property "axes" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\PropertyAccessorCollectionTest_Car" can be defined with the methods "addAxis\(\)", "removeAxis\(\)" but the new value must be an array or an instance of \\\Traversable\./');
$car = new PropertyAccessorCollectionTest_Car();

$this->propertyAccessor->setValue($car, 'axes', 'Not an array or Traversable');
Expand Down
Expand Up @@ -760,7 +760,7 @@ public function testRemoverWithoutAdder()
public function testAdderAndRemoveNeedsTheExactParametersDefined()
{
$this->expectException('Symfony\Component\PropertyAccess\Exception\NoSuchPropertyException');
$this->expectExceptionMessageRegExp('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\. The method "removeFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 2 arguments, but should accept only 1\./');
$this->expectExceptionMessageRegExp('/.*The method "addFoo" in class "Symfony\\\Component\\\PropertyAccess\\\Tests\\\Fixtures\\\TestAdderRemoverInvalidArgumentLength" requires 0 arguments, but should accept only 1\./');
$object = new TestAdderRemoverInvalidArgumentLength();
$this->propertyAccessor->setValue($object, 'foo', [1, 2]);
}
Expand Down
3 changes: 2 additions & 1 deletion src/Symfony/Component/PropertyAccess/composer.json
Expand Up @@ -17,7 +17,8 @@
],
"require": {
"php": "^7.2.5",
"symfony/inflector": "^4.4|^5.0"
"symfony/inflector": "^4.4|^5.0",
"symfony/property-info": "^5.1"
},
"require-dev": {
"symfony/cache": "^4.4|^5.0"
Expand Down
5 changes: 5 additions & 0 deletions src/Symfony/Component/PropertyInfo/CHANGELOG.md
@@ -1,6 +1,11 @@
CHANGELOG
=========

5.1.0
-----

* Add support for extracting accessor and mutator via PHP Reflection

4.3.0
-----

Expand Down
372 changes: 333 additions & 39 deletions src/Symfony/Component/PropertyInfo/Extractor/ReflectionExtractor.php

Large diffs are not rendered by default.

82 changes: 82 additions & 0 deletions src/Symfony/Component/PropertyInfo/PropertyReadInfo.php
@@ -0,0 +1,82 @@
<?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;

/**
* The property read info tells how a property can be read.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*
* @internal
*/
final class PropertyReadInfo
{
public const TYPE_METHOD = 'method';
public const TYPE_PROPERTY = 'property';

public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';

private $type;

private $name;

private $visibility;

private $static;

private $byRef;

public function __construct(string $type, string $name, string $visibility, bool $static, bool $byRef)
{
$this->type = $type;
$this->name = $name;
$this->visibility = $visibility;
$this->static = $static;
$this->byRef = $byRef;
}

/**
* Get type of access.
*/
public function getType(): string
{
return $this->type;
}

/**
* Get name of the access, which can be a method name or a property name, depending on the type.
*/
public function getName(): string
{
return $this->name;
}

public function getVisibility(): string
{
return $this->visibility;
}

public function isStatic(): bool
{
return $this->static;
}

/**
* Whether this accessor can be accessed by reference.
*/
public function canBeReference(): bool
{
return $this->byRef;
}
}
@@ -0,0 +1,25 @@
<?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;

/**
* Extract read information for the property of a class.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*/
interface PropertyReadInfoExtractorInterface
{
/**
* Get read information object for a given property of a class.
*/
public function getReadInfo(string $class, string $property, array $context = []): ?PropertyReadInfo;
}
123 changes: 123 additions & 0 deletions src/Symfony/Component/PropertyInfo/PropertyWriteInfo.php
@@ -0,0 +1,123 @@
<?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;

/**
* The write mutator defines how a property can be written.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*
* @internal
*/
final class PropertyWriteInfo
{
public const TYPE_NONE = 'none';
public const TYPE_METHOD = 'method';
public const TYPE_PROPERTY = 'property';
public const TYPE_ADDER_AND_REMOVER = 'adder_and_remover';
public const TYPE_CONSTRUCTOR = 'constructor';

public const VISIBILITY_PUBLIC = 'public';
public const VISIBILITY_PROTECTED = 'protected';
public const VISIBILITY_PRIVATE = 'private';

private $type;
private $name;
private $visibility;
private $static;
private $adderInfo;
private $removerInfo;
private $errors = [];

public function __construct(string $type = self::TYPE_NONE, string $name = null, string $visibility = null, bool $static = null)
{
$this->type = $type;
$this->name = $name;
$this->visibility = $visibility;
$this->static = $static;
}

public function getType(): string
{
return $this->type;
}

public function getName(): string
{
if (null === $this->name) {
throw new \LogicException("Calling getName() when having a mutator of type {$this->type} is not tolerated");
}

return $this->name;
}

public function setAdderInfo(self $adderInfo): void
{
$this->adderInfo = $adderInfo;
}

public function getAdderInfo(): self
{
if (null === $this->adderInfo) {
throw new \LogicException("Calling getAdderInfo() when having a mutator of type {$this->type} is not tolerated");
}

return $this->adderInfo;
}

public function setRemoverInfo(self $removerInfo): void
{
$this->removerInfo = $removerInfo;
}

public function getRemoverInfo(): self
{
if (null === $this->removerInfo) {
throw new \LogicException("Calling getRemoverInfo() when having a mutator of type {$this->type} is not tolerated");
}

return $this->removerInfo;
}

public function getVisibility(): string
{
if (null === $this->visibility) {
throw new \LogicException("Calling getVisibility() when having a mutator of type {$this->type} is not tolerated");
}

return $this->visibility;
}

public function isStatic(): bool
{
if (null === $this->static) {
throw new \LogicException("Calling isStatic() when having a mutator of type {$this->type} is not tolerated");
}

return $this->static;
}

public function setErrors(array $errors): void
{
$this->errors = $errors;
}

public function getErrors(): array
{
return $this->errors;
}

public function hasErrors(): bool
{
return (bool) \count($this->errors);
}
}
@@ -0,0 +1,25 @@
<?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;

/**
* Extract write information for the property of a class.
*
* @author Joel Wurtz <jwurtz@jolicode.com>
*/
interface PropertyWriteInfoExtractorInterface
{
/**
* Get write information object for a given property of a class.
*/
public function getWriteInfo(string $class, string $property, array $context = []): ?PropertyWriteInfo;
}

0 comments on commit a1e4222

Please sign in to comment.