Skip to content

[FrameworkBundle][PropertyInfo] Allow defining accessors and mutators via an attribute #59529

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

Open
wants to merge 1 commit into
base: 7.3
Choose a base branch
from

Conversation

HypeMC
Copy link
Contributor

@HypeMC HypeMC commented Jan 16, 2025

Q A
Branch? 7.3
Bug fix? no
New feature? yes
Deprecations? no
Issues -
License MIT

A continuation/revival of #38515, contains #59497 and #59528

The idea of this PR is to allow defining accessors and mutators via a #[WithAccessors] attribute, eg:

class Foo
{
    #[WithAccessors(getter: 'giveProp', setter: 'receiveProp', adder: 'pushProp', remover: 'popProp')]
    private array $prop;

    public function giveProp(): string {}

    public function receiveProp(string $prop): void {}

    public function pushProp(string $prop): void {}

    public function popProp(string $prop): void {}
}

I haven't added YAML or XML support yet, but the foundation is in place, so it can easily be added in a future PR if needed.

@HypeMC HypeMC requested a review from dunglas as a code owner January 16, 2025 19:04
@carsonbot carsonbot added this to the 7.3 milestone Jan 16, 2025
@carsonbot carsonbot changed the title [PropertyInfo][FrameworkBundle] Allow defining accessors and mutators via an attribute [FrameworkBundle][PropertyInfo] Allow defining accessors and mutators via an attribute Jan 16, 2025
$allowGetterSetter = $context['enable_getter_setter_extraction'] ?? false;
$magicMethods = $context['enable_magic_methods_extraction'] ?? $this->magicMethodsFlags;
$allowMagicCall = (bool) ($magicMethods & self::ALLOW_MAGIC_CALL);
$allowMagicSet = (bool) ($magicMethods & self::ALLOW_MAGIC_SET);
$allowConstruct = $context['enable_constructor_extraction'] ?? $this->enableConstructorExtraction;
$allowAdderRemover = $context['enable_adder_remover_extraction'] ?? true;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Needed earlier


$camelized = $this->camelize($property);
Copy link
Contributor Author

@HypeMC HypeMC Jan 16, 2025

Choose a reason for hiding this comment

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

No point in getting the camelized version if the info can be extracted from the constructor. Moved it to when it's actually needed.

$constructor = $reflClass->getConstructor();
$singulars = $this->inflector->singularize($camelized);
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 was only used in findAdderAndRemover(), so I moved it there.

$mutator = new PropertyWriteInfo(PropertyWriteInfo::TYPE_ADDER_AND_REMOVER);
$mutator->setAdderInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $adderAccessName, $this->getWriteVisibilityForMethod($adderMethod), $adderMethod->isStatic()));
$mutator->setRemoverInfo(new PropertyWriteInfo(PropertyWriteInfo::TYPE_METHOD, $removerAccessName, $this->getWriteVisibilityForMethod($removerMethod), $removerMethod->isStatic()));
if (null === $adderAccessName || null === $removerAccessName) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

The $adderAccessName and $removerAccessName have been defined in the metadata.

@HypeMC HypeMC force-pushed the with-accessors-attribute branch 3 times, most recently from 8aec093 to d54d95a Compare January 17, 2025 12:57
->set('property_info.mapping.class_metadata_factory', ClassMetadataFactory::class)
->args([service('property_info.mapping.attribute_loader')])

->alias(ClassMetadataFactoryInterface::class, 'property_info.mapping.class_metadata_factory')
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 ancitipate a use case for autowiring this in an app? if not we could remove the alias

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@HypeMC HypeMC force-pushed the with-accessors-attribute branch from d54d95a to e55a729 Compare January 17, 2025 13:29
Copy link
Member

@nicolas-grekas nicolas-grekas left a comment

Choose a reason for hiding this comment

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

I'm wondering about the added metadata factory infrastructure. Do we really need to provide an API for this? I'm not sure it's a useful extensibility point.
Wouldn't the PR be way simpler without? What's the real world use case of these interfaces if you think we should add them?

->set('property_info.mapping.class_metadata_factory', ClassMetadataFactory::class)
->args([service('property_info.mapping.attribute_loader')])

// Cache
Copy link
Member

Choose a reason for hiding this comment

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

captain obvious ;) let's remove these

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 :D

public ?string $adder = null,
public ?string $remover = null,
) {
if (null === $this->getter && null === $this->setter && null === $this->adder && null === $this->remover) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (null === $this->getter && null === $this->setter && null === $this->adder && null === $this->remover) {
if (!($this->getter || $this->setter || $this->adder || $this->remover)) {

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

if (null === $this->getter && null === $this->setter && null === $this->adder && null === $this->remover) {
throw new LogicException('You need to have at least one method name set.');
}
if (null === $this->adder xor null === $this->remover) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (null === $this->adder xor null === $this->remover) {
if ($this->adder xor $this->remover) {

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

{
$propertyMetadata = $this->getClassMetadata($reflClass->getName())?->getPropertyMetadataFor($property);
if ((null !== $addMethod = $propertyMetadata?->adder) && (null !== $removeMethod = $propertyMetadata?->remover)) {
Copy link
Member

Choose a reason for hiding this comment

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

for readability (to me at least), there are many cases where checks could be simplified like this:

Suggested change
if ((null !== $addMethod = $propertyMetadata?->adder) && (null !== $removeMethod = $propertyMetadata?->remover)) {
if (($addMethod = $propertyMetadata?->adder) && ($removeMethod = $propertyMetadata?->remover)) {

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

}

if ($invalidMethods) {
throw new MappingException(\sprintf('The class "%s" does not have the following methods: "%s".', $refClass->name, implode('", "', $invalidMethods)), $refClass->name, $invalidMethods);
Copy link
Member

Choose a reason for hiding this comment

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

"Check its #[WithAccessors] attributes."

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

@HypeMC HypeMC force-pushed the with-accessors-attribute branch from e55a729 to bb0f56d Compare February 2, 2025 23:47
@HypeMC
Copy link
Contributor Author

HypeMC commented Feb 3, 2025

I'm wondering about the added metadata factory infrastructure. Do we really need to provide an API for this? I'm not sure it's a useful extensibility point. Wouldn't the PR be way simpler without? What's the real world use case of these interfaces if you think we should add them?

@nicolas-grekas I think it really depends on whether XML and/or YAML support should or will be added. If supporting multiple formats is desired, then the current infrastructure makes sense to me. Otherwise, I'd simplify it by keeping only an attribute loader, possibly with a cached loader.

Personally, I never use XML/YAML for any kind of mapping (validator, serializer, etc.), so I'd be fine with removing it.

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.

4 participants