Passed
Pull Request — master (#5)
by Alex
03:06
created

AbstractCascadeService   A

Complexity

Total Complexity 15

Size/Duplication

Total Lines 168
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 15
eloc 63
dl 0
loc 168
rs 10
c 0
b 0
f 0

5 Methods

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