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.
Passed
Pull Request — master (#192)
by joseph
40:19
created

DtoFactory::createDtoFromEntity()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 12
Code Lines 8

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 8
nc 2
nop 1
dl 0
loc 12
ccs 9
cts 9
cp 1
crap 2
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
14
/**
15
 * @SuppressWarnings(PHPMD.CouplingBetweenObjects)
16
 */
17
class DtoFactory implements DtoFactoryInterface
18
{
19
    /**
20
     * @var NamespaceHelper
21
     */
22
    private $namespaceHelper;
23
    /**
24
     * @var UuidFactory
25
     */
26
    private $uuidFactory;
27
    /**
28
     * @var array
29
     */
30
    private $createdDtos = [];
31
32 2
    public function __construct(
33
        NamespaceHelper $namespaceHelper,
34
        UuidFactory $uuidFactory
35
    ) {
36 2
        $this->namespaceHelper = $namespaceHelper;
37 2
        $this->uuidFactory     = $uuidFactory;
38 2
    }
39
40
    /**
41
     * Pass in the FQN for an entity and get an empty DTO, including nested empty DTOs for required relations
42
     *
43
     * @param string $entityFqn
44
     *
45
     * @return mixed
46
     */
47 2
    public function createEmptyDtoFromEntityFqn(string $entityFqn)
48
    {
49 2
        $dtoFqn = $this->namespaceHelper->getEntityDtoFqnFromEntityFqn($entityFqn);
50
51 2
        $dto = new $dtoFqn();
52 2
        $this->resetCreationTransaction();
53 2
        $this->createdDtos[$dtoFqn] = $dto;
54 2
        $this->setId($dto);
55 2
        $this->addRequiredItemsToDto($dto);
56 2
        $this->resetCreationTransaction();
57
58 2
        return $dto;
59
    }
60
61
    /**
62
     * When creating DTOs, we keep track of created DTOs. When you start creating a new DTO, you should call this first
63
     * and then call again after you have finished.
64
     *
65
     * This is handled for you in ::createEmptyDtoFromEntityFqn
66
     *
67
     * @return DtoFactory
68
     */
69 2
    public function resetCreationTransaction(): self
70
    {
71 2
        $this->createdDtos = [];
72
73 2
        return $this;
74
    }
75
76
    /**
77
     * If the Entity that the DTO represents has a settable and buildable UUID, then we should set that at the point of
78
     * creating a DTO for a new Entity instance
79
     *
80
     * @param DataTransferObjectInterface $dto
81
     */
82 2
    private function setId(DataTransferObjectInterface $dto): void
83
    {
84 2
        $entityFqn  = $dto::getEntityFqn();
85 2
        $reflection = $this->getDsmFromEntityFqn($entityFqn)
86 2
                           ->getReflectionClass();
87 2
        if ($reflection->implementsInterface(UuidPrimaryKeyInterface::class)) {
88 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

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