Skip to content

Commit 62b4ca0

Browse files
committed
fix(ProxyGenerator): Prevent Doctrine entities from being proxied.
1 parent aa41106 commit 62b4ca0

File tree

8 files changed

+140
-4
lines changed

8 files changed

+140
-4
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
* fix: Fix deprecations.
1616
* refactor(`ProxyGeneratorInterface`): Use class as input. Remove dependency on `ObjectToObjectMetadata`.
1717
* refactor(`ProxyGeneratorInterface`): Move proxy namespace to top-level namespace.
18+
* fix(`ProxyGenerator`): Prevent Doctrine entities from being proxied.
1819

1920
## 0.7.3
2021

config/services.php

+9
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
use Rekalogika\Mapper\Mapping\Implementation\WarmableMappingFactory;
2828
use Rekalogika\Mapper\Mapping\MappingFactoryInterface;
2929
use Rekalogika\Mapper\ObjectCache\Implementation\ObjectCacheFactory;
30+
use Rekalogika\Mapper\Proxy\Implementation\DoctrineProxyGenerator;
3031
use Rekalogika\Mapper\Proxy\Implementation\ProxyGenerator;
3132
use Rekalogika\Mapper\Proxy\Implementation\ProxyRegistry;
3233
use Rekalogika\Mapper\SubMapper\Implementation\SubMapperFactory;
@@ -377,6 +378,14 @@
377378
$services
378379
->set('rekalogika.mapper.proxy_generator', ProxyGenerator::class);
379380

381+
$services
382+
->set('rekalogika.mapper.proxy_generator.doctrine', DoctrineProxyGenerator::class)
383+
->decorate('rekalogika.mapper.proxy_generator')
384+
->args([
385+
service('.inner'),
386+
service('doctrine'),
387+
]);
388+
380389
$services
381390
->set('rekalogika.mapper.proxy_registry', ProxyRegistry::class)
382391
->args([

src/Proxy/Exception/ProxyNotSupportedException.php

+10-3
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,11 @@ class ProxyNotSupportedException extends RuntimeException
2222
/**
2323
* @param class-string $class
2424
*/
25-
public function __construct(string $class, \Throwable $previous)
26-
{
25+
public function __construct(
26+
string $class,
27+
?string $reason = null,
28+
?\Throwable $previous = null
29+
) {
2730
parent::__construct(
2831
sprintf(
2932
'Creating a proxy for class "%s" is not supported.',
@@ -32,7 +35,11 @@ public function __construct(string $class, \Throwable $previous)
3235
previous: $previous
3336
);
3437

35-
$this->reason = $previous->getMessage();
38+
$this->reason = $reason ?? $previous?->getMessage() ?? sprintf(
39+
'Reason is not provided, thrown by "%s", line %d.',
40+
$this->getFile(),
41+
$this->getLine()
42+
);
3643
}
3744

3845
public function getReason(): string
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Proxy\Implementation;
15+
16+
use Doctrine\Persistence\ManagerRegistry;
17+
use Rekalogika\Mapper\Proxy\Exception\ProxyNotSupportedException;
18+
use Rekalogika\Mapper\Proxy\ProxyGeneratorInterface;
19+
use Rekalogika\Mapper\Proxy\ProxySpecification;
20+
21+
/**
22+
* Prevent proxy creation for Doctrine entities.
23+
*/
24+
final class DoctrineProxyGenerator implements ProxyGeneratorInterface
25+
{
26+
public function __construct(
27+
private ProxyGeneratorInterface $decorated,
28+
private ManagerRegistry $managerRegistry
29+
) {
30+
}
31+
32+
public function generateProxy(string $class): ProxySpecification
33+
{
34+
$manager = $this->managerRegistry->getManagerForClass($class);
35+
36+
if ($manager) {
37+
throw new ProxyNotSupportedException($class, reason: 'Doctrine entities do not support proxying.');
38+
}
39+
40+
return $this->decorated->generateProxy($class);
41+
}
42+
}

src/Proxy/Implementation/ProxyGenerator.php

+1-1
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public function generateProxy(string $class): ProxySpecification
2929
try {
3030
$proxyCode = $this->generateProxyCode($class);
3131
} catch (LogicException $e) {
32-
throw new ProxyNotSupportedException($class, $e);
32+
throw new ProxyNotSupportedException($class, previous: $e);
3333
}
3434

3535
return new ProxySpecification($proxyClass, $proxyCode);
+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\Doctrine;
15+
16+
use Doctrine\ORM\Mapping as ORM;
17+
18+
#[ORM\Entity]
19+
class SimpleEntity
20+
{
21+
#[ORM\Id]
22+
#[ORM\GeneratedValue]
23+
#[ORM\Column()]
24+
private ?int $id = null;
25+
26+
#[ORM\Column()]
27+
private ?string $name = null;
28+
29+
public function getId(): int
30+
{
31+
if ($this->id === null) {
32+
throw new \LogicException('The identifier is not set.');
33+
}
34+
return $this->id;
35+
}
36+
37+
public function getName(): ?string
38+
{
39+
return $this->name;
40+
}
41+
42+
public function setName(?string $name): self
43+
{
44+
$this->name = $name;
45+
46+
return $this;
47+
}
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
/*
6+
* This file is part of rekalogika/mapper package.
7+
*
8+
* (c) Priyadi Iman Nurcahyo <https://rekalogika.dev>
9+
*
10+
* For the full copyright and license information, please view the LICENSE file
11+
* that was distributed with this source code.
12+
*/
13+
14+
namespace Rekalogika\Mapper\Tests\Fixtures\Doctrine;
15+
16+
class SimpleEntityInputDto
17+
{
18+
public ?string $name = null;
19+
}

tests/IntegrationTest/DoctrineTest.php

+10
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
use Rekalogika\Mapper\Tests\Fixtures\Doctrine\EntityWithMultipleIdentifier;
1818
use Rekalogika\Mapper\Tests\Fixtures\Doctrine\EntityWithSingleIdentifier;
1919
use Rekalogika\Mapper\Tests\Fixtures\Doctrine\EntityWithSingleIdentifierDto;
20+
use Rekalogika\Mapper\Tests\Fixtures\Doctrine\SimpleEntity;
21+
use Rekalogika\Mapper\Tests\Fixtures\Doctrine\SimpleEntityInputDto;
2022
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\EagerPropertiesResolverInterface;
2123
use Rekalogika\Mapper\Transformer\EagerPropertiesResolver\Implementation\DoctrineEagerPropertiesResolver;
2224
use Symfony\Component\VarExporter\LazyObjectInterface;
@@ -101,5 +103,13 @@ public function testCompositeId(): void
101103
$this->assertEquals(['id1', 'id2'], $eagerProperties);
102104
}
103105

106+
public function testInputDtoToEntityMapping(): void
107+
{
108+
$input = new SimpleEntityInputDto();
109+
$input->name = 'my-name';
110+
111+
$entity = $this->mapper->map($input, SimpleEntity::class);
112+
$this->assertNotInstanceOf(LazyObjectInterface::class, $entity);
113+
}
104114

105115
}

0 commit comments

Comments
 (0)