8000 [PropertyInfo] Implement Naming Strategy for Properties · Issue #16889 · symfony/symfony · GitHub
[go: up one dir, main page]

Skip to content

[PropertyInfo] Implement Naming Strategy for Properties #16889

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
zanbaldwin opened this issue Dec 7, 2015 · 5 comments
Closed

[PropertyInfo] Implement Naming Strategy for Properties #16889

zanbaldwin opened this issue Dec 7, 2015 · 5 comments

Comments

@zanbaldwin
Copy link
Member

The ReflectionExtractor sees a public property as separate from its accessor/mutator methods. For example:

class Website
{
    public $domainName;
    public $author;

    public function getDomainName()
    {
        return $this->domainName;
    }

    public function getAuthor()
    {
        return $this->author;
    }
}

var_dump($reflectionExtractor->getProperties());
/*
array (size=4)
  0 => string 'domainName' (length=10)
  1 => string 'author' (length=6)
  2 => string 'DomainName' (length=10)
  3 => string 'Author' (length=6)
*/

The easiest fix would be to lowercase the first letter of the property name determined from a method, though this would cause problems with properties that actually do start with an uppercase letter.

Keeping in mind that it's likely that this component will be used in conjunction with the PropertyAccess component, would it be suitable for a naming strategy to be added (the most obvious being the underscore naming strategy used in both PropertyAccess and Doctrine)?

/cc @dunglas

@dunglas
Copy link
Member
dunglas commented Dec 7, 2015

It's definitely a bug. The first letter must be lower cased.

About the naming strategy, why not adding support for snake_case because as you pointed out it exists in PropertyAccess. But IMO it's better to have be coherent between these 2 components: support only camelCase/snake_case for both or a support for custom strategies for both.

@zanbaldwin
Copy link
Member Author

snake_case for PropertyInfo it is. It would be nice to implement naming strategies in both components for edge cases eventually, but I don't see it as a priority.

However, a basic property name transformer might be a good idea to maintain consistency across extractors.

@linaori
Copy link
Contributor
linaori commented Dec 8, 2015

Currently doctrine does not fully support snake-case properties: doctrine/collections#29

@zanbaldwin
Copy link
Member Author

Good point.

It's probably best that the component does not take on the role of dictating naming conventions, especially since PSRs actively avoid taking a stance on such issues.

How about implementing a closest-match property name transformer to match identifiers to the correct class properties/methods?

Identifier Attempt (in order)
first_name $first_name, $firstName, {prefix}First_name(), {prefix}FirstName()
firstName $firstName, {prefix}FirstName()
FirstName $FirstName, $firstName, {prefix}FirstName()

To return a property identifier, return class properties as-is and lowercasing the first letter of those determined from class methods. There won't be any issue of which naming convention (snake_case, camelCase, StudleyCaps, etc) is used since the transformer will always choose the property/method closest to the identifier.

Trying to support naming strategies would result in compatibility and support nightmares. There are just too many possible ways for it to go wrong, especially if a mix of naming conventions were used within the same class.

interface PropertyNameTransformerInterface
{
    /**
     * @param string $property
     * @return string
     */
    public function sourceToIdentifier($property);

    /**
     * @param string $name
     * @return string
     */
    public function identifierToSource($name);
}

Only issue is how to change the public API of the extractors to ensure each one gets this new property name transformer.

@zanbaldwin
Copy link
Member Author

Edit; the more I think about this the more I want to just lowercase the first letter...

The PropertyAccess component already maps identifier strings to the correct property/method, why include it in PropertyInfo which is such a small component in comparison? It's an unnecessary overhead, especially where multiple properties/methods exist with only capitalisation differences.

Compatibility with the PropertyAccess component needs to improve in the future but I don't think a naming strategy is the way to go IMO.


  • Property names get returned as-is.
  • For methods, strip the prefix and (unless a property with exactly the same capitalisation exists) lowercase the first letter.
    • In the reverse, capitalise the first letter and prepend the prefix.

Simple and effective. Thoughts?

dunglas added a commit that referenced this issue Dec 14, 2015
…actor (zanderbaldwin)

This PR was squashed before being merged into the 2.8 branch (closes #16911).

Discussion
----------

[PropertyInfo] Update List Information from ReflectionExtractor

| Q             | A
| ------------- | ---
| Bug fix?      | yes
| New feature?  | no
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | #16889
| License       | MIT
| Doc PR        | symfony/symfony-docs#5974

Unless a property with the same casing exists, lowercase the first letter of a property name extracted from a method. From what I understand, we don't actually need to support `snake_case` at all in the PropertyInfo component.

I cannot think of any use-case where PropertyAccess would be used to get/set a property value *before* using PropertyInfo to find out information about the property and the values it supports.

Since PropertyInfo supports the discovery of property names, which can then be used as identifiers in PropertyAccess, there should be no need to support a naming strategy to map property identifiers from one naming convention to another.

---

Running `$reflectionExtractor->getProperties($class)` with the following classes:

```php
class X
{
    public $a;
    public $b;
    public function getA() {}
}
// Result: array('a', 'b');
```

```php
class Y
{
    public $A;
    public $b;
    public function setA() {}
}
// Result: array('A', 'b');
```

```php
class Y
{
    public $username;
    protected $emailAddress;
    public $password;
    public function getEmailAddress() {}
    public function isActive() {}
}
// Result: array('username', 'emailAddress', 'password', 'active');
```

Commits
-------

b2da76c [PropertyInfo] Update List Information from ReflectionExtractor
@dunglas dunglas closed this as completed Dec 14, 2015
nicolas-grekas added a commit that referenced this issue Sep 29, 2023
… isReadable() when checking snake_case properties (jbtronics)

This PR was squashed before being merged into the 6.4 branch.

Discussion
----------

[PropertyInfo] Make isWriteable() more consistent with isReadable() when checking snake_case properties

| Q             | A
| ------------- | ---
| Branch?       | 6.4
| Bug fix?      | no
| New feature?  | yes
| Deprecations? | no
| Tickets       | Related with issues #29933 and #16889
| License       | MIT
| Doc PR        |

Currently PropertyInfo is a bit inconsistent in the behavior of isWriteable() and isReadable() when using it with properties in snake_case format, which are only accessible via a getter and setter. To be readable the getter function has to be in camel_case (e.g. `getSnakeCase()`), while the setter function has to remain in the snake case format (e.g. `setSnake_case()`).

In this example class:
```php
class Dummy {
  private string $snake_case;

  public function getSnakeCase(): string
  { }

  public function setSnakeCase(string $snake_case): void
  { }

}
````

the property `snake_case` is considered readable like you would expect, but not writeable, even though the property has a useable setter and the value can be actually be modified fine by something like the PropertyAccess component.
To make the property actually considered writeable, the setter would need to be named `setSnake_case`, which is pretty inconsistent with the behavior of isReadable and makes it very hard to use this component on snake_case properties.

This inconsistencies are caused by the fact, that `isReadable` in ReflectionExtractor uses the `getReadInfo()` function which does a camelization of the property name internally, while the `isWriteable()` function uses the `getMutatorMethod()` function which just perform a capitalization of the first letter.

This PR fixes this inconsistencies between isReadable() and isWriteable() by allowing to use a camelCase style setter to be considered writeable, as this is much more common then to use the mix of snake and camelCase currently required.

The getWriteInfo() function is not useable here, because it needs both a add and remove Function on collection setters to give proper infos, while the current `isWriteable()` implementation considers a class with just a add or a remove function as writeable. Therefore the property name just gets camelized before being feed into the `getMutatorMethod()`, which gives the desired result.

To maximize backwards compatibility, if no camelCase style setter is found, it is still checked for a snake_case setter, so that classes having these, are still considered writeable after the change.

The current behavior is causing some inconsistencies in higher level libraries, which rely on this component. In API Platform for example properties in snake_case are considered read only even though they have a (camel case) setter, because of this. See issue: api-platform/core#5641 and api-platform/core#1554

Commits
-------

7c9e6bc [PropertyInfo] Make isWriteable() more consistent with isReadable() when checking snake_case properties
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants
0