GitHub Access Token became invalid

It seems like the GitHub access token used for retrieving details about this repository from GitHub became invalid. This might prevent certain types of inspections from being run (in particular, everything related to pull requests).
Please ask an admin of your repository to re-new the access token on this website.
Completed
Pull Request — master (#221)
by joseph
19:29
created

DtoFactory::createEmptyDtoFromEntityFqn()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 8
nc 1
nop 1
dl 0
loc 12
ccs 9
cts 9
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\DataTransferObjects;
6
7
use Doctrine\ORM\EntityManagerInterface;
8
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
9
use EdmondsCommerce\DoctrineStaticMeta\DoctrineStaticMeta;
10
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Factories\UuidFactory;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Interfaces\PrimaryKey\UuidPrimaryKeyInterface;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\DataTransferObjectInterface;
13
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityData;
14
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
15
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
16
use LogicException;
17
use ReflectionException;
18
use RuntimeException;
19
use TypeError;
20
21
/**
22
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
23
 */
24
class DtoFactory implements DtoFactoryInterface
25
{
26
    /**
27
     * @var NamespaceHelper
28
     */
29
    private $namespaceHelper;
30
    /**
31
     * @var UuidFactory
32
     */
33
    private $uuidFactory;
34
    /**
35
     * @var array
36
     */
37
    private $createdDtos = [];
38
39 2
    public function __construct(
40
        NamespaceHelper $namespaceHelper,
41
        UuidFactory $uuidFactory
42
    ) {
43 2
        $this->namespaceHelper = $namespaceHelper;
44 2
        $this->uuidFactory     = $uuidFactory;
45 2
    }
46
47
    /**
48
     * Pass in the FQN for an entity and get an empty DTO, including nested empty DTOs for required relations
49
     *
50
     * @param string $entityFqn
51
     *
52
     * @return mixed
53
     */
54 2
    public function createEmptyDtoFromEntityFqn(string $entityFqn)
55
    {
56 2
        $dtoFqn = $this->namespaceHelper->getEntityDtoFqnFromEntityFqn($entityFqn);
57
58 2
        $dto = new $dtoFqn();
59 2
        $this->resetCreationTransaction();
60 2
        $this->createdDtos[$dtoFqn] = $dto;
61 2
        $this->setId($dto);
62 2
        $this->addRequiredItemsToDto($dto);
63 2
        $this->resetCreationTransaction();
64
65 2
        return $dto;
66
    }
67
68
    /**
69
     * When creating DTOs, we keep track of created DTOs. When you start creating a new DTO, you should call this first
70
     * and then call again after you have finished.
71
     *
72
     * This is handled for you in ::createEmptyDtoFromEntityFqn
73
     *
74
     * @return DtoFactory
75
     */
76 2
    public function resetCreationTransaction(): self
77
    {
78 2
        $this->createdDtos = [];
79
80 2
        return $this;
81
    }
82
83
    /**
84
     * If the Entity that the DTO represents has a settable and buildable UUID, then we should set that at the point of
85
     * creating a DTO for a new Entity instance
86
     *
87
     * @param DataTransferObjectInterface $dto
88
     */
89 2
    private function setId(DataTransferObjectInterface $dto): void
90
    {
91 2
        $entityFqn  = $dto::getEntityFqn();
92 2
        $reflection = $this->getDsmFromEntityFqn($entityFqn)
93 2
                           ->getReflectionClass();
94 2
        if ($reflection->implementsInterface(UuidPrimaryKeyInterface::class)) {
95 2
            $dto->setId($entityFqn::buildUuid($this->uuidFactory));
0 ignored issues
show
Bug introduced by
The method setId() does not exist on EdmondsCommerce\Doctrine...TransferObjectInterface. It seems like you code against a sub-type of EdmondsCommerce\Doctrine...TransferObjectInterface such as EdmondsCommerce\Doctrine...AbstractEntityUpdateDto or TemplateNamespace\Entity...jects\TemplateEntityDto. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

95
            $dto->/** @scrutinizer ignore-call */ 
96
                  setId($entityFqn::buildUuid($this->uuidFactory));
Loading history...
96
        }
97 2
    }
98
99
    /**
100
     * Get the instance of DoctrineStaticMeta from the Entity by FQN
101
     *
102
     * @param string $entityFqn
103
     *
104
     * @return DoctrineStaticMeta
105
     */
106 2
    private function getDsmFromEntityFqn(string $entityFqn): DoctrineStaticMeta
107
    {
108 2
        return $entityFqn::getDoctrineStaticMeta();
109
    }
110
111 2
    private function addRequiredItemsToDto(DataTransferObjectInterface $dto): void
112
    {
113 2
        $this->addNestedRequiredDtos($dto);
114 2
        $this->addRequiredEmbeddableObjectsToDto($dto);
115 2
    }
116
117
    /**
118
     * Take the DTO for a defined EntityFqn and then parse the required relations and create nested DTOs for them
119
     *
120
     * Checks if the required relation is already set and if so, does nothing
121
     *
122
     * @param DataTransferObjectInterface $dto
123
     *
124
     * @throws ReflectionException
125
     * @throws DoctrineStaticMetaException
126
     */
127 2
    private function addNestedRequiredDtos(DataTransferObjectInterface $dto): void
128
    {
129 2
        $entityFqn         = $dto::getEntityFqn();
130 2
        $dsm               = $this->getDsmFromEntityFqn($entityFqn);
131 2
        $requiredRelations = $dsm->getRequiredRelationProperties();
132 2
        foreach ($requiredRelations as $propertyName => $types) {
133 2
            $numTypes = count($types);
134 2
            if (1 !== $numTypes) {
135
                throw new RuntimeException('Unexpected number of types, only expecting 1: ' . print_r($types, true));
136
            }
137 2
            $entityInterfaceFqn = $types[0];
138 2
            $getter             = 'get' . $propertyName;
139 2
            if ('[]' === substr($entityInterfaceFqn, -2)) {
140 2
                if ($dto->$getter()->count() > 0) {
141
                    continue;
142
                }
143 2
                $entityInterfaceFqn = substr($entityInterfaceFqn, 0, -2);
144 2
                $this->addNestedDtoToCollection($dto, $propertyName, $entityInterfaceFqn);
145 2
                continue;
146
            }
147 2
            $issetAsDtoMethod    = 'isset' . $propertyName . 'AsDto';
148 2
            $issetAsEntityMethod = 'isset' . $propertyName . 'AsEntity';
149 2
            if (true === $dto->$issetAsDtoMethod() || true === $dto->$issetAsEntityMethod()) {
150 2
                continue;
151
            }
152 2
            $this->addNestedDto($dto, $propertyName, $entityInterfaceFqn);
153
        }
154 2
    }
155
156
    /**
157
     * Create and add a related DTO into the owning DTO collection property
158
     *
159
     * @param DataTransferObjectInterface $dto
160
     * @param string                      $propertyName
161
     * @param string                      $entityInterfaceFqn
162
     *
163
     * @throws DoctrineStaticMetaException
164
     * @throws ReflectionException
165
     */
166 2
    private function addNestedDtoToCollection(
167
        DataTransferObjectInterface $dto,
168
        string $propertyName,
169
        string $entityInterfaceFqn
170
    ): void {
171 2
        $collectionGetter = 'get' . $propertyName;
172 2
        $dto->$collectionGetter()->add(
173 2
            $this->createDtoRelatedToDto(
174 2
                $dto,
175 2
                $this->namespaceHelper->getEntityFqnFromEntityInterfaceFqn($entityInterfaceFqn)
176
            )
177
        );
178 2
    }
179
180
    /**
181
     * Create a DTO with a preset relation to the owning Entity DTO and all other items filled with new objects
182
     *
183
     * @param DataTransferObjectInterface $owningDto
184
     * @param string                      $relatedEntityFqn
185
     *
186
     * @return DataTransferObjectInterface
187
     * @throws DoctrineStaticMetaException
188
     * @throws ReflectionException
189
     */
190 2
    public function createDtoRelatedToDto(
191
        DataTransferObjectInterface $owningDto,
192
        string $relatedEntityFqn
193
    ): DataTransferObjectInterface {
194 2
        $this->resetCreationTransaction();
195 2
        return $this->createDtoRelatedToEntityDataObject($owningDto, $relatedEntityFqn);
196
    }
197
198
    /**
199
     * @param EntityData $owningDataObject
200
     * @param string     $relatedEntityFqn
201
     *
202
     * @return DataTransferObjectInterface
203
     * @throws DoctrineStaticMetaException
204
     * @throws ReflectionException
205
     *
206
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
207
     */
208 2
    private function createDtoRelatedToEntityDataObject(
209
        EntityData $owningDataObject,
210
        string $relatedEntityFqn
211
    ): DataTransferObjectInterface {
212 2
        $relatedDtoFqn = $this->namespaceHelper->getEntityDtoFqnFromEntityFqn($relatedEntityFqn);
213 2
        $newlyCreated  = false;
214 2
        $dto           = $this->getCreatedDto($relatedDtoFqn);
215 2
        if (null === $dto) {
216 2
            $newlyCreated = true;
217 2
            $dto          = $this->createDtoInstance($relatedDtoFqn);
218
        }
219
        /**
220
         * @var DoctrineStaticMeta $owningDsm
221
         */
222 2
        $owningEntityFqn = $owningDataObject::getEntityFqn();
223 2
        $owningDsm       = $owningEntityFqn::getDoctrineStaticMeta();
224 2
        $owningSingular  = $owningDsm->getSingular();
225 2
        $owningPlural    = $owningDsm->getPlural();
226
227
        /**
228
         * @var DoctrineStaticMeta $relatedDsm
229
         */
230 2
        $relatedDsm = $relatedEntityFqn::getDoctrineStaticMeta();
231 2
        $relatedDsm->getRequiredRelationProperties();
232
233 2
        $dtoSuffix = $owningDataObject instanceof DataTransferObjectInterface ? 'Dto' : '';
234
235 2
        $relatedRequiredRelations = $relatedDsm->getRequiredRelationProperties();
236 2
        foreach (array_keys($relatedRequiredRelations) as $propertyName) {
237
            switch ($propertyName) {
238 2
                case $owningSingular:
239 2
                    $getter = 'get' . $owningSingular . $dtoSuffix;
240
                    try {
241 2
                        if (null !== $dto->$getter()) {
242
                            break 2;
243
                        }
244 2
                    } catch (TypeError $e) {
245
                        //null will cause a type error on getter
246
                    }
247 2
                    $setter = 'set' . $owningSingular . $dtoSuffix;
248 2
                    $dto->$setter($owningDataObject);
249
250 2
                    break 2;
251
                case $owningPlural:
252
                    $collectionGetter = 'get' . $owningPlural;
253
                    $collection       = $dto->$collectionGetter();
254
                    foreach ($collection as $item) {
255
                        if ($item === $owningDataObject) {
256
                            break 3;
257
                        }
258
                    }
259
                    $collection->add($owningDataObject);
260
261
                    break 2;
262
            }
263
        }
264 2
        if (true === $newlyCreated) {
265 2
            $this->addRequiredItemsToDto($dto);
266
        }
267
268 2
        return $dto;
269
    }
270
271 2
    private function getCreatedDto(string $dtoFqn): ?DataTransferObjectInterface
272
    {
273 2
        return $this->createdDtos[$dtoFqn] ?? null;
274
    }
275
276 2
    private function createDtoInstance(string $dtoFqn): DataTransferObjectInterface
277
    {
278 2
        if (null !== $this->getCreatedDto($dtoFqn)) {
279
            throw new LogicException('Trying to set a created DTO ' . $dtoFqn . ' when one already exists');
280
        }
281 2
        $dto = new $dtoFqn();
282 2
        $this->setId($dto);
283 2
        $this->createdDtos[ltrim($dtoFqn, '\\')] = $dto;
284
285 2
        return $dto;
286
    }
287
288 2
    private function addNestedDto(
289
        DataTransferObjectInterface $dto,
290
        string $propertyName,
291
        string $entityInterfaceFqn
292
    ): void {
293 2
        $dtoSetter = 'set' . $propertyName . 'Dto';
294 2
        $dto->$dtoSetter(
295 2
            $this->createDtoRelatedToDto(
296 2
                $dto,
297 2
                $this->namespaceHelper->getEntityFqnFromEntityInterfaceFqn($entityInterfaceFqn)
298
            )
299
        );
300 2
    }
301
302 2
    private function addRequiredEmbeddableObjectsToDto(DataTransferObjectInterface $dto): void
303
    {
304 2
        $dsm                  = $this->getDsmFromEntityFqn($dto::getEntityFqn());
305 2
        $embeddableProperties = $dsm->getEmbeddableProperties();
306 2
        foreach ($embeddableProperties as $property => $embeddableObject) {
307
            $setter = 'set' . $property;
308
            $dto->$setter($embeddableObject::create($embeddableObject::DEFAULTS));
309
        }
310 2
    }
311
312
    /**
313
     * Create a DTO with a preset relation to the owning Entity and all other items filled with new objects
314
     *
315
     * @param EntityInterface $owningEntity
316
     * @param string          $relatedEntityFqn
317
     *
318
     * @return DataTransferObjectInterface
319
     * @throws DoctrineStaticMetaException
320
     * @throws ReflectionException
321
     */
322
    public function createDtoRelatedToEntityInstance(
323
        EntityInterface $owningEntity,
324
        string $relatedEntityFqn
325
    ): DataTransferObjectInterface {
326
        $this->resetCreationTransaction();
327
        return $this->createDtoRelatedToEntityDataObject($owningEntity, $relatedEntityFqn);
328
    }
329
330
    /**
331
     * Create a DTO with the values from teh Entity, optionally with some values directly overridden with your values
332
     * to set
333
     *
334
     * @param EntityInterface $entity
335
     *
336
     * @return mixed
337
     * @throws ReflectionException
338
     */
339 1
    public function createDtoFromEntity(EntityInterface $entity)
340
    {
341 1
        $this->resetCreationTransaction();
342 1
        $dsm     = $entity::getDoctrineStaticMeta();
343 1
        $dtoFqn  = $this->namespaceHelper->getEntityDtoFqnFromEntityFqn($dsm->getReflectionClass()->getName());
344 1
        $dto     = new $dtoFqn();
345 1
        $setters = $dsm->getSetters();
346 1
        foreach ($setters as $getterName => $setterName) {
347 1
            $dto->$setterName($entity->$getterName());
348
        }
349
350 1
        return $dto;
351
    }
352
}
353