Skip to content

Commit ca18cb5

Browse files
committed
WIP
1 parent 7e49091 commit ca18cb5

File tree

4 files changed

+133
-107
lines changed

4 files changed

+133
-107
lines changed

config/services/denormalizer.xml

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,18 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<services>
8+
<service id="sylius_import_export.denormalizer.entity_relation" class="Sylius\ImportExport\Denormalizer\EntityRelationDenormalizer">
9+
<argument type="service" id="doctrine.orm.entity_manager" />
10+
<tag name="serializer.normalizer" priority="64" />
11+
</service>
12+
813
<service id="sylius_import_export.denormalizer.relation_resolver" class="Sylius\ImportExport\Denormalizer\DoctrineRelationResolver">
914
<argument type="service" id="doctrine.orm.entity_manager" />
15+
<argument type="service" id="serializer" />
1016
</service>
1117

1218
<service id="sylius_import_export.denormalizer.default_resource" class="Sylius\ImportExport\Denormalizer\DefaultResourceDenormalizer">
1319
<argument type="service" id="serializer" />
14-
<argument type="service" id="sylius_import_export.denormalizer.relation_resolver" />
15-
<argument type="service" id="doctrine.orm.entity_manager" />
1620
</service>
1721

1822
<service id="sylius_import_export.denormalizer.registry" class="Sylius\ImportExport\Denormalizer\DenormalizerRegistry">

src/Denormalizer/DefaultResourceDenormalizer.php

Lines changed: 9 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -13,95 +13,33 @@
1313

1414
namespace Sylius\ImportExport\Denormalizer;
1515

16-
use Doctrine\ORM\EntityManagerInterface;
1716
use Sylius\ImportExport\Serializer\ExportAwareItemNormalizer;
17+
use Symfony\Component\Serializer\Normalizer\AbstractObjectNormalizer;
18+
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
1819
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
19-
use Webmozart\Assert\Assert;
2020

2121
final readonly class DefaultResourceDenormalizer implements ResourceDenormalizerInterface
2222
{
2323
public function __construct(
2424
private DenormalizerInterface $denormalizer,
25-
private RelationResolverInterface $relationResolver,
26-
private EntityManagerInterface $entityManager,
2725
) {
2826
}
2927

3028
public function denormalize(array $data, string $resourceClass): object
3129
{
32-
$metadata = $this->entityManager->getClassMetadata($resourceClass);
33-
$processedData = [];
34-
35-
foreach ($data as $field => $value) {
36-
if (null === $value || (is_string($value) && '' === $value)) {
37-
continue;
38-
}
39-
40-
if ($metadata->hasAssociation($field)) {
41-
$associationMapping = $metadata->getAssociationMapping($field);
42-
$targetEntity = $associationMapping['targetEntity'];
43-
Assert::string($targetEntity);
44-
45-
if ($metadata->isCollectionValuedAssociation($field)) {
46-
if (is_array($value) && !empty($value)) {
47-
$resolvedEntities = $this->relationResolver->resolveCollection($targetEntity, $value);
48-
$filteredEntities = array_filter($resolvedEntities); // Remove null values
49-
if (!empty($filteredEntities)) {
50-
$processedData[$field] = $filteredEntities;
51-
}
52-
// Don't add the field at all if no entities found
53-
}
54-
} else {
55-
if (is_array($value) && !empty($value)) {
56-
$resolvedEntity = $this->relationResolver->resolveEntity($targetEntity, $value);
57-
if (null !== $resolvedEntity) {
58-
$processedData[$field] = $resolvedEntity;
59-
}
60-
// Don't add the field at all if entity not found
61-
}
62-
}
63-
} elseif (is_array($value)) {
64-
$processedData[$field] = $this->processNestedArray($value);
65-
} else {
66-
$processedData[$field] = $value;
67-
}
68-
}
69-
70-
// Ensure all entity relationships are properly managed before denormalization
71-
foreach ($processedData as $field => $value) {
72-
if ($metadata->hasAssociation($field)) {
73-
if ($metadata->isCollectionValuedAssociation($field) && is_array($value)) {
74-
foreach ($value as $entity) {
75-
if (is_object($entity) && !$this->entityManager->contains($entity)) {
76-
throw new \RuntimeException(sprintf(
77-
'Entity %s in collection %s is not managed by EntityManager',
78-
get_class($entity),
79-
$field
80-
));
81-
}
82-
}
83-
} elseif (is_object($value) && !$this->entityManager->contains($value)) {
84-
throw new \RuntimeException(sprintf(
85-
'Entity %s in field %s is not managed by EntityManager',
86-
get_class($value),
87-
$field
88-
));
89-
}
90-
}
91-
}
92-
30+
// Use Symfony's deep object population to handle nested relations
9331
return $this->denormalizer->denormalize(
94-
$processedData,
32+
$data,
9533
$resourceClass,
9634
null,
97-
[ExportAwareItemNormalizer::EXPORT_CONTEXT_KEY => true]
35+
[
36+
ExportAwareItemNormalizer::EXPORT_CONTEXT_KEY => true,
37+
DateTimeNormalizer::FORMAT_KEY => 'Y-m-d H:i:s',
38+
AbstractObjectNormalizer::DEEP_OBJECT_TO_POPULATE => true,
39+
]
9840
);
9941
}
10042

101-
private function processNestedArray(array $data): array
102-
{
103-
return $data;
104-
}
10543

10644
public function supports(string $resourceClass): bool
10745
{

src/Denormalizer/DoctrineRelationResolver.php

Lines changed: 24 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,14 @@
1414
namespace Sylius\ImportExport\Denormalizer;
1515

1616
use Doctrine\ORM\EntityManagerInterface;
17+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
1718
use Webmozart\Assert\Assert;
1819

1920
final readonly class DoctrineRelationResolver implements RelationResolverInterface
2021
{
2122
public function __construct(
2223
private EntityManagerInterface $entityManager,
24+
private DenormalizerInterface $denormalizer,
2325
) {
2426
}
2527

@@ -30,37 +32,37 @@ public function resolveEntity(string $entityClass, array $data): ?object
3032
}
3133

3234
Assert::classExists($entityClass);
33-
$repository = $this->entityManager->getRepository($entityClass);
34-
$identifierField = $this->getIdentifierField($entityClass, $data);
3535

36-
if (!isset($data[$identifierField])) {
37-
return null;
36+
// Try to find existing entity first
37+
$existingEntity = $this->findExistingEntity($entityClass, $data);
38+
if (null !== $existingEntity) {
39+
return $existingEntity;
3840
}
3941

40-
$identifier = $data[$identifierField];
42+
// Create new entity if not found
43+
return $this->createNewEntity($entityClass, $data);
44+
}
4145

42-
if ('id' === $identifierField) {
43-
$entity = $repository->find($identifier);
44-
} else {
45-
$entity = $repository->findOneBy([$identifierField => $identifier]);
46-
}
46+
private function findExistingEntity(string $entityClass, array $data): ?object
47+
{
48+
$repository = $this->entityManager->getRepository($entityClass);
4749

48-
// Log if entity not found for debugging
49-
if (null === $entity) {
50-
error_log(sprintf('Import: Skipping relation - %s not found with %s = %s',
51-
substr($entityClass, strrpos($entityClass, '\\') + 1),
52-
$identifierField,
53-
$identifier
54-
));
55-
return null;
50+
// Try to find by code first, then by id
51+
if (isset($data['code'])) {
52+
return $repository->findOneBy(['code' => $data['code']]);
5653
}
5754

58-
// Ensure entity is managed by EntityManager to prevent cascade persist errors
59-
if (!$this->entityManager->contains($entity)) {
60-
$entity = $this->entityManager->merge($entity);
55+
if (isset($data['id'])) {
56+
return $repository->find($data['id']);
6157
}
6258

63-
return $entity;
59+
return null;
60+
}
61+
62+
63+
private function createNewEntity(string $entityClass, array $data): object
64+
{
65+
return $this->denormalizer->denormalize($data, $entityClass);
6466
}
6567

6668
public function resolveCollection(string $entityClass, array $dataCollection): array
@@ -80,16 +82,4 @@ public function resolveCollection(string $entityClass, array $dataCollection): a
8082
return $entities;
8183
}
8284

83-
private function getIdentifierField(string $entityClass, array $data): string
84-
{
85-
if (isset($data['code'])) {
86-
return 'code';
87-
}
88-
89-
if (isset($data['id'])) {
90-
return 'id';
91-
}
92-
93-
return 'id';
94-
}
9585
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Sylius package.
5+
*
6+
* (c) Sylius Sp. z o.o.
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+
declare(strict_types=1);
13+
14+
namespace Sylius\ImportExport\Denormalizer;
15+
16+
use Doctrine\ORM\EntityManagerInterface;
17+
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
18+
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
19+
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareTrait;
20+
21+
final class EntityRelationDenormalizer implements DenormalizerInterface, DenormalizerAwareInterface
22+
{
23+
use DenormalizerAwareTrait;
24+
25+
public function __construct(
26+
private EntityManagerInterface $entityManager,
27+
) {
28+
}
29+
30+
public function denormalize(mixed $data, string $type, ?string $format = null, array $context = []): mixed
31+
{
32+
if (!is_array($data)) {
33+
return $data;
34+
}
35+
36+
// Try to find existing entity by id or code
37+
$existingEntity = $this->findExistingEntity($type, $data);
38+
if (null !== $existingEntity) {
39+
// Ensure entity is managed to prevent cascade persist errors
40+
if (!$this->entityManager->contains($existingEntity)) {
41+
$existingEntity = $this->entityManager->merge($existingEntity);
42+
}
43+
return $existingEntity;
44+
}
45+
46+
// Add context to prevent infinite recursion
47+
$context['entity_relation_denormalizer_skip'] = true;
48+
49+
// Create new entity using the default denormalizer
50+
return $this->denormalizer->denormalize($data, $type, $format, $context);
51+
}
52+
53+
public function supportsDenormalization(mixed $data, string $type, ?string $format = null, array $context = []): bool
54+
{
55+
// Skip if we're already processing to avoid infinite recursion
56+
if (isset($context['entity_relation_denormalizer_skip'])) {
57+
return false;
58+
}
59+
60+
// Only handle entity classes that have Doctrine metadata
61+
if (!is_array($data) || !class_exists($type)) {
62+
return false;
63+
}
64+
65+
try {
66+
$this->entityManager->getClassMetadata($type);
67+
// Only handle if data contains id or code (for existing entities)
68+
return isset($data['id']) || isset($data['code']);
69+
} catch (\Exception) {
70+
return false;
71+
}
72+
}
73+
74+
public function getSupportedTypes(?string $format): array
75+
{
76+
return ['*' => false];
77+
}
78+
79+
private function findExistingEntity(string $entityClass, array $data): ?object
80+
{
81+
$repository = $this->entityManager->getRepository($entityClass);
82+
83+
// Try to find by code first, then by id
84+
if (isset($data['code'])) {
85+
return $repository->findOneBy(['code' => $data['code']]);
86+
}
87+
88+
if (isset($data['id'])) {
89+
return $repository->find($data['id']);
90+
}
91+
92+
return null;
93+
}
94+
}

0 commit comments

Comments
 (0)