Skip to content

Create a TypeRegistry and enable injection of Type instances#2966

Open
GromNaN wants to merge 23 commits intodoctrine:2.17.xfrom
GromNaN:2-type-registry
Open

Create a TypeRegistry and enable injection of Type instances#2966
GromNaN wants to merge 23 commits intodoctrine:2.17.xfrom
GromNaN:2-type-registry

Conversation

@GromNaN
Copy link
Copy Markdown
Member

@GromNaN GromNaN commented Dec 10, 2025

Q A
Type feature
BC Break no (deprecation)
Fixed issues Closes #2818

Summary

  • Introduce a new class TypeRegistry that replace the static method of the Type class. The name is the same as DBAL TypeRegistry, with the same methods (except override because register creates or replace the type, "upsert" is a MongoDB specificity 😉)
  • Deprecate all the static methods of the Type class
  • Inject the TypeRegistry into the Configuration and add the public method getTypeRegistry(). This method is used by UnitOfWork, DocumentPersister and the aggregation builder.
  • Inject the TypeRegistry into ClassMetadata instances.

The Bundle PR: doctrine/DoctrineMongoDBBundle#963

@GromNaN GromNaN requested a review from Copilot December 10, 2025 00:28
Copy link
Copy Markdown

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 introduces a new TypeRegistry class to manage type instances and enables dependency injection of Type instances, deprecating the static methods on the Type class. This architectural change improves testability and allows for better isolation of type registries per DocumentManager.

Key changes include:

  • Creating TypeRegistry to replace static Type methods
  • Injecting TypeRegistry into DocumentManager and ClassMetadata
  • Deprecating all static methods on the Type class

Reviewed changes

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

Show a summary per file
File Description
src/Types/TypeRegistry.php New class implementing type registration and retrieval
src/Types/Type.php Static methods deprecated in favor of TypeRegistry
src/DocumentManager.php Integration of TypeRegistry with initialization logic
src/Mapping/ClassMetadata.php TypeRegistry injection and usage for type operations
src/UnitOfWork.php Updated to use DocumentManager's TypeRegistry
src/Persisters/*.php Updated to use DocumentManager's TypeRegistry
src/Hydrator/HydratorFactory.php Updated to use DocumentManager's TypeRegistry
src/Aggregation/*.php Updated to use DocumentManager's TypeRegistry
tests/Tests/Types/TypeRegistryTest.php New test coverage for TypeRegistry
tests/Tests/Types/TypeTest.php Updated tests for deprecated Type methods
tests/Tests/CaptureDeprecationMessages.php New trait for capturing deprecation messages in tests
tests/**/*Test.php Test files updated to use TypeRegistry

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/Tests/Types/TypeTest.php
Comment thread src/Mapping/ClassMetadata.php Outdated
Comment thread src/DocumentManager.php Outdated
Comment thread src/Persisters/DocumentPersister.php
Comment thread src/DocumentManager.php Outdated
Copy link
Copy Markdown
Member

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

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

Initial review done. I like the idea of a type registry, but it once again shows that we really need to clean up the dependency mess that we currently have.
The only concern I have is exposing the convertToDatabase method from DocumentPersister. I think we should mark it as @internal to ensure people don't treat it as part of the public API (unless the whole persister is already marked as such).

Comment thread docs/en/reference/custom-mapping-types.rst Outdated
Comment thread docs/en/reference/custom-mapping-types.rst Outdated
Comment thread src/DocumentManager.php Outdated
Comment thread src/DocumentManager.php Outdated
Comment thread src/Types/TypeRegistry.php
Comment thread src/Types/TypeRegistry.php Outdated
Comment thread src/Types/TypeRegistry.php Outdated
/**
* The TypeRegistry is responsible for managing the mapping types supported.
*/
class TypeRegistry
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should it be final?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Then I would have to create an interface since it's not internal. Which is more strict for BC than a regular class.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Then I would have to create an interface since it's not internal.

Do you? Is every type supposed to be replacable/mockable? I think it's OK to marks something as final and postpone the decision of making it extensible vs having an interface to when a need for extensibility actually emerges. For something that looks like an map with extra methods, I'm not convinced extensibility is needed.

Feel free to ignore this if you have a hard rule about this in this project.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Agree with making it final. We don't need to mock it for internal tests (yet), and people should definitely be mocking it for their own tests considering it's a registry they can create and inject as they see fit. We can always make the class non-final in a future minor release if necessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've already a use-case for extending the TypeRegistry: in the bundle I add lazy-loading of type instances from the container: https://github.com/doctrine/DoctrineMongoDBBundle/pull/963/changes#diff-b090a1e775d0625ed9280a9140328e118d6ad52fafb71464bf7246ac937e8442

@greg0ire
Copy link
Copy Markdown
Member

Some commits look like they should be squashed together.

@GromNaN
Copy link
Copy Markdown
Member Author

GromNaN commented Dec 12, 2025

Some commits look like they should be squashed together.

Yes, my plan was to have 2 separate commits to ease with rebase and merges, but the implementation changed a lot and that will not help anymore. I'll squash-merge the PR as we usually do on the MongoDB-ODM project.

Copy link
Copy Markdown
Member

@alcaeus alcaeus left a comment

Choose a reason for hiding this comment

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

Code LGTM. Do you want to add the deprecations to the UPGRADE document now or do we do that in the 3.0.x branch when we remove deprecated functionality?

@GromNaN GromNaN changed the base branch from 2.16.x to 2.17.x March 28, 2026 23:08
GromNaN added 4 commits March 29, 2026 00:30
Enable having a distinct list of types for each instance of DocumentManager in 3.0

Enable injecting type instances constructed with parameters,
which is necessary for generic embedded object transformations.
GromNaN added 18 commits March 29, 2026 00:30
MongoDB has 1 ms of precision, we use UTCDateTime object to normalize the timezone and skip the microseconds
- Fix DocumentPersister::prepareShardKeyQuery() passing $mapping['name']
  (DB field name) to getFieldType() which expects the PHP property name;
  use $mapping['fieldName'] instead, consistent with all other call sites
- Move TypeRegistry upgrade instructions from UPGRADE-2.16.md to the new
  UPGRADE-2.17.md, and fix method names in the migration table
  (get/has/register/getMap instead of the old static method names)
Covers the bug where getFieldType() received the DB field name
($mapping['name']) instead of the PHP property name ($mapping['fieldName']),
causing type conversion to be skipped when the two names differ.
Direct deserialization of ClassMetadata has never been supported, as the
factory is responsible for restoring runtime dependencies after cache hits.
…structors

Replace method_exists() check (which incorrectly blocked types inheriting a
no-arg constructor from a parent) with a try/catch on instantiation. This
correctly rejects only classes whose constructor has required parameters,
and as a bonus caches the created instance so get() never re-instantiates.
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.

Extract the static methods of the Type class into a TypeRegistry service

5 participants