Skip to content

Conversation

@joelwurtz
Copy link
Member

No description provided.

@joelwurtz joelwurtz force-pushed the feat/object-mapper branch 3 times, most recently from 81cb479 to bcc9aea Compare January 23, 2026 11:14
@joelwurtz joelwurtz force-pushed the feat/object-mapper branch 2 times, most recently from af31016 to c28a2d8 Compare January 26, 2026 10:03
@joelwurtz joelwurtz marked this pull request as ready for review January 26, 2026 10:08
@joelwurtz joelwurtz requested a review from Copilot January 27, 2026 08:51
Copy link

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 adds first-class support for Symfony’s symfony/object-mapper by teaching AutoMapper to understand #[Map]-style attributes, and by providing an implementation of Symfony’s ObjectMapperInterface backed by AutoMapper.

Changes:

  • Introduces AutoMapper\ObjectMapper\ObjectMapper, integrating Symfony’s ObjectMapperInterface with AutoMapper (including transform callables, target selection, and error handling).
  • Extends the metadata/mapper generation pipeline (providers, conditions, lazy-object handling, service-locator integration) to consume Symfony ObjectMapper attributes and condition/transform callables.
  • Adds an extensive PHPUnit test suite and fixtures mirroring Symfony’s ObjectMapper tests to verify compatibility (recursion, lazy loading, promoted properties, collections, service-based transforms, embedded mappings, etc.).

Reviewed changes

Copilot reviewed 99 out of 99 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
tests/ObjectMapper/ObjectMapperTest.php Integration tests verifying AutoMapper’s ObjectMapper implementation matches expected Symfony ObjectMapperInterface behavior across many scenarios.
tests/ObjectMapper/Fixtures/A.php Source fixture annotated with Symfony #[Map] attributes to test basic property mapping, transforms, conditions, and relation mapping.
tests/ObjectMapper/Fixtures/B.php Target fixture for A, used to validate property mapping results, defaults, and relations.
tests/ObjectMapper/Fixtures/C.php Fixture using #[Map(D::class)] with property-level #[Map] annotations to test attribute-driven target mapping.
tests/ObjectMapper/Fixtures/D.php Simple DTO target with constructor-promoted properties used in mapping and relation tests.
tests/ObjectMapper/Fixtures/ClassWithoutTarget.php Fixture with no mapping target to exercise error paths when no mapping is found.
tests/ObjectMapper/Fixtures/DeeperRecursion/Recursive.php Source fixture with recursive relations plus #[Map] to test deep recursion handling.
tests/ObjectMapper/Fixtures/DeeperRecursion/RecursiveDto.php DTO counterpart for Recursive, used to assert recursive mapping structure.
tests/ObjectMapper/Fixtures/DeeperRecursion/Relation.php Source relation class with #[Map(RelationDto::class)] used in deep recursion scenario.
tests/ObjectMapper/Fixtures/DeeperRecursion/RelationDto.php DTO relation type for deep recursion mapping tests.
tests/ObjectMapper/Fixtures/DefaultLazy/OrderSource.php Source fixture with #[Map] to test mapping of lazily-related user objects (skipped test notes differing behavior).
tests/ObjectMapper/Fixtures/DefaultLazy/OrderTarget.php Target DTO for lazy order/user mapping scenarios.
tests/ObjectMapper/Fixtures/DefaultLazy/UserSource.php Source for default-lazy user mapping tests, annotated with #[Map(UserTarget::class)].
tests/ObjectMapper/Fixtures/DefaultLazy/UserTarget.php Target DTO for lazy user mapping with nullable name.
tests/ObjectMapper/Fixtures/DefaultValueStdClass/TargetDto.php DTO using #[Map(if: self::isDefined)] to test optional property mapping from stdClass.
tests/ObjectMapper/Fixtures/EmbeddedMapping/Address.php Address value object used in embedded property mapping tests.
tests/ObjectMapper/Fixtures/EmbeddedMapping/User.php Target entity with embedded Address, used to verify path-based mapping (address.zipcode, etc.).
tests/ObjectMapper/Fixtures/EmbeddedMapping/UserDto.php DTO using #[Map(target: 'address.*')] to test embedded mapping into nested objects.
tests/ObjectMapper/Fixtures/Flatten/TargetUser.php Flattened DTO for user/profile mapping tests.
tests/ObjectMapper/Fixtures/Flatten/User.php Readonly source with #[Map(target: TargetUser::class)] mapping nested UserProfile into flat fields.
tests/ObjectMapper/Fixtures/Flatten/UserProfile.php Value object with static helper methods used as transform callables in flattening tests.
tests/ObjectMapper/Fixtures/HydrateObject/SourceOnly.php Constructor-based DTO using #[Map(source: ...)] to test mapping from source-only data.
tests/ObjectMapper/Fixtures/InitializedConstructor/A.php Source fixture with pre-initialized tags array for constructor-initialization tests.
tests/ObjectMapper/Fixtures/InitializedConstructor/B.php Target with internal tags initialization and mutators to validate constructor/initialization semantics.
tests/ObjectMapper/Fixtures/InitializedConstructor/C.php Target class whose constructor initializes bar to test constructor-based hydration.
tests/ObjectMapper/Fixtures/InitializedConstructor/D.php Class with computed barUpperCase and #[Map(if: false)] to test differing constructor vs. field names.
tests/ObjectMapper/Fixtures/InstanceCallback/A.php Source using #[Map(transform: [B::class, 'newInstance'])] to test instance-creating callbacks.
tests/ObjectMapper/Fixtures/InstanceCallback/B.php Target with custom constructor and static newInstance factory used in instance callback tests.
tests/ObjectMapper/Fixtures/InstanceCallbackWithArguments/A.php Source with class-level transform [B::class, 'newInstance'] to test transform callables with value and source arguments.
tests/ObjectMapper/Fixtures/InstanceCallbackWithArguments/B.php Target capturing both transformed value and source for verification.
tests/ObjectMapper/Fixtures/LazyFoo.php Custom class implementing LazyObjectInterface to test lazy-object initialization before mapping.
tests/ObjectMapper/Fixtures/MapStruct/AToBMapper.php Example mapper implementing ObjectMapperInterface plus custom #[Map] attributes to simulate MapStruct-like behavior.
tests/ObjectMapper/Fixtures/MapStruct/Map.php Local attribute extending Symfony’s Map to experiment with MapStruct-style declarative mappings.
tests/ObjectMapper/Fixtures/MapStruct/MapStructMapperMetadataFactory.php Custom ObjectMapperMetadataFactoryInterface implementation modeling MapStruct-like mapping metadata.
tests/ObjectMapper/Fixtures/MapStruct/Source.php Source struct for MapStruct-style mapper tests.
tests/ObjectMapper/Fixtures/MapStruct/Target.php Target struct to validate mapping of selected properties via MapStruct-like metadata.
tests/ObjectMapper/Fixtures/MapTargetToSource/A.php Source with constructor-promoted source property for inverse mapping tests.
tests/ObjectMapper/Fixtures/MapTargetToSource/B.php Target annotated with #[Map(source: A::class)] and constructor #[Map(source: 'source')] for target-to-source mapping.
tests/ObjectMapper/Fixtures/MultipleTargetProperty/A.php Source fixture with multiple #[Map] attributes using TargetClass to test per-target property behavior.
tests/ObjectMapper/Fixtures/MultipleTargetProperty/B.php First target DTO for multiple-target-property mapping tests.
tests/ObjectMapper/Fixtures/MultipleTargetProperty/C.php Second target DTO with additional fields, used to validate conditional per-target mapping.
tests/ObjectMapper/Fixtures/MultipleTargets/A.php Source annotated with multiple #[Map(target: ... , if: ...)] entries to select between multiple target types.
tests/ObjectMapper/Fixtures/MultipleTargets/B.php Empty target class for multiple-target mapping (unused target) tests.
tests/ObjectMapper/Fixtures/MultipleTargets/C.php Target with readonly foo used to verify correct target selection.
tests/ObjectMapper/Fixtures/MyProxy.php Simple class used as native PHP 8.4 lazy proxy target in tests.
tests/ObjectMapper/Fixtures/PartialInput/FinalInput.php Final input DTO with defaults to test partial input mapping behavior.
tests/ObjectMapper/Fixtures/PartialInput/PartialInput.php Source type annotated with #[Map(target: FinalInput::class)] to test partial updates.
tests/ObjectMapper/Fixtures/PromotedConstructor/Source.php Source with promoted properties to test mapping with constructor promotion.
tests/ObjectMapper/Fixtures/PromotedConstructor/Target.php Target with promoted properties verifying constructor-promoted mapping updates.
tests/ObjectMapper/Fixtures/PromotedConstructorWithMetadata/Source.php Source annotated with custom Map (MapStruct namespace) targeting a class with extra constructor requirements.
tests/ObjectMapper/Fixtures/PromotedConstructorWithMetadata/Target.php Target with a required extra promoted property to ensure mapping doesn’t re-instantiate when already constructed.
tests/ObjectMapper/Fixtures/ReadOnlyPromotedProperty/ReadOnlyPromotedPropertyA.php Source with readonly promoted property and #[Map] for nested readonly mapping tests.
tests/ObjectMapper/Fixtures/ReadOnlyPromotedProperty/ReadOnlyPromotedPropertyAMapped.php Target for ReadOnlyPromotedPropertyA, verifying nested mapped readonly properties.
tests/ObjectMapper/Fixtures/ReadOnlyPromotedProperty/ReadOnlyPromotedPropertyB.php Nested source with #[Map] to its mapped counterpart.
tests/ObjectMapper/Fixtures/ReadOnlyPromotedProperty/ReadOnlyPromotedPropertyBMapped.php Target for nested readonly mapping tests.
tests/ObjectMapper/Fixtures/Recursion/AB.php Source with self-referential property and #[Map(Dto::class)] to test recursion.
tests/ObjectMapper/Fixtures/Recursion/Dto.php DTO with self-referential property Dto $dto to validate recursive mapping behavior.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/LoadedValue.php Simple value object named LoadedValue used in service-loaded transform tests.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/LoadedValueService.php Service class that lazily loads and returns a LoadedValue instance.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/LoadedValueTarget.php Target DTO whose relation is populated from LoadedValueService.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/ServiceLoadedValueTransformer.php Symfony TransformCallableInterface implementation that inspects mapping metadata and returns service-loaded values.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/ValueToMap.php Source with #[Map(target: LoadedValueTarget::class)] to test class-level service-loaded relation transforms.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/ValueToMapRelation.php Related value annotated with #[Map(..., transform: ServiceLoadedValueTransformer::class)] to drive service-based relation mapping.
tests/ObjectMapper/Fixtures/ServiceLocator/A.php Source annotated to use service-locator-based transform and condition callables.
tests/ObjectMapper/Fixtures/ServiceLocator/B.php Target with property bar used to validate service-based mapping behavior.
tests/ObjectMapper/Fixtures/ServiceLocator/ConditionCallable.php Condition callable implementing Symfony’s ConditionCallableInterface for use via service locator.
tests/ObjectMapper/Fixtures/ServiceLocator/TransformCallable.php Transform callable implementing TransformCallableInterface to be used from the service locator.
tests/ObjectMapper/Fixtures/TargetTransform/SourceEntity.php Source entity with name for target-transform tests.
tests/ObjectMapper/Fixtures/TargetTransform/TargetDto.php Target DTO with class-level #[Map(..., transform: [self::class, 't'])] verifying target transform semantics.
tests/ObjectMapper/Fixtures/TransformCollection/TransformCollectionA.php Source with #[Map(transform: new MapCollection())] to test collection-wide transforms.
tests/ObjectMapper/Fixtures/TransformCollection/TransformCollectionB.php Target DTO with foo array of TransformCollectionD used to verify transformed collection element types.
tests/ObjectMapper/Fixtures/TransformCollection/TransformCollectionC.php Element source annotated to map to TransformCollectionD and remap barbaz.
tests/ObjectMapper/Fixtures/TransformCollection/TransformCollectionD.php Target element class with baz used in collection transform assertions.
tests/ObjectMapper/Fixtures/DefaultLazy/OrderSource.php (Duplicate noted earlier) Source for lazy order mapping; supports mapping with nested lazy user.
tests/ObjectMapper/Fixtures/DefaultLazy/OrderTarget.php (Duplicate noted earlier) Target for lazy order mapping (id + user).
tests/ObjectMapper/Fixtures/DefaultLazy/UserSource.php (Duplicate noted earlier) Source user for default lazy mapping.
tests/ObjectMapper/Fixtures/DefaultLazy/UserTarget.php (Duplicate noted earlier) Target user for default lazy mapping.
tests/ObjectMapper/Fixtures/HydrateObject/SourceOnly.php (Duplicate noted earlier) Constructor-only DTO mapping from source object properties.
tests/ObjectMapper/Fixtures/EmbeddedMapping/* (Summarized above individually) Fixtures for embedded mapping of address into user.
tests/ObjectMapper/Fixtures/Flatten/* (Summarized above individually) Fixtures for flattening nested profile into user DTO.
tests/ObjectMapper/Fixtures/PartialInput/* (Summarized above individually) Fixtures for partial input to final input mapping.
tests/ObjectMapper/Fixtures/PromotedConstructor*/* (Summarized above individually) Fixtures for promoted-constructor mapping tests.
tests/ObjectMapper/Fixtures/ReadOnlyPromotedProperty/* (Summarized above individually) Fixtures for readonly-promoted-property lazy loading tests.
tests/ObjectMapper/Fixtures/Recursion/* (Summarized above individually) Fixtures for recursion mapping tests.
tests/ObjectMapper/Fixtures/ServiceLoadedValue/* (Summarized above individually) Fixtures for service-loaded relation tests.
tests/ObjectMapper/Fixtures/ServiceLocator/* (Summarized above individually) Fixtures for service-locator-based transform and condition tests.
tests/ObjectMapper/Fixtures/TargetTransform/* (Summarized above individually) Fixtures for target-transform tests.
tests/ObjectMapper/Fixtures/TransformCollection/* (Summarized above individually) Fixtures for collection transform tests.
tests/ObjectMapper/Fixtures/MyProxy.php (Summarized above) Proxy subject for native PHP 8.4 lazy object tests.
tests/ObjectMapper/Fixtures/DefaultValueStdClass/TargetDto.php (Summarized above) Fixture for default-value mapping from stdClass.
tests/AutoMapperBuilder.php Test helper updated to pass an extraServices array into AutoMapper::create for registering object-mapper-related services.
src/AutoMapper.php Extends AutoMapper::create() to accept extraMapperServices and registers them into the internal service locator.
src/AttributeReference/Reference.php Adds getReferenceExpression() to encapsulate building ReflectionReference AST nodes for attributes.
src/Event/GenerateMapperEvent.php Extends event to carry a typed Provider object and to extend Symfony’s Event base for propagation control.
src/Event/PropertyMetadataEvent.php Allows the if field to be either a string or an AttributeReference\Reference to support object-mapper callbacks.
src/EventListener/ApiPlatform/JsonLdListener.php Adapts provider assignment to use the new Provider value object for JSON-LD IRI provider.
src/EventListener/Doctrine/DoctrineProviderListener.php Updates Doctrine provider selection to use the new typed Provider.
src/EventListener/MapProviderListener.php Wraps resolved provider IDs into Metadata\Provider instead of raw strings.
src/EventListener/ObjectMapper/MapListener.php New abstract base for object-mapper-specific listeners that converts Symfony #[Map] attributes into AutoMapper transformers (including service-locator and expression-language handling).
src/EventListener/ObjectMapper/MapSourceListener.php Translates source-side Symfony #[Map] attributes into PropertyMetadataEvents and sets up proper providers and conditions.
src/EventListener/ObjectMapper/MapTargetListener.php Translates target-side #[Map] attributes into metadata, including support for class-level transforms as providers.
src/Generator/MapMethodStatementsGenerator.php Adds lazy-object initialization logic and refactors provider handling to support both AutoMapper and ObjectMapper-based providers via the new Provider type.
src/Generator/PropertyConditionsGenerator.php Extends condition generation to support AttributeReference\Reference and Symfony ConditionCallableInterface-based conditions, and adjusts callable invocation signatures.
src/MapperContext.php Adds new context flags INITIALIZE_LAZY_OBJECT and LAZY_MAPPING, and types class constants for safer use.
src/Metadata/GeneratorMetadata.php Changes the provider field to the new Provider object type.
src/Metadata/MapperMetadata.php Adjusts generated mapper class names to handle anonymous classes more gracefully by normalizing their names.
src/Metadata/MetadataFactory.php Wires new object-mapper listeners, updates provider handling, and ensures PropertyMetadata construction supports Reference-backed conditions.
src/Metadata/PropertyMetadata.php Allows the if property to be either a string or a Reference for attribute-based conditions.
src/Metadata/Provider.php Introduces a typed value object representing provider type and identifier, with flags to distinguish ObjectMapper-origin providers.
src/ObjectMapper/ObjectMapper.php New implementation of Symfony’s ObjectMapperInterface delegating to AutoMapper, with support for mapping selection, transform callables, and robust error handling.
src/Transformer/ObjectMapper/TransformCallableTransformer.php New property transformer bridging Symfony TransformCallableInterface into AutoMapper’s PropertyTransformerInterface.
src/Transformer/ReferenceTransformer.php Refactors to use Reference::getReferenceExpression() and to support both AutoMapper and ObjectMapper-style transform methods (transform vs transformer).
src/Transformer/ServiceLocatorTransformer.php New transformer that resolves Symfony transform callables from the service locator and invokes them with (value, source, target).
phpstan.neon Adds an ignore rule for phpstan’s generic return-type complaint on ObjectMapper::map().
composer.json Adds symfony/object-mapper as a dev requirement and targets PHP 8.4+, aligning with new features (typed constants, native lazy objects).
CHANGELOG.md Documents support for ObjectMapper attributes and the new Symfony ObjectMapperInterface implementation (with a minor spelling issue flagged separately).

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

Copy link
Member

@Korbeil Korbeil left a comment

Choose a reason for hiding this comment

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

Having Symfony's ObjectMapper is really awesome, thanks 😍

@joelwurtz joelwurtz merged commit 98069b1 into main Jan 28, 2026
5 checks passed
@joelwurtz joelwurtz deleted the feat/object-mapper branch January 28, 2026 12:39
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.

3 participants