Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 6b5ddd5

Browse filesBrowse files
committed
[FrameworkBundle] Object Mapper component bindings
1 parent 199baf7 commit 6b5ddd5
Copy full SHA for 6b5ddd5

File tree

Expand file treeCollapse file tree

12 files changed

+224
-8
lines changed
Filter options
Expand file treeCollapse file tree

12 files changed

+224
-8
lines changed

‎src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+8Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,8 @@
125125
use Symfony\Component\Notifier\Recipient\Recipient;
126126
use Symfony\Component\Notifier\TexterInterface;
127127
use Symfony\Component\Notifier\Transport\TransportFactoryInterface as NotifierTransportFactoryInterface;
128+
use Symfony\Component\ObjectMapper\CallableInterface;
129+
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
128130
use Symfony\Component\Process\Messenger\RunProcessMessageHandler;
129131
use Symfony\Component\PropertyAccess\PropertyAccessor;
130132
use Symfony\Component\PropertyInfo\Extractor\PhpDocExtractor;
@@ -772,6 +774,12 @@ private function registerFormConfiguration(array $config, ContainerBuilder $cont
772774
if (!ContainerBuilder::willBeAvailable('symfony/translation', Translator::class, ['symfony/framework-bundle', 'symfony/form'])) {
773775
$container->removeDefinition('form.type_extension.upload.validator');
774776
}
777+
778+
if (interface_exists(ObjectMapperInterface::class)) {
779+
$loader->load('object_mapper.php');
780+
$container->registerForAutoconfiguration(CallableInterface::class)
781+
->addTag('object_mapper.callable');
782+
}
775783
}
776784

777785
private function registerHttpCacheConfiguration(array $config, ContainerBuilder $container, bool $httpMethodOverride): void
+35Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\DependencyInjection\Loader\Configurator;
13+
14+
use Symfony\Component\ObjectMapper\CallablesLocator;
15+
use Symfony\Component\ObjectMapper\Metadata\MapperMetadataFactoryInterface;
16+
use Symfony\Component\ObjectMapper\Metadata\ReflectionMapperMetadataFactory;
17+
use Symfony\Component\ObjectMapper\ObjectMapper;
18+
use Symfony\Component\ObjectMapper\ObjectMapperInterface;
19+
20+
return static function (ContainerConfigurator $container) {
21+
$container->services()
22+
->set('object_mapper.metadata_factory', ReflectionMapperMetadataFactory::class)
23+
->alias(ReflectionMapperMetadataFactory::class, 'object_mapper.metadata_factory')
24+
->alias(MapperMetadataFactoryInterface::class, 'object_mapper.metadata_factory')
25+
26+
->set('object_mapper', ObjectMapper::class)
27+
->args([
28+
service('object_mapper.metadata_factory')->ignoreOnInvalid(),
29+
service('property_accessor')->ignoreOnInvalid(),
30+
tagged_locator('object_mapper.callable'),
31+
])
32+
->alias(ObjectMapper::class, 'object_mapper')
33+
->alias(ObjectMapperInterface::class, 'object_mapper')
34+
;
35+
};
+17Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper;
13+
14+
final class ObjectMapped
15+
{
16+
public string $a;
17+
}
+21Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper;
13+
14+
use Symfony\Component\ObjectMapper\Attribute\Map;
15+
16+
#[Map(target: ObjectMapped::class)]
17+
final class ObjectToBeMapped
18+
{
19+
#[Map(transform: TransformCallable::class)]
20+
public string $a = 'nottransformed';
21+
}
+25Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper;
13+
14+
use Symfony\Component\ObjectMapper\CallableInterface;
15+
16+
/**
17+
* @implements CallableInterface<ObjectToBeMapped>
18+
*/
19+
final class TransformCallable implements CallableInterface
20+
{
21+
public function __invoke(mixed $value, object $object): mixed
22+
{
23+
return 'transformed';
24+
}
25+
}
+31Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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\Bundle\FrameworkBundle\Tests\Functional;
13+
14+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper\ObjectMapped;
15+
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper\ObjectToBeMapped;
16+
17+
/**
18+
* @author Kévin Dunglas <dunglas@gmail.com>
19+
*/
20+
class ObjectMapperTest extends AbstractWebTestCase
21+
{
22+
public function testObjectMapper(): void
23+
{
24+
static::bootKernel(['test_case' => 'ObjectMapper']);
25+
26+
/** @var Symfony\Component\ObjectMapper\ObjectMapperInterface<ObjectMapped> */
27+
$objectMapper = static::getContainer()->get('object_mapper.alias');
28+
$mapped = $objectMapper->map(new ObjectToBeMapped());
29+
$this->assertSame($mapped->a, 'transformed');
30+
}
31+
}
+16Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
use Symfony\Bundle\FrameworkBundle\FrameworkBundle;
13+
14+
return [
15+
new FrameworkBundle(),
16+
];
+9Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
imports:
2+
- { resource: ../config/default.yml }
3+
4+
services:
5+
object_mapper.alias:
6+
alias: object_mapper
7+
public: true
8+
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\ObjectMapper\TransformCallable:
9+
autoconfigure: true

‎src/Symfony/Bundle/FrameworkBundle/composer.json

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
"symfony/security-bundle": "^6.4|^7.0",
6161
"symfony/semaphore": "^6.4|^7.0",
6262
"symfony/serializer": "^6.4|^7.0",
63+
"symfony/object-mapper": "^6.4|^7.0",
6364
"symfony/stopwatch": "^6.4|^7.0",
6465
"symfony/string": "^6.4|^7.0",
6566
"symfony/translation": "^6.4|^7.0",

‎src/Symfony/Component/ObjectMapper/Attribute/Map.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/Attribute/Map.php
+6-4Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@
1111

1212
namespace Symfony\Component\ObjectMapper\Attribute;
1313

14+
use Symfony\Component\ObjectMapper\CallableInterface;
15+
1416
/**
1517
* Configures a class or a property to map to.
1618
*
@@ -24,10 +26,10 @@
2426
readonly class Map
2527
{
2628
/**
27-
* @param string|class-string|null $source The property or the class to map from
28-
* @param string|class-string|null $target The property or the class to map to
29-
* @param string|bool|callable(mixed $value, object $object): bool|null $if A boolean, Symfony service name or a callable that instructs whether to map
30-
* @param (string|callable(mixed $value, object $object): mixed)|(string|callable(mixed $value, object $object): mixed)[]|null $transform A Symfony service name or a callable that transform the value during mapping
29+
* @param string|class-string|null $source The property or the class to map from
30+
* @param string|class-string|null $target The property or the class to map to
31+
* @param string|bool|callable(mixed $value, object $object): bool|null $if A boolean, Symfony service name or a callable that instructs whether to map
32+
* @param (CallableInterface|string|callable(mixed $value, object $object): mixed)|(CallableInterface|string|callable(mixed $value, object $object): mixed)[]|null $transform A Symfony service name or a callable that transform the value during mapping
3133
*/
3234
public function __construct(
3335
public ?string $target = null,
+30Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
* An interface representing that gets called by "Map::if" and "Map::transform".
16+
*
17+
* @template T of object
18+
*
19+
* @experimental
20+
*
21+
* {@see Symfony\Component\ObjectMapper\Attribute\Map}
22+
*/
23+
interface CallableInterface
24+
{
25+
/**
26+
* @param mixed $value the value being mapped
27+
* @param T $object the object we're working on
28+
*/
29+
public function __invoke(mixed $value, object $object): mixed;
30+
}

‎src/Symfony/Component/ObjectMapper/ObjectMapper.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/ObjectMapper/ObjectMapper.php
+25-4Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
namespace Symfony\Component\ObjectMapper;
1313

14+
use Psr\Container\ContainerInterface;
1415
use Symfony\Component\ObjectMapper\Exception\MappingException;
1516
use Symfony\Component\ObjectMapper\Exception\MappingTransformException;
1617
use Symfony\Component\ObjectMapper\Exception\ReflectionException;
@@ -32,9 +33,13 @@
3233
*/
3334
final class ObjectMapper implements ObjectMapperInterface
3435
{
36+
/**
37+
* @param ContainerInterface<CallableInterface> $callableLocator
38+
*/
3539
public function __construct(
3640
private readonly MapperMetadataFactoryInterface $metadataFactory = new ReflectionMapperMetadataFactory(),
3741
private readonly ?PropertyAccessorInterface $propertyAccessor = null,
42+
private readonly ?ContainerInterface $callableLocator = null,
3843
) {
3944
}
4045

@@ -114,7 +119,7 @@ public function map(object $source, object|string|null $target = null): object
114119
$propertyName = $property->getName();
115120
$mappings = $this->metadataFactory->create($source, $propertyName);
116121
foreach ($mappings as $mapping) {
117-
if (($fn = $mapping->if) && !$this->call($fn, null, $source)) {
122+
if (($if = $mapping->if) && ($fn = $this->getCallable($if)) && !$this->call($fn, null, $source)) {
118123
continue;
119124
}
120125

@@ -228,7 +233,7 @@ private function getMapTarget(array $metadata, mixed $value, object $source): ?M
228233
{
229234
$mapTo = null;
230235
foreach ($metadata as $mapAttribute) {
231-
if (($fn = $mapAttribute->if) && !$this->call($fn, $value, $source)) {
236+
if (($if = $mapAttribute->if) && ($fn = $this->getCallable($if)) && !$this->call($fn, $value, $source)) {
232237
continue;
233238
}
234239

@@ -251,11 +256,27 @@ private function applyTransforms(Mapping $map, mixed $value, object $object): mi
251256
}
252257

253258
foreach ($transforms as $transform) {
254-
if (\is_callable($transform)) {
255-
$value = $this->call($transform, $value, $object);
259+
if ($fn = $this->getCallable($transform)) {
260+
$value = $this->call($fn, $value, $object);
256261
}
257262
}
258263

259264
return $value;
260265
}
266+
267+
/**
268+
* @param (CallableInterface|string|callable(mixed $value, object $object): mixed) $fn
269+
*/
270+
private function getCallable(string|callable|CallableInterface $fn): ?callable
271+
{
272+
if (is_callable($fn)) {
273+
return $fn;
274+
}
275+
276+
if ($this->callableLocator?->has($fn)) {
277+
return $this->callableLocator->get($fn);
278+
}
279+
280+
return null;
281+
}
261282
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.