resolveTargetEntityOrCollection()   A
last analyzed

Complexity

Conditions 3
Paths 3

Size

Total Lines 39
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 23
c 1
b 0
f 0
dl 0
loc 39
rs 9.552
cc 3
nc 3
nop 4
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\DoctrineEntityRepository\Persistence;
6
7
use Arp\DoctrineEntityRepository\Constant\ClearMode;
8
use Arp\DoctrineEntityRepository\Constant\EntityEventOption;
9
use Arp\DoctrineEntityRepository\Constant\FlushMode;
10
use Arp\DoctrineEntityRepository\Constant\TransactionMode;
11
use Arp\DoctrineEntityRepository\EntityRepositoryInterface;
12
use Arp\DoctrineEntityRepository\Persistence\Exception\PersistenceException;
13
use Arp\Entity\EntityInterface;
14
use Doctrine\ORM\EntityManagerInterface;
15
use Doctrine\ORM\Mapping\ClassMetadata;
16
use Psr\Log\LoggerInterface;
17
18
/**
19
 * @author  Alex Patterson <[email protected]>
20
 * @package Arp\DoctrineEntityRepository\Persistence
21
 */
22
abstract class AbstractCascadeService
23
{
24
    /**
25
     * @var LoggerInterface
26
     */
27
    protected LoggerInterface $logger;
28
29
    /**
30
     * @var array<string, mixed>
31
     */
32
    protected array $options = [
33
        EntityEventOption::TRANSACTION_MODE => TransactionMode::DISABLED,
34
        EntityEventOption::FLUSH_MODE       => FlushMode::DISABLED,
35
        EntityEventOption::CLEAR_MODE       => ClearMode::DISABLED,
36
    ];
37
38
    /**
39
     * @var array<string, mixed>
40
     */
41
    protected array $collectionOptions = [
42
        EntityEventOption::TRANSACTION_MODE => TransactionMode::DISABLED,
43
        EntityEventOption::FLUSH_MODE       => FlushMode::DISABLED,
44
        EntityEventOption::CLEAR_MODE       => ClearMode::DISABLED,
45
    ];
46
47
    /**
48
     * @param LoggerInterface $logger
49
     * @param array<mixed>    $options
50
     * @param array<mixed>    $collectionOptions
51
     */
52
    public function __construct(LoggerInterface $logger, array $options = [], array $collectionOptions = [])
53
    {
54
        $this->logger = $logger;
55
        $this->options = empty($options) ? $this->options : $options;
56
        $this->collectionOptions = empty($collectionOptions) ? $this->collectionOptions : $collectionOptions;
57
    }
58
59
    /**
60
     * @param EntityManagerInterface $entityManager
61
     * @param class-string           $entityName
62
     *
63
     * @return EntityRepositoryInterface
64
     * @throws PersistenceException
65
     * @todo We should implement a way to decorate the call the getRepository() with a concrete implementation
66
     *       of the EntityRepositoryProviderInterface
67
     */
68
    protected function getTargetRepository(
69
        EntityManagerInterface $entityManager,
70
        string $entityName
71
    ): EntityRepositoryInterface {
72
        if (!class_exists($entityName, true) && !$entityManager->getMetadataFactory()->hasMetadataFor($entityName)) {
73
            $errorMessage = sprintf('The target repository class \'%s\' could not be found', $entityName);
74
75
            $this->logger->error($errorMessage, ['entity_name' => $entityName]);
76
77
            throw new PersistenceException($errorMessage);
78
        }
79
80
        try {
81
            /** @var EntityRepositoryInterface|object|null $targetRepository */
82
            $targetRepository = $entityManager->getRepository($entityName);
83
        } catch (\Exception $e) {
84
            $errorMessage = sprintf(
85
                'An error occurred while attempting to load the repository for entity class \'%s\' : %s',
86
                $entityName,
87
                $e->getMessage()
88
            );
89
            $this->logger->error($errorMessage, ['exception' => $e]);
90
91
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
92
        }
93
94
        if (!isset($targetRepository) || !($targetRepository instanceof EntityRepositoryInterface)) {
95
            $errorMessage = sprintf(
96
                'The entity repository must be an object of type \'%s\'; \'%s\' returned in \'%s::%s\'',
97
                EntityRepositoryInterface::class,
98
                (is_object($targetRepository) ? get_class($targetRepository) : gettype($targetRepository)),
99
                static::class,
100
                __FUNCTION__
101
            );
102
103
            $this->logger->error($errorMessage, ['entity_name' => $entityName]);
104
105
            throw new PersistenceException($errorMessage);
106
        }
107
108
        return $targetRepository;
109
    }
110
111
    /**
112
     * @param EntityInterface                $sourceEntity
113
     * @param string                         $fieldName
114
     * @param ClassMetadata<EntityInterface> $sourceMetadata
115
     * @param ClassMetadata<EntityInterface> $targetMetadata
116
     *
117
     * @return EntityInterface|EntityInterface[]|iterable
118
     *
119
     * @throws PersistenceException
120
     */
121
    protected function resolveTargetEntityOrCollection(
122
        EntityInterface $sourceEntity,
123
        string $fieldName,
124
        ClassMetadata $sourceMetadata,
125
        ClassMetadata $targetMetadata
126
    ) {
127
        $methodName = 'get' . ucfirst($fieldName);
128
129
        if (!method_exists($sourceEntity, $methodName)) {
130
            $errorMessage = sprintf(
131
                'Failed to find required entity method \'%s::%s\'. The method is required for cascade operations '
132
                . 'of field \'%s\' of target entity \'%s\'',
133
                $sourceMetadata->getName(),
134
                $methodName,
135
                $fieldName,
136
                $targetMetadata->getName()
137
            );
138
139
            $this->logger->error($errorMessage);
140
141
            throw new PersistenceException($errorMessage);
142
        }
143
144
        try {
145
            $targetEntityOrCollection = $sourceEntity->{$methodName}();
146
        } catch (\Exception $e) {
147
            $errorMessage = sprintf(
148
                'The call to resolve entity of type \'%s\' from method call \'%s::%s\' failed: %s',
149
                $targetMetadata->getName(),
150
                $sourceMetadata->getName(),
151
                $methodName,
152
                $e->getMessage()
153
            );
154
            $this->logger->error($errorMessage, ['exception' => $e]);
155
156
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
157
        }
158
159
        return $targetEntityOrCollection;
160
    }
161
162
    /**
163
     * @param iterable<EntityInterface>|EntityInterface|mixed|null $entityOrCollection
164
     * @param array<mixed>                                         $mapping
165
     *
166
     * @return bool
167
     */
168
    protected function isValidAssociation($entityOrCollection, array $mapping): bool
169
    {
170
        if (null === $entityOrCollection) {
171
            /**
172
             * @todo mapping class has a methods to fetch the id field mapping directly
173
             *
174
             * Note that we are hard coding the '0' key as the single field to use as the id/primary key.
175
             * If we implement EntityInterface correctly we will never have a composite key.
176
             */
177
            return isset($mapping['joinColumns'][0]['nullable']) && $mapping['joinColumns'][0]['nullable'];
178
        }
179
180
        return (is_iterable($entityOrCollection) || $entityOrCollection instanceof EntityInterface);
181
    }
182
183
    /**
184
     * @param EntityManagerInterface $entityManager
185
     * @param string                 $entityName
186
     *
187
     * @return ClassMetadata<EntityInterface|object>
188
     *
189
     * @throws PersistenceException
190
     */
191
    protected function getClassMetadata(EntityManagerInterface $entityManager, string $entityName): ClassMetadata
192
    {
193
        try {
194
            return $entityManager->getClassMetadata($entityName);
195
        } catch (\Exception $e) {
196
            $errorMessage = sprintf(
197
                'The entity metadata mapping for class \'%s\' could not be loaded: %s',
198
                $entityName,
199
                $e->getMessage()
200
            );
201
            $this->logger->error($errorMessage, ['exception' => $e, 'entity_name' => $entityName]);
202
203
            throw new PersistenceException($errorMessage, $e->getCode(), $e);
204
        }
205
    }
206
}
207