Skip to content
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

Use typed properties for default metadata for #7939 #8439

Merged
merged 16 commits into from
Mar 1, 2021

Conversation

Lustmored
Copy link
Contributor

This is simple implementation of idea outlined in #7939. During completion step of a field mapping completion or association mapping completion it looks for typed property and, when existent, uses it to determine default for type, targetEntity and nullable (in fields and joinTable).

I have added simple tests covering all cases I found.

I am not a great fan of encapsulating code in PHP_VERSION_ID >= 70400, but at the time of writing got no better idea.

I couldn't get master branch to work locally and that's the only reason why I target '2.9.x' (Composer has problem resolving doctrine/dbal).

This is my first PR to Doctrine project, so if anything is done wrong please let me know and I will do my best to fix it :)

@Lustmored
Copy link
Contributor Author

I don't know how to proceed with tests under PHP < 7.4 as there are no typed properties and entity class required for new test cases fails there. I probably could move it to other namespace, but that seems like complicating file structure without much benefit. Please help me with any suggestion :)

Copy link
Member

@beberlei beberlei left a comment

Choose a reason for hiding this comment

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

Thank you for your PR, this is a very good first contribution, congrats :-)

I made some comments and have some questions inline.

@@ -1414,6 +1420,53 @@ protected function _validateAndCompleteFieldMapping(array &$mapping)
throw MappingException::missingFieldName($this->name);
}

if (
PHP_VERSION_ID >= 70400
&& isset($this->reflClass)
Copy link
Member

Choose a reason for hiding this comment

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

Just to understand, do you have a case where $this->reflClass is not set? I assume when DisconnectedMetadataFactory is used while converting an XML/YML mapping or generating entities?

Copy link
Member

Choose a reason for hiding this comment

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

The condition is duplicated below as well, can you factor it out into a private method?

private function isTypedProperty($name) : bool
{
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

When I remove isset($this->reflClass) there are 56 test errors and 3 failures with Error: Call to a member function hasProperty() on null message. I have no idea if those tests should be fixed or it is expected behavior and don't know Doctrine internals enough yet to determine if it can occur in real usage.

I can work through those tests and enforce setting $this->reflClass if that's desired, but someone more experience would need to take a look for real cases where unset reflClass is desired.

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 have also factored condition as requested

Copy link
Member

Choose a reason for hiding this comment

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

It makes sense that it fails for when the DisconnectedMetadataFactory is used in case of EntityGenerator and exporter and such things. Of course this creates an inconsistency where the types default to "string" when reflClass does not exist. But there is no way to avoid that at the moment, so lets keep it this way.

case DateTimeImmutable::class:
$mapping['type'] = 'datetime_immutable';
break;
case 'array':
Copy link
Member

Choose a reason for hiding this comment

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

this is ambiguous, could also be simple_array or object type. Not sure what to do, since otherwise it will default to "string" later.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

My reasoning was to provide useful defaults instead of solving every possible use case. In this example having "string" as property type would simply fail at runtime as ORM would try to assign string value to array typed property. Therefore I believe useful default is better than nothing.

But the same problem comes with \DateType and many other fields. I believe the best long-term solution would be to create new interface, for example DefaultTypeResolverInterface that could look like this:

interface DefaultTypeResolverInterface
{
    public function determineTypeForProperty(\ReflectionType $type): ?string;
}

And provide at least one implementation (maybe working as here) and then allow users to choose implementation based on their config.

This way we could provide useful defaults for everyone and allow users to have simple method of providing own rules (for example I would benefit from overwriting \DateTimeImmutable in my projects).

I don't know how to approach such solution in ORM structure, but would love to do so. If this seems reasonable please just give me some directions and I will follow through.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, lets stay with the hardcoded resolving rules for now and we'll leave the replacable resolving rules for a future PR.

You should document what PHP types get mapped to which Doctrine types in the documentation, see docs/ folder.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I have added documentation where it seemed logical to me

lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php Outdated Show resolved Hide resolved
lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php Outdated Show resolved Hide resolved
lib/Doctrine/ORM/Mapping/ClassMetadataInfo.php Outdated Show resolved Hide resolved
@beberlei beberlei added this to the 2.9.0 milestone Feb 5, 2021
@Lustmored
Copy link
Contributor Author

Thanks for the review. I will work on it next week 👍

@Lustmored
Copy link
Contributor Author

I have rebased onto 2.9.x (still some conflicts pop out, don't know why) and handled suggestions 👍

@Lustmored Lustmored requested a review from beberlei February 8, 2021 12:17
*
* @return void
*/
private function _validateAndCompleteTypedFieldMapping(array &$mapping)
Copy link
Member

Choose a reason for hiding this comment

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

don't pass by reference please but return the array again. This is how other _validate methods in this class do it already, so that stay consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, I have changed _validateAndCompleteFieldMapping too while at it - it was still using reference.

*
* @return void
*/
private function _validateAndCompleteTypedAssociationMapping(array &$mapping)
Copy link
Member

Choose a reason for hiding this comment

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

same here, return the array again and don't pass by reference

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, done

@Lustmored
Copy link
Contributor Author

I think I addressed all current comments to some degree. I see that checks are somehow stuck and I see no way to "unstuck" them to see if my code passes. Is it possible somehow to run them?

@Lustmored Lustmored requested a review from beberlei February 9, 2021 10:55
@beberlei
Copy link
Member

beberlei commented Feb 9, 2021

@Lustmored this is because of the conflict in ClassMetadataInfo.php, you need to fix that before the checks run.

Copy link
Member

@beberlei beberlei left a comment

Choose a reason for hiding this comment

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

Just two small changes, then I believe this is ready to go.

@@ -715,6 +717,7 @@ Required attributes:

- **targetEntity**: FQCN of the referenced target entity. Can be the
unqualified class name if both classes are in the same namespace.
When typed properties are used it is inherited from PHP type.
Copy link
Member

Choose a reason for hiding this comment

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

I would formulate this a bit differently: You can omit this value if you use a PHP property type instead.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

OK, changed


// Join table Nullable
$cm->mapOneToOne(['fieldName' => 'email', 'joinColumns' => [[]]]);
$this->assertFalse($cm->getAssociationMapping('email')['joinColumns'][0]['nullable']);
Copy link
Member

Choose a reason for hiding this comment

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

Missing assertion on target_entity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, missed it. Assertion added

@Lustmored
Copy link
Contributor Author

I think I have complied with all your comments. I have also rebased onto 2.9.x and pushed CS fixed.

Only remaining failing check is CS complaing about method names starting with underscore - I don't have the confidence to change them without asking first.

@Lustmored Lustmored requested a review from beberlei February 10, 2021 08:52
@Lustmored
Copy link
Contributor Author

I have rebased onto 2.9 and made a simple change to use Types:: constants instead of strings for types. Is there anything else I can do to make this happen? :)

@beberlei beberlei merged commit b3ed525 into doctrine:2.9.x Mar 1, 2021
@beberlei
Copy link
Member

beberlei commented Mar 1, 2021

@Lustmored thank you very much!

@Lustmored Lustmored deleted the 2.9.x branch March 4, 2021 09:43
@beberlei
Copy link
Member

beberlei commented Apr 4, 2021

@Lustmored FYI, This doesn't work anymore with AttributeDriver (does it with Annotations?) Because of the default value of Column::$type = "string". The typed mapping completion only works when $mapping['type'] is not set. I am going to investigate more.

@Lustmored
Copy link
Contributor Author

@beberlei I see the problem and I see how I missed it in tests (I have created only tests for ClassMetadataInfo and not metadata obtained from mapping drivers). I am also looking into it. Probably simplest solution would be to change the rule from isset to ! isset($foo) || $foo === 'string', but that would lead to obvious problem of impossibility to overwrite type inherited from typed property with a simple string type.

@beberlei
Copy link
Member

beberlei commented Apr 4, 2021

@Lustmored i think setting Column::$type to nullable & default null is better

@Lustmored
Copy link
Contributor Author

@beberlei That's exactly what I'm experimenting with right now. Probably will come with a PR fixing the issue and introducing more tests to ensure it works with mapping later today 👍

@Lustmored
Copy link
Contributor Author

@beberlei let's continue in #8589. Hopefully all cases are covered there this time :)

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