├── Attribute └── Map.php ├── CHANGELOG.md ├── Condition └── TargetClass.php ├── ConditionCallableInterface.php ├── Exception ├── ExceptionInterface.php ├── MappingException.php ├── MappingTransformException.php └── RuntimeException.php ├── LICENSE ├── Metadata ├── Mapping.php ├── ObjectMapperMetadataFactoryInterface.php └── ReflectionObjectMapperMetadataFactory.php ├── ObjectMapper.php ├── ObjectMapperInterface.php ├── README.md ├── TransformCallableInterface.php └── composer.json /Attribute/Map.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Attribute; 13 | 14 | /** 15 | * Configures a class or a property to map to. 16 | * 17 | * @experimental 18 | * 19 | * @author Antoine Bluchet 20 | */ 21 | #[\Attribute(\Attribute::TARGET_CLASS | \Attribute::TARGET_PROPERTY | \Attribute::IS_REPEATABLE)] 22 | class Map 23 | { 24 | /** 25 | * @param string|class-string|null $source The property or the class to map from 26 | * @param string|class-string|null $target The property or the class to map to 27 | * @param string|bool|callable(mixed, object): bool|null $if A boolean, a service id or a callable that instructs whether to map 28 | * @param (string|callable(mixed, object): mixed)|(string|callable(mixed, object): mixed)[]|null $transform A service id or a callable that transforms the value during mapping 29 | */ 30 | public function __construct( 31 | public readonly ?string $target = null, 32 | public readonly ?string $source = null, 33 | public readonly mixed $if = null, 34 | public readonly mixed $transform = null, 35 | ) { 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | CHANGELOG 2 | ========= 3 | 4 | 7.3 5 | --- 6 | 7 | * Add the component as experimental 8 | -------------------------------------------------------------------------------- /Condition/TargetClass.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Condition; 13 | 14 | use Symfony\Component\ObjectMapper\ConditionCallableInterface; 15 | 16 | /** 17 | * @template T of object 18 | * 19 | * @implements ConditionCallableInterface 20 | */ 21 | final class TargetClass implements ConditionCallableInterface 22 | { 23 | /** 24 | * @param class-string $className 25 | */ 26 | public function __construct(private readonly string $className) 27 | { 28 | } 29 | 30 | public function __invoke(mixed $value, object $source, ?object $target): bool 31 | { 32 | return $target instanceof $this->className; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /ConditionCallableInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper; 13 | 14 | /** 15 | * Service used by "Map::if". 16 | * 17 | * @template T of object 18 | * @template T2 of object 19 | * 20 | * @experimental 21 | * 22 | * {@see Symfony\Component\ObjectMapper\Attribute\Map} 23 | */ 24 | interface ConditionCallableInterface 25 | { 26 | /** 27 | * @param mixed $value The value being mapped 28 | * @param T $source The object we're working on 29 | * @param T2|null $target The target we're mapping to 30 | */ 31 | public function __invoke(mixed $value, object $source, ?object $target): bool; 32 | } 33 | -------------------------------------------------------------------------------- /Exception/ExceptionInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Exception; 13 | 14 | /** 15 | * @experimental 16 | * 17 | * @author Antoine Bluchet 18 | */ 19 | interface ExceptionInterface extends \Throwable 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/MappingException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Exception; 13 | 14 | /** 15 | * @experimental 16 | * 17 | * @author Antoine Bluchet 18 | */ 19 | class MappingException extends RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/MappingTransformException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Exception; 13 | 14 | /** 15 | * @experimental 16 | * 17 | * @author Antoine Bluchet 18 | */ 19 | final class MappingTransformException extends RuntimeException 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /Exception/RuntimeException.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Exception; 13 | 14 | /** 15 | * @experimental 16 | * 17 | * @author Antoine Bluchet 18 | */ 19 | class RuntimeException extends \RuntimeException implements ExceptionInterface 20 | { 21 | } 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025-present Fabien Potencier 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Metadata/Mapping.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Metadata; 13 | 14 | use Symfony\Component\ObjectMapper\Attribute\Map; 15 | 16 | /** 17 | * Configures a class or a property to map to. 18 | * 19 | * @internal 20 | * 21 | * @author Antoine Bluchet 22 | */ 23 | final class Mapping extends Map 24 | { 25 | } 26 | -------------------------------------------------------------------------------- /Metadata/ObjectMapperMetadataFactoryInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Metadata; 13 | 14 | /** 15 | * Factory to create Mapper metadata. 16 | * 17 | * @experimental 18 | * 19 | * @author Antoine Bluchet 20 | */ 21 | interface ObjectMapperMetadataFactoryInterface 22 | { 23 | /** 24 | * @param array $context 25 | * 26 | * @return list 27 | */ 28 | public function create(object $object, ?string $property = null, array $context = []): array; 29 | } 30 | -------------------------------------------------------------------------------- /Metadata/ReflectionObjectMapperMetadataFactory.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper\Metadata; 13 | 14 | use Symfony\Component\ObjectMapper\Attribute\Map; 15 | use Symfony\Component\ObjectMapper\Exception\MappingException; 16 | 17 | /** 18 | * @internal 19 | * 20 | * @author Antoine Bluchet 21 | */ 22 | final class ReflectionObjectMapperMetadataFactory implements ObjectMapperMetadataFactoryInterface 23 | { 24 | public function create(object $object, ?string $property = null, array $context = []): array 25 | { 26 | try { 27 | $refl = new \ReflectionClass($object); 28 | $mapTo = []; 29 | foreach (($property ? $refl->getProperty($property) : $refl)->getAttributes(Map::class, \ReflectionAttribute::IS_INSTANCEOF) as $mapAttribute) { 30 | $map = $mapAttribute->newInstance(); 31 | $mapTo[] = new Mapping($map->target, $map->source, $map->if, $map->transform); 32 | } 33 | 34 | return $mapTo; 35 | } catch (\ReflectionException $e) { 36 | throw new MappingException($e->getMessage(), $e->getCode(), $e); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /ObjectMapper.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper; 13 | 14 | use Psr\Container\ContainerInterface; 15 | use Symfony\Component\ObjectMapper\Exception\MappingException; 16 | use Symfony\Component\ObjectMapper\Exception\MappingTransformException; 17 | use Symfony\Component\ObjectMapper\Metadata\Mapping; 18 | use Symfony\Component\ObjectMapper\Metadata\ObjectMapperMetadataFactoryInterface; 19 | use Symfony\Component\ObjectMapper\Metadata\ReflectionObjectMapperMetadataFactory; 20 | use Symfony\Component\PropertyAccess\PropertyAccessorInterface; 21 | 22 | /** 23 | * Object to object mapper. 24 | * 25 | * @experimental 26 | * 27 | * @author Antoine Bluchet 28 | */ 29 | final class ObjectMapper implements ObjectMapperInterface 30 | { 31 | /** 32 | * Tracks recursive references. 33 | */ 34 | private ?\SplObjectStorage $objectMap = null; 35 | 36 | public function __construct( 37 | private readonly ObjectMapperMetadataFactoryInterface $metadataFactory = new ReflectionObjectMapperMetadataFactory(), 38 | private readonly ?PropertyAccessorInterface $propertyAccessor = null, 39 | private readonly ?ContainerInterface $transformCallableLocator = null, 40 | private readonly ?ContainerInterface $conditionCallableLocator = null, 41 | ) { 42 | } 43 | 44 | public function map(object $source, object|string|null $target = null): object 45 | { 46 | $objectMapInitialized = false; 47 | if (null === $this->objectMap) { 48 | $this->objectMap = new \SplObjectStorage(); 49 | $objectMapInitialized = true; 50 | } 51 | 52 | $metadata = $this->metadataFactory->create($source); 53 | $map = $this->getMapTarget($metadata, null, $source, null); 54 | $target ??= $map?->target; 55 | $mappingToObject = \is_object($target); 56 | 57 | if (!$target) { 58 | throw new MappingException(\sprintf('Mapping target not found for source "%s".', get_debug_type($source))); 59 | } 60 | 61 | if (\is_string($target) && !class_exists($target)) { 62 | throw new MappingException(\sprintf('Mapping target class "%s" does not exist for source "%s".', $target, get_debug_type($source))); 63 | } 64 | 65 | try { 66 | $targetRefl = new \ReflectionClass($target); 67 | } catch (\ReflectionException $e) { 68 | throw new MappingException($e->getMessage(), $e->getCode(), $e); 69 | } 70 | 71 | $mappedTarget = $mappingToObject ? $target : $targetRefl->newInstanceWithoutConstructor(); 72 | if ($map && $map->transform) { 73 | $mappedTarget = $this->applyTransforms($map, $mappedTarget, $mappedTarget, null); 74 | 75 | if (!\is_object($mappedTarget)) { 76 | throw new MappingTransformException(\sprintf('Cannot map "%s" to a non-object target of type "%s".', get_debug_type($source), get_debug_type($mappedTarget))); 77 | } 78 | } 79 | 80 | if (!is_a($mappedTarget, $targetRefl->getName(), false)) { 81 | throw new MappingException(\sprintf('Expected the mapped object to be an instance of "%s" but got "%s".', $targetRefl->getName(), get_debug_type($mappedTarget))); 82 | } 83 | 84 | $this->objectMap[$source] = $mappedTarget; 85 | $ctorArguments = []; 86 | $constructor = $targetRefl->getConstructor(); 87 | foreach ($constructor?->getParameters() ?? [] as $parameter) { 88 | if (!$parameter->isPromoted()) { 89 | continue; 90 | } 91 | 92 | $parameterName = $parameter->getName(); 93 | $property = $targetRefl->getProperty($parameterName); 94 | 95 | if ($property->isReadOnly() && $property->isInitialized($mappedTarget)) { 96 | continue; 97 | } 98 | 99 | // this may be filled later on see storeValue 100 | $ctorArguments[$parameterName] = $parameter->isDefaultValueAvailable() ? $parameter->getDefaultValue() : null; 101 | } 102 | 103 | $readMetadataFrom = $source; 104 | $refl = $this->getSourceReflectionClass($source, $targetRefl); 105 | 106 | // When source contains no metadata, we read metadata on the target instead 107 | if ($refl === $targetRefl) { 108 | $readMetadataFrom = $mappedTarget; 109 | } 110 | 111 | $mapToProperties = []; 112 | foreach ($refl->getProperties() as $property) { 113 | if ($property->isStatic()) { 114 | continue; 115 | } 116 | 117 | $propertyName = $property->getName(); 118 | $mappings = $this->metadataFactory->create($readMetadataFrom, $propertyName); 119 | foreach ($mappings as $mapping) { 120 | $sourcePropertyName = $propertyName; 121 | if ($mapping->source && (!$refl->hasProperty($propertyName) || !isset($source->$propertyName))) { 122 | $sourcePropertyName = $mapping->source; 123 | } 124 | 125 | if (false === $if = $mapping->if) { 126 | continue; 127 | } 128 | 129 | $value = $this->getRawValue($source, $sourcePropertyName); 130 | if ($if && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source, $mappedTarget)) { 131 | continue; 132 | } 133 | 134 | $targetPropertyName = $mapping->target ?? $propertyName; 135 | if (!$targetRefl->hasProperty($targetPropertyName)) { 136 | continue; 137 | } 138 | 139 | $value = $this->getSourceValue($source, $mappedTarget, $value, $this->objectMap, $mapping); 140 | $this->storeValue($targetPropertyName, $mapToProperties, $ctorArguments, $value); 141 | } 142 | 143 | if (!$mappings && $targetRefl->hasProperty($propertyName)) { 144 | $value = $this->getSourceValue($source, $mappedTarget, $this->getRawValue($source, $propertyName), $this->objectMap); 145 | $this->storeValue($propertyName, $mapToProperties, $ctorArguments, $value); 146 | } 147 | } 148 | 149 | if (!$mappingToObject && $ctorArguments && $constructor) { 150 | try { 151 | $mappedTarget->__construct(...$ctorArguments); 152 | } catch (\ReflectionException $e) { 153 | throw new MappingException($e->getMessage(), $e->getCode(), $e); 154 | } 155 | } 156 | 157 | foreach ($mapToProperties as $property => $value) { 158 | $this->propertyAccessor ? $this->propertyAccessor->setValue($mappedTarget, $property, $value) : ($mappedTarget->{$property} = $value); 159 | } 160 | 161 | if ($objectMapInitialized) { 162 | $this->objectMap = null; 163 | } 164 | 165 | return $mappedTarget; 166 | } 167 | 168 | private function getRawValue(object $source, string $propertyName): mixed 169 | { 170 | return $this->propertyAccessor ? $this->propertyAccessor->getValue($source, $propertyName) : $source->{$propertyName}; 171 | } 172 | 173 | private function getSourceValue(object $source, object $target, mixed $value, \SplObjectStorage $objectMap, ?Mapping $mapping = null): mixed 174 | { 175 | if ($mapping?->transform) { 176 | $value = $this->applyTransforms($mapping, $value, $source, $target); 177 | } 178 | 179 | if ( 180 | \is_object($value) 181 | && ($innerMetadata = $this->metadataFactory->create($value)) 182 | && ($mapTo = $this->getMapTarget($innerMetadata, $value, $source, $target)) 183 | && (\is_string($mapTo->target) && class_exists($mapTo->target)) 184 | ) { 185 | $value = $this->applyTransforms($mapTo, $value, $source, $target); 186 | 187 | if ($value === $source) { 188 | $value = $target; 189 | } elseif ($objectMap->contains($value)) { 190 | $value = $objectMap[$value]; 191 | } else { 192 | $value = $this->map($value, $mapTo->target); 193 | } 194 | } 195 | 196 | return $value; 197 | } 198 | 199 | /** 200 | * Store the value either the constructor arguments or as a property to be mapped. 201 | * 202 | * @param array $mapToProperties 203 | * @param array $ctorArguments 204 | */ 205 | private function storeValue(string $propertyName, array &$mapToProperties, array &$ctorArguments, mixed $value): void 206 | { 207 | if (\array_key_exists($propertyName, $ctorArguments)) { 208 | $ctorArguments[$propertyName] = $value; 209 | 210 | return; 211 | } 212 | 213 | $mapToProperties[$propertyName] = $value; 214 | } 215 | 216 | /** 217 | * @param callable(): mixed $fn 218 | */ 219 | private function call(callable $fn, mixed $value, object $source, ?object $target = null): mixed 220 | { 221 | if (\is_string($fn)) { 222 | return \call_user_func($fn, $value); 223 | } 224 | 225 | return $fn($value, $source, $target); 226 | } 227 | 228 | /** 229 | * @param Mapping[] $metadata 230 | */ 231 | private function getMapTarget(array $metadata, mixed $value, object $source, ?object $target): ?Mapping 232 | { 233 | $mapTo = null; 234 | foreach ($metadata as $mapAttribute) { 235 | if (($if = $mapAttribute->if) && ($fn = $this->getCallable($if, $this->conditionCallableLocator)) && !$this->call($fn, $value, $source, $target)) { 236 | continue; 237 | } 238 | 239 | $mapTo = $mapAttribute; 240 | } 241 | 242 | return $mapTo; 243 | } 244 | 245 | private function applyTransforms(Mapping $map, mixed $value, object $source, ?object $target): mixed 246 | { 247 | if (!$transforms = $map->transform) { 248 | return $value; 249 | } 250 | 251 | if (\is_callable($transforms)) { 252 | $transforms = [$transforms]; 253 | } elseif (!\is_array($transforms)) { 254 | $transforms = [$transforms]; 255 | } 256 | 257 | foreach ($transforms as $transform) { 258 | if ($fn = $this->getCallable($transform, $this->transformCallableLocator)) { 259 | $value = $this->call($fn, $value, $source, $target); 260 | } 261 | } 262 | 263 | return $value; 264 | } 265 | 266 | /** 267 | * @param (string|callable(mixed $value, object $object): mixed) $fn 268 | */ 269 | private function getCallable(string|callable $fn, ?ContainerInterface $locator = null): ?callable 270 | { 271 | if (\is_callable($fn)) { 272 | return $fn; 273 | } 274 | 275 | if ($locator?->has($fn)) { 276 | return $locator->get($fn); 277 | } 278 | 279 | return null; 280 | } 281 | 282 | /** 283 | * @param \ReflectionClass $targetRefl 284 | * 285 | * @return \ReflectionClass 286 | */ 287 | private function getSourceReflectionClass(object $source, \ReflectionClass $targetRefl): \ReflectionClass 288 | { 289 | $metadata = $this->metadataFactory->create($source); 290 | try { 291 | $refl = new \ReflectionClass($source); 292 | } catch (\ReflectionException $e) { 293 | throw new MappingException($e->getMessage(), $e->getCode(), $e); 294 | } 295 | 296 | if ($metadata) { 297 | return $refl; 298 | } 299 | 300 | foreach ($refl->getProperties() as $property) { 301 | if ($this->metadataFactory->create($source, $property->getName())) { 302 | return $refl; 303 | } 304 | } 305 | 306 | return $targetRefl; 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /ObjectMapperInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper; 13 | 14 | use Symfony\Component\ObjectMapper\Exception\MappingException; 15 | use Symfony\Component\ObjectMapper\Exception\MappingTransformException; 16 | 17 | /** 18 | * Object to object mapper. 19 | * 20 | * @experimental 21 | * 22 | * @author Antoine Bluchet 23 | */ 24 | interface ObjectMapperInterface 25 | { 26 | /** 27 | * @template T of object 28 | * 29 | * @param object $source The object to map from 30 | * @param T|class-string|null $target The object or class to map to 31 | * 32 | * @return T 33 | * 34 | * @throws MappingException When the mapping configuration is wrong 35 | * @throws MappingTransformException When a transformation on an object does not return an object 36 | */ 37 | public function map(object $source, object|string|null $target = null): object; 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Object Mapper Component 2 | ======================= 3 | 4 | The Object Mapper component allows you to map an object to another object, 5 | facilitating the mapping using attributes. 6 | 7 | **This Component is experimental**. 8 | [Experimental features](https://symfony.com/doc/current/contributing/code/experimental.html) 9 | are not covered by Symfony's 10 | [Backward Compatibility Promise](https://symfony.com/doc/current/contributing/code/bc.html). 11 | 12 | Resources 13 | --------- 14 | 15 | * [Documentation](https://symfony.com/doc/current/components/object-mapper.html) 16 | * [Contributing](https://symfony.com/doc/current/contributing/index.html) 17 | * [Report issues](https://github.com/symfony/symfony/issues) and 18 | [send Pull Requests](https://github.com/symfony/symfony/pulls) 19 | in the [main Symfony repository](https://github.com/symfony/symfony) 20 | -------------------------------------------------------------------------------- /TransformCallableInterface.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | namespace Symfony\Component\ObjectMapper; 13 | 14 | /** 15 | * Service used by "Map::transform". 16 | * 17 | * @template T of object 18 | * @template T2 of object 19 | * 20 | * @experimental 21 | * 22 | * {@see Symfony\Component\ObjectMapper\Attribute\Map} 23 | */ 24 | interface TransformCallableInterface 25 | { 26 | /** 27 | * @param mixed $value The value being mapped 28 | * @param T $source The object we're working on 29 | * @param T2|null $target The target we're mapping to 30 | */ 31 | public function __invoke(mixed $value, object $source, ?object $target): mixed; 32 | } 33 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "symfony/object-mapper", 3 | "type": "library", 4 | "description": "Provides a way to map an object to another object", 5 | "keywords": [], 6 | "homepage": "https://symfony.com", 7 | "license": "MIT", 8 | "authors": [ 9 | { 10 | "name": "Fabien Potencier", 11 | "email": "fabien@symfony.com" 12 | }, 13 | { 14 | "name": "Symfony Community", 15 | "homepage": "https://symfony.com/contributors" 16 | } 17 | ], 18 | "require": { 19 | "php": ">=8.2", 20 | "psr/container": "^2.0" 21 | }, 22 | "require-dev": { 23 | "symfony/property-access": "^7.2" 24 | }, 25 | "autoload": { 26 | "psr-4": { 27 | "Symfony\\Component\\ObjectMapper\\": "" 28 | }, 29 | "exclude-from-classmap": [ 30 | "/Tests/" 31 | ] 32 | }, 33 | "conflict": { 34 | "symfony/property-access": "<7.2" 35 | }, 36 | "minimum-stability": "dev" 37 | } 38 | --------------------------------------------------------------------------------