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
96:29 queued 93:03
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 declare(strict_types=1);
2
3
namespace EdmondsCommerce\DoctrineStaticMeta\Entity\DataTransferObjects;
4
5
use Doctrine\ORM\EntityManagerInterface;
6
use EdmondsCommerce\DoctrineStaticMeta\CodeGeneration\NamespaceHelper;
7
use EdmondsCommerce\DoctrineStaticMeta\DoctrineStaticMeta;
8
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Factories\UuidFactory;
9
use EdmondsCommerce\DoctrineStaticMeta\Entity\Fields\Interfaces\PrimaryKey\UuidPrimaryKeyInterface;
10
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\DataTransferObjectInterface;
11
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityData;
12
use EdmondsCommerce\DoctrineStaticMeta\Entity\Interfaces\EntityInterface;
13
use EdmondsCommerce\DoctrineStaticMeta\Exception\DoctrineStaticMetaException;
14
use LogicException;
15
use ReflectionException;
16
use RuntimeException;
17
use TypeError;
18
19
/**
20
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
21
 */
22
class DtoFactory implements DtoFactoryInterface
23
{
24
    /**
25
     * @var NamespaceHelper
26
     */
27
    private $namespaceHelper;
28
    /**
29
     * @var UuidFactory
30
     */
31
    private $uuidFactory;
32
    /**
33
     * @var array
34
     */
35
    private $createdDtos = [];
36
37 2
    public function __construct(
38
        NamespaceHelper $namespaceHelper,
39
        UuidFactory $uuidFactory
40
    ) {
41 2
        $this->namespaceHelper = $namespaceHelper;
42 2
        $this->uuidFactory     = $uuidFactory;
43 2
    }
44
45
    /**
46
     * Pass in the FQN for an entity and get an empty DTO, including nested empty DTOs for required relations
47
     *
48
     * @param string $entityFqn
49
     *
50
     * @return mixed
51
     */
52 2
    public function createEmptyDtoFromEntityFqn(string $entityFqn)
53
    {
54 2
        $dtoFqn = $this->namespaceHelper->getEntityDtoFqnFromEntityFqn($entityFqn);
55
56 2
        $dto = new $dtoFqn();
57 2
        $this->resetCreationTransaction();
58 2
        $this->createdDtos[$dtoFqn] = $dto;
59 2
        $this->setId($dto);
60 2
        $this->addRequiredItemsToDto($dto);
61 2
        $this->resetCreationTransaction();
62
63 2
        return $dto;
64
    }
65
66
    /**
67
     * When creating DTOs, we keep track of created DTOs. When you start creating a new DTO, you should call this first
68
     * and then call again after you have finished.
69
     *
70
     * This is handled for you in ::createEmptyDtoFromEntityFqn
71
     *
72
     * @return DtoFactory
73
     */
74 2
    public function resetCreationTransaction(): self
75
    {
76 2
        $this->createdDtos = [];
77
78 2
        return $this;
79
    }
80
81
    /**
82
     * If the Entity that the DTO represents has a settable and buildable UUID, then we should set that at the point of
83
     * creating a DTO for a new Entity instance
84
     *
85
     * @param DataTransferObjectInterface $dto
86
     */
87 2
    private function setId(DataTransferObjectInterface $dto): void
88
    {
89 2
        $entityFqn  = $dto::getEntityFqn();
90 2
        $reflection = $this->getDsmFromEntityFqn($entityFqn)
91 2
                           ->getReflectionClass();
92 2
        if ($reflection->implementsInterface(UuidPrimaryKeyInterface::class)) {
93 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

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