Skip to content

Decouple property naming from PropertyCollection #53

Closed
Spea wants to merge 3 commits intoliip:2.xfrom
rebuy-de:naming-strategy
Closed

Decouple property naming from PropertyCollection #53
Spea wants to merge 3 commits intoliip:2.xfrom
rebuy-de:naming-strategy

Conversation

@Spea
Copy link
Contributor

@Spea Spea commented Jul 25, 2025

While working on discriminator support, I stumbled upon a bug where it was impossible to force a camelCase name for a property.

So using a class like this:

class MyClass {
    #[SerializedName('myProperty')]
    public string $myProperty;
}

would result in the following error

LogicException : You can not rename my_property into myProperty as it is the same property. Did you miss to handle camelCase properties with PropertyCollection::serializedName?

With the changes I did in this PR, it is now possible to pass different property naming strategies to the different parsers.
Besides fixing the bug at hand, this also provides more freedom when it comes to property collection handling, as you don't have to be aware that the collection itself creates the serialized name, but instead it is up to the caller to decide what serialized name should be used.

There is currently a bug where when you use a camelCase name for the `SerializedName` attribute which would result in the same snake_case name, that the `RawClassMetadata::renameProperty` method would throw an exception because it thinks we are trying to use the same name again.
This test only acts as a reference so we can fix the actual bug.
@Spea Spea force-pushed the naming-strategy branch from 8a994f2 to 99b6543 Compare July 25, 2025 14:23
Copy link
Member

@dbu dbu left a comment

Choose a reason for hiding this comment

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

thanks for this pull request. seems like a nice cleanup, that static method was indeed rather ugly.

i am slightly worried that when different parsers are given different strategies (e.g. one is forgotten and the default is taken), the parsers would add metadata to different names, leading to confusion. but i don't see a good way around that, i think we just need to document that.

can you please adjust the readme section https://github.com/liip/metadata-parser?tab=readme-ov-file#property-naming-strategy (haha, were we call it strategy when its a global static flag :-P - you actually convert that to the strategy pattern here)

$serializedName = $this->namingStrategy->getSerializedName($reflProperty->getName());
$property = PropertyVariationMetadata::fromReflection($reflProperty);
$classMetadata->addPropertyVariation($reflProperty->getName(), $property);
$classMetadata->addPropertyVariation($serializedName, $property);
Copy link
Member

Choose a reason for hiding this comment

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

oh, i vaguely remember that we struggled with different representation of the same property.

so your approach is to always use the serialized name in the metadata and never the original name?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

so your approach is to always use the serialized name in the metadata and never the original name?

That was actually already the case, the only difference now is, that the serialized name is created directly in the parser so you have more control over it instead of directly in the PropertyCollection

With this change it is now possible to pass different property naming strategies to the different parsers.
This also provides more freedom when it comes to property collection handling, as you don't have to be aware that the collection itself creates the serialized name, but instead it is up to the caller to decide what serialized name should be used.
@Spea Spea force-pushed the naming-strategy branch from 99b6543 to 4e69d6b Compare July 27, 2025 16:21
@Spea
Copy link
Contributor Author

Spea commented Jul 27, 2025

i am slightly worried that when different parsers are given different strategies (e.g. one is forgotten and the default is taken), the parsers would add metadata to different names, leading to confusion

I thought about this too, and I see a few other directions we could go here

  • Create an abstract parser class which handles the default strategy and has a separate method for injecting a different one
  • Create a separate interface ModelParserNamingStrategyAwareInterface which has one method setPropertyNamingStrategy and check in the Builder class if the current parser implements this interface and call the related method
  • Add a second parameter to the parse method where you have to pass the naming strategy
    parse(RawClassMetadata $classMetadata, PropertyNamingStrategyInterface $namingStrategy): void

This means in all cases the Builder class would be the responsible one for handling naming strategies.

Personally I think the third option is better, but also a bit weird in case the parser does not need any naming strategy (like the LiipMetadataAttributeParser)

What do you think?

@dbu
Copy link
Member

dbu commented Aug 4, 2025

i like the third option best. yes, we will pass the strategy even when no naming is needed, but there is no real cost involved as the strategy is injected/created only once.
the consistency of the strategy is very important, so being able to guarantee that seems worth it.

@dbu
Copy link
Member

dbu commented Aug 4, 2025

do you adjust this PR correspondingly?

are there other things for version 2.x or should i tag the release once we have these changes?

@Spea
Copy link
Contributor Author

Spea commented Aug 4, 2025

do you adjust this PR correspondingly?

are there other things for version 2.x or should i tag the release once we have these changes?

Yes, I will adjust this PR accordingly. After this, the last thing I want to add is the discriminator support (which is almost done, at least the metadata-parser side). However, I have an upcoming vacation, so I'm not sure if I can finish it before, but I will definitely finish it this month.

This ensures that the same strategy is used for all parsers. Using the strategy is completely optional, so parsers can just ignore it if they don't need it.
@Spea
Copy link
Contributor Author

Spea commented Aug 4, 2025

do you adjust this PR correspondingly?

Done via 2006b3d - Thanks for your feedback on this 🙇

@Spea Spea requested a review from dbu August 4, 2025 18:19
Copy link
Member

@dbu dbu left a comment

Choose a reason for hiding this comment

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

great, thanks a lot! i did some tweaks with the changelog, will try to accept those changes myself and merge.

i am in no hurry to release version 2, i will wait for the discriminator support. it will be easier if its in a major version, and maybe we end up wanting to do BC breaks.

* Adjusted code to not trigger warnings with PHP 8.4
* Removed deprecated `getDeserializeFormat` method from date time
* Replaced `@Preferred` annotation with the `#[Preferred]` attribute
* Added a new `PropertyNamingStrategyInterface` which can be used to modify the naming strategy for properties. Previously
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
* Added a new `PropertyNamingStrategyInterface` which can be used to modify the naming strategy for properties. Previously
* Replaced `PropertyCollection::useIdenticalNamingStrategy` with the `PropertyNamingStrategyInterface`. To change the default strategy, you can now inject it into the parser instead of changing a static property.

* Removed deprecated `getDeserializeFormat` method from date time
* Replaced `@Preferred` annotation with the `#[Preferred]` attribute
* Added a new `PropertyNamingStrategyInterface` which can be used to modify the naming strategy for properties. Previously
this could be adjusted via the `PropertyCollection::useIdenticalNamingStrategy` method, but as this was a bit error prone, a separate
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
this could be adjusted via the `PropertyCollection::useIdenticalNamingStrategy` method, but as this was a bit error prone, a separate

* Replaced `@Preferred` annotation with the `#[Preferred]` attribute
* Added a new `PropertyNamingStrategyInterface` which can be used to modify the naming strategy for properties. Previously
this could be adjusted via the `PropertyCollection::useIdenticalNamingStrategy` method, but as this was a bit error prone, a separate
interface was introduced. This new interface also comes with two implementations:
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
interface was introduced. This new interface also comes with two implementations:
The library provides two implementations for the property naming strategy:

this could be adjusted via the `PropertyCollection::useIdenticalNamingStrategy` method, but as this was a bit error prone, a separate
interface was introduced. This new interface also comes with two implementations:
* `IdenticalPropertyNamingStrategy`
* `SnakeCasePropertyNamingStrategy`.
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
* `SnakeCasePropertyNamingStrategy`.
* `SnakeCasePropertyNamingStrategy` (default).

@dbu
Copy link
Member

dbu commented Aug 5, 2025

i applied the changelog changes and merged the branch. thanks a lot for this contribution !

@dbu dbu closed this Aug 5, 2025
@Spea Spea deleted the naming-strategy branch August 5, 2025 09:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants