Skip to content

[PropertyAccess] Allow custom property accessors #22190

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from

Conversation

lrlopez
Copy link
Contributor

@lrlopez lrlopez commented Mar 27, 2017

Q A
Branch master
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets #18016, #5013, #9336, #5219, among others
License MIT
Doc PR symfony/symfony-docs#6400

Here is the first of the two PR related to #18016. This implements changes on the PropertyAccess component and will be followed by another one leveraging the new features into the FrameworkBundle.

Now it is possible to override the property accessors (getters, setters, adders and removers) using annotations or YAML config files. Metadata is automatically cached in the pool configured for the property path cache.

Some examples of how to use the new feature:

Annotations:

/**
 * @PropertyAccessor(getter="getCurrentValue", setter="updateValue")
 */
protected $value;

/**
 * @PropertyAccessor(adder="joinMember", remover="detachMember")
 */
protected $members;

public function getCurrentValue()
{
    return $this->value;
}

public function updateValue($newValue)
{
    $this->value = $newValue;
    return $this;
}

public function joinMember($newMember)
{
    // Add $newMember to the collection
}

public function detachMember($oldMember)
{
    // Remove $oldMember from the collection
}

// Notice that "calculated" is not a real property!
/**
 * @GetterAccessor(property="calculated")
 */
public function getCalculated()
{
    // whatever...
}

// You can omit 'property' in method annotations
/**
 * @SetterAccessor("calculated")
 */
public function setCalculated($calculated)
{
    // whatever...
}

YAML:

'AppBundle\Entity\Example':
  properties:
    value:
      getter: getCurrentValue
      setter: updateValue
    members:
      adder: joinMember
      remover: detachMember
    calculated:
      getter: getCalculated
      setter: setCalculated

XML:

<?xml version="1.0" ?>

<property-access xmlns="http://symfony.com/schema/dic/property-access-mapping"
                    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
                    xsi:schemaLocation="http://symfony.com/schema/dic/property-access-mapping http://symfony.com/schema/dic/property-access-mapping/property-access-mapping-1.0.xsd">

    <class name="AppBundle\Entity\Example">
        <property name="value" getter="getCurrentValue" setter="updateValue" />
        <property name="members" adder="joinMember" remover="detachMember" />
        <property name="calculated" getter="getCalculated" setter="setCalculated" />
    </class>

</property-access>

{
$classMetadata = new ClassMetadata('c');

$a1 = $this->createMock('Symfony\Component\PropertyAccess\Mapping\PropertyMetadata');
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to fail on PHP 5.5 & PHPUnit 4.8.35. ClassMetadataTest extends PHPUnit\Framework\TestCase which implements that method, so I'm not sure on how to solve this. It doesn't fail on any other scenarios. Any ideas?

@Koc
Copy link
Contributor

Koc commented Mar 28, 2017

totally agree with you that Symfony needs some component for reading metadata.

@lrlopez lrlopez changed the title [PropertyAccess] Allow custom property accessors (WIP) [PropertyAccess] Allow custom property accessors Mar 29, 2017
@lrlopez
Copy link
Contributor Author

lrlopez commented Mar 29, 2017

Ok. I think this PR is ready for review. Did we make the cut for Symfony 3.3? 😅

{
// Overwrite only if not defined
if (null === $this->getter) {
$this->getter = $propertyMetadata->getGetter();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The function call is not needed here.

/**
* Merges another PropertyMetadata with the current one.
*
* @param PropertyMetadata $propertyMetadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param self $propertyMetadata .

Actually it could be self in the signature as well but this is not common in the code base, so I don't if it should be done.

@@ -912,7 +936,7 @@ private function getPropertyPath($propertyPath)
*
* @return AdapterInterface
*
* @throws RuntimeException When the Cache Component isn't available
* @throws \RuntimeException When the Cache Component isn't available
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This change should not target master.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't recall making this change, I'll revert it. Thank you!

private function getPropertyMetadata($class, $property)
{
if ($this->cacheItemPool) {
$key = $class.'\\'.$property;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not using a dot as separator directly since it's gonna be replaced?

if ($this->classMetadataFactory) {
$classMetadata = $this->classMetadataFactory->getMetadataFor($class);
if ($classMetadata) {
$metadataCollection = $classMetadata->getPropertyMetadataCollection();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should add a getMetadataForProperty($property) method that would return the metadata or null and remove the overhead below, the block could then look like:

$metadata = null;
if ($this->classMetadataFactory && $classMetadata = $this->classMetadataFactory->getMetadataFor($class) ) {
    $metadata = $classMetadata->getMetadataForProperty($property);
}

return $metadata;

/**
* Merges a {@link ClassMetadata} into the current one.
*
* @param ClassMetadata $classMetadata
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@param self $classMetadata


$class = ltrim(is_object($value) ? get_class($value) : $value, '\\');

if (class_exists($class) || interface_exists($class)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

return class_exists($class) || interface_exists($class)?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

😅

{
if ($metadata && $metadata->getAdder() && $metadata->getRemover()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I couldn't find anything in this PR related to this requirement, it should be clear somewhere that in one is set this other is required.
Although I feel like one should should be enough, perhaps allowing to mix adder and remover guessing?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just mimicking the actual PropertyAccess behavior, in which both methods must be defined to activate this kind of accessors. You're right, docs should warn about this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll try to fix one of the accessor and let the component guess the other one, seems reasonable

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

Copy link
Contributor

@HeahDude HeahDude Apr 9, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks! It would be awesome if it was covered by tests too to prevent regression.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done!

@lrlopez lrlopez force-pushed the property-access-component branch from 66e6b48 to d3eb07e Compare April 9, 2017 17:55
@lrlopez
Copy link
Contributor Author

lrlopez commented Apr 9, 2017

@HeahDude, thank you very much for the review!

@lrlopez lrlopez force-pushed the property-access-component branch 5 times, most recently from ce94b7d to 0691fb2 Compare April 9, 2017 18:54
@@ -133,6 +141,11 @@ class PropertyAccessor implements PropertyAccessorInterface
private $cacheItemPool;

/**
* @var MetadataFactoryInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing |null.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is really necessary? Then it should be also added to $classMetadataFactory above...

@lrlopez lrlopez force-pushed the property-access-component branch from 0691fb2 to 58aad1f Compare April 9, 2017 21:37
@@ -158,11 +171,12 @@ class PropertyAccessor implements PropertyAccessorInterface
* @param bool $throwExceptionOnInvalidIndex
* @param CacheItemPoolInterface $cacheItemPool
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right, same here, missing the new argument by the way ;).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed! Thanks

@lrlopez lrlopez force-pushed the property-access-component branch from 58aad1f to 5e8f0dd Compare April 9, 2017 21:56
@lrlopez lrlopez force-pushed the property-access-component branch 2 times, most recently from 3ec7ce5 to 2d9fc7a Compare April 10, 2017 00:10
@lrlopez
Copy link
Contributor Author

lrlopez commented Apr 10, 2017

Annotation names changed as per @HeahDude request. Suggestions welcomed!

@xabbuh
Copy link
Member

xabbuh commented Oct 3, 2017

The CI builds don't seem to be happy. Can you have a look at the failing tests?

@lrlopez
Copy link
Contributor Author

lrlopez commented Oct 3, 2017

Sure!

@lrlopez lrlopez force-pushed the property-access-component branch from e27b1b6 to 72943a1 Compare October 3, 2017 18:37
@lrlopez
Copy link
Contributor Author

lrlopez commented Oct 3, 2017

Done

@lrlopez lrlopez force-pushed the property-access-component branch from 72943a1 to 2f2ad49 Compare October 3, 2017 18:40
@nicolas-grekas
Copy link
Member

ping @xabbuh @dunglas @symfony/deciders it looks like this one is ready, any review or should we move to 4.1?

*
* @param PropertyMetadata $propertyMetadata
*/
public function addPropertyMetadata(PropertyMetadata $propertyMetadata)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you think we can make this value object immutable? We do that for new VO (PSR-7 style, see the PropertyInfo component for instance).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be done. But I'm curious about what would be the benefits. This class is used internally only, it's not exposed outside the component. Some parts of this code are borrowed from the Serializer component but I infer that the current implementation intent is to allow fast & easy merging of metadata from different sources and classes in the hierarchy.

@xabbuh
Copy link
Member

xabbuh commented Oct 8, 2017

I wonder if we should also take the changes proposed in #23789 into account.

@lrlopez lrlopez force-pushed the property-access-component branch 2 times, most recently from c228dbd to 146916b Compare November 6, 2017 08:54
@lrlopez lrlopez force-pushed the property-access-component branch from 146916b to e6aa7f1 Compare November 6, 2017 09:01
@chalasr chalasr modified the milestones: 3.4, 4.1 Nov 9, 2017
@chalasr
Copy link
Member

chalasr commented Nov 9, 2017

3.4 does not accept features anymore.
Moving to 4.1, allowing to use PHP7.1 features such as scalar typehints.

@lrlopez
Copy link
Contributor Author

lrlopez commented Nov 9, 2017

It's a shame. Well, if someone wants to take this PR over, please go ahead. I don't think I'll be able to invest more time into it (too many rebases and changes since 3.1) at least until next summer.

@joelwurtz
Copy link
Contributor

I believe #30248 will help here, as it introduce the concept of extracting Accessor / Extractor from the metadata

lyrixx added a commit that referenced this pull request Jan 31, 2020
…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
@lyrixx
Copy link
Member

lyrixx commented Jan 31, 2020

#30704 has been merged

@nicolas-grekas nicolas-grekas changed the base branch from 3.4 to master June 15, 2020 07:08
@fabpot fabpot closed this Oct 7, 2020
@fabpot
Copy link
Member

fabpot commented Oct 7, 2020

We've just moved away from master as the main branch to use 5.x instead. Unfortunately, I cannot reopen the PR and change the target branch to 5.x. Can you open a new PR referencing this one to not loose the discussion? Thank you for your understanding and for your help.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.