Skip to content

Conversation

GromNaN
Copy link
Member

@GromNaN GromNaN commented Sep 1, 2025

Q A
Type feature
BC Break no
Fixed issues Related to #2789

Summary

The method getTypeFromPHPVariable is used only by convertPHPToDatabaseValue. Which is used to process expressions in the aggregation builder.

With this new feature, we try to find the type for object class name; assuming custom types are registered using the FQCN of the objects they store.

@GromNaN GromNaN added the Feature label Sep 1, 2025
@GromNaN GromNaN requested a review from Copilot September 1, 2025 16:26
Copilot

This comment was marked as outdated.

sprintf(
'Could not convert database value "%s" from "%s" to %s',
$value,
get_debug_type($value),
Copy link
Member

Choose a reason for hiding this comment

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

Are use function lines necessary? I'm OK to omit them, but just want to ensure you've considered the question.

Copy link
Member Author

Choose a reason for hiding this comment

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

The use function are not necessary for global functions. That's a CS preference to use them. I omit them for brevity in the example.


This pattern can be adapted for any custom class—just implement the conversion logic for your value object.

.. |FQCN| raw:: html <abbr title="Fully-Qualified Class Name">FQCN</abbr>
Copy link
Member

Choose a reason for hiding this comment

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

@GromNaN: this looks like it may be a false positive, since this is some very niche RST syntax

@GromNaN GromNaN force-pushed the type-from-php-variable branch from d446495 to d106a5a Compare September 3, 2025 15:59
Copy link
Member Author

@GromNaN GromNaN 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 the review. I added the type detection for DTO class name in property type.


This pattern can be adapted for any custom class—just implement the conversion logic for your value object.

.. |FQCN| raw:: html <abbr title="Fully-Qualified Class Name">FQCN</abbr>
Copy link
Member Author

Choose a reason for hiding this comment

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

I revert changes on this line. I'm not sure how this will be formatted.

Comment on lines 220 to 229
By using the |FQCN| of the value object class as the type name, the type is
automatically used when encountering a property of that class. This means you
can omit the ``type`` option when defining the field mapping::

.. code-block:: php
#[Field]
public Ramsey\Uuid\Uuid $id;
Copy link
Member Author

Choose a reason for hiding this comment

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

This is a new feature that can compete with embedded documents: adding custom logic to convert a value object into BSON.

Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe the example should be with a custom class instead, since UUID are new supported using symfony/uid #2826

Copy link
Member Author

Choose a reason for hiding this comment

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

We don't have a global namespace for exception like it is generally done in libraries. Existing exception names are:

  • Doctrine\ODM\MongoDB\MongoDBException
  • Doctrine\ODM\MongoDB\ConfigurationException
  • Doctrine\ODM\MongoDB\DocumentNotFoundException
  • Doctrine\ODM\MongoDB\Mapping\MappingException
  • Doctrine\ODM\MongoDB\Hydrator\HydratorException
  • Doctrine\ODM\MongoDB\LockException

Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR improves type detection for PHP variables in the MongoDB ODM by enhancing the getTypeFromPHPVariable method to automatically detect custom types based on object class names. The change allows custom types registered using their FQCN to be automatically selected when processing aggregation expressions.

Key changes:

  • Enhanced type detection to check if an object's class name corresponds to a registered custom type
  • Improved exception handling by introducing a dedicated InvalidTypeException
  • Added automatic type inference for object properties when custom types are registered with class FQCNs

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
lib/Doctrine/ODM/MongoDB/Types/Type.php Enhanced getTypeFromPHPVariable method to detect custom types by class name and improved type detection logic
lib/Doctrine/ODM/MongoDB/Types/InvalidTypeException.php New dedicated exception class for invalid type scenarios
lib/Doctrine/ODM/MongoDB/Mapping/ClassMetadata.php Added automatic type detection for properties with registered custom types
tests/Doctrine/ODM/MongoDB/Tests/Types/TypeTest.php Added comprehensive test coverage for the new type detection functionality
tests/Doctrine/ODM/MongoDB/Tests/Functional/CustomTypeTest.php Added tests demonstrating custom type detection and proper test setup/teardown
tests/Doctrine/ODM/MongoDB/Tests/Functional/Ticket/GH2789Test.php Improved test isolation by properly managing type registry state
docs/en/reference/custom-mapping-types.rst Updated documentation with UUID custom type example and FQCN usage patterns

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

return $value;
}
if ($value instanceof Binary) {
Copy link
Member

Choose a reason for hiding this comment

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

Would it make sense to restrict this to a MongoDB\BSON\Binary where getType() returns either TYPE_OLD_UUID or TYPE_UUID? As-is, it's not clear what would happen if you encountered binary data from the server with a different sub-type.

I'll note that convertToDatabaseValue() yields a Binary object with TYPE_UUID, so perhaps there's no reason to accept TYPE_OLD_UUID here.

If you are intentionally being liberal in what you accept and conservative in what you emit (Robustness Principle) and would like to leave this as-is, that's fine by me. You can optionally add a comment here explaining that design decision for the benefit of readers.

return null;
}
if ($value instanceof Binary) {
Copy link
Member

Choose a reason for hiding this comment

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

Would you want to ensure you always yield a Binary with sub-type TYPE_UUID? If so, you might need to split this into two separate conditionals.

if ($value instanceof Binary && $value->getType() !== Binary::TYPE_UUID) {
    // Note: this may throw if the data is not exactly 16 bytes
    return new Binary($value->getBytes(), Binary::TYPE_UUID);
}

if ($value instanceof Binary) {
    return $value;
}

Copy link
Member Author

Choose a reason for hiding this comment

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

<field field-name="field" type="date_with_timezone" />
Custom Type Example: Mapping a UUID Class
Copy link
Member

Choose a reason for hiding this comment

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

Note: I'm currently working on adding a type for Symfony UUIDs as I needed them for a project. That would make this entire chapter obsolete.

Copy link
Member Author

Choose a reason for hiding this comment

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

The chapter is about mapping a DTO with a custom type. UUID is only an example, we can find an other example.

@GromNaN GromNaN added this to the 2.13.0 milestone Sep 24, 2025
sprintf(
'Could not convert database value "%s" from "%s" to %s',
$value,
get_debug_type($value),
Copy link
Member Author

Choose a reason for hiding this comment

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

The use function are not necessary for global functions. That's a CS preference to use them. I omit them for brevity in the example.

Comment on lines 180 to 188
if (null === $value || [] === $value) {
return null;
}
if ($value instanceof Binary) {
return $value;
}
Copy link
Member Author

Choose a reason for hiding this comment

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

I can simplify the example and remove this edge cases.

Suggested change
if (null === $value || [] === $value) {
return null;
}
if ($value instanceof Binary) {
return $value;
}
if (null === $value) {
return null;
}

Copy link
Member

Choose a reason for hiding this comment

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

Without the $value instanceof Binary check, wouldn't the method then throw InvalidArgumentException for a Binary value? Or are you comfortable assuming that a Binary would never be passed in?

I didn't follow why [] was originally being handled alongside null, so I don't have much thought on that change.

Copy link
Member Author

Choose a reason for hiding this comment

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

In the example of document class, the property has the ?\Ramsey\Uuid\Uuid type, which ensure the RamseyUuidType::convertToDatabaseValue() method never get anything else.
As soon as the field value is the database is not corrupted, the simple implementation will work perfectly. No need to add support for edge cases that are never encountered.

return null;
}
if ($value instanceof Binary) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Comment on lines 220 to 229
By using the |FQCN| of the value object class as the type name, the type is
automatically used when encountering a property of that class. This means you
can omit the ``type`` option when defining the field mapping::

.. code-block:: php
#[Field]
public Ramsey\Uuid\Uuid $id;
Copy link
Member Author

Choose a reason for hiding this comment

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

Maybe the example should be with a custom class instead, since UUID are new supported using symfony/uid #2826

@GromNaN GromNaN force-pushed the type-from-php-variable branch 2 times, most recently from 30cb393 to d1ea68b Compare October 1, 2025 15:12
@GromNaN GromNaN force-pushed the type-from-php-variable branch from d1ea68b to 840f7b8 Compare October 3, 2025 14:27
@GromNaN GromNaN force-pushed the type-from-php-variable branch from 840f7b8 to f0ecc10 Compare October 3, 2025 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants