Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
84.06% covered (warning)
84.06%
58 / 69
66.67% covered (warning)
66.67%
4 / 6
CRAP
0.00% covered (danger)
0.00%
0 / 1
CreateDomainObject
84.06% covered (warning)
84.06%
58 / 69
66.67% covered (warning)
66.67%
4 / 6
22.79
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getBoundedContextConfig
25.00% covered (danger)
25.00%
3 / 12
0.00% covered (danger)
0.00%
0 / 1
6.80
 getIdentifierPath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getDomainObjectPath
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 generateDomainIdentifierCode
100.00% covered (success)
100.00%
22 / 22
100.00% covered (success)
100.00%
1 / 1
5
 generateDomainObjectCode
93.10% covered (success)
93.10%
27 / 29
0.00% covered (danger)
0.00%
0 / 1
9.03
1<?php
2namespace Apie\Maker\CodeGenerators;
3
4use Apie\Common\ValueObjects\EntityNamespace;
5use Apie\Core\Entities\EntityInterface;
6use Apie\Core\Identifiers\IdentifierInterface;
7use Apie\Maker\Dtos\DomainObjectDto;
8use Apie\Maker\Enums\IdType;
9use Apie\Maker\Utils;
10use LogicException;
11use Nette\PhpGenerator\PhpFile;
12use Nette\PhpGenerator\PromotedParameter;
13use Nette\PhpGenerator\PsrPrinter;
14use ReflectionClass;
15
16class CreateDomainObject
17{
18    private readonly ?string $searchContextPath;
19    private readonly ?string $searchContextNamespace;
20    /**
21     * @param array<string, mixed> $boundedContexts
22     * @param array<string, string|null>|null $scanBoundedContextConfig
23     */
24    public function __construct(
25        private readonly array $boundedContextConfig,
26        ?array $scanBoundedContextConfig = null
27    ) {
28        $this->searchContextPath = $scanBoundedContextConfig['search_path'] ?? null;
29        $this->searchContextNamespace = $this->searchContextPath ? ($scanBoundedContextConfig['search_namespace'] ?? null) : null;
30    }
31
32    private function getBoundedContextConfig(DomainObjectDto $domainObjectDto): array
33    {
34        $boundedContextId = $domainObjectDto->boundedContextId->toNative();
35        if (!isset($this->boundedContextConfig[$boundedContextId])) {
36            if ($this->searchContextPath) {
37                $path = $domainObjectDto->boundedContextId->toPascalCaseSlug()->toNative();
38                return [
39                    'entities_folder' => $this->searchContextPath . '/' . $path . '/Resources',
40                    'entities_namespace' => $this->searchContextNamespace . $path . '\\Resources',
41                    'actions_folder' => $this->searchContextPath . '/' . $path . '/Actions',
42                    'actions_namespace' => $this->searchContextNamespace . $path . '\\Actions',
43                ];
44            }
45            throw new LogicException('Bounded context "' . $boundedContextId . '" not found!');
46        }
47        return $this->boundedContextConfig[$boundedContextId];
48    }
49
50    public function getIdentifierPath(DomainObjectDto $domainObjectDto): string
51    {
52        $boundedContextConfig = $this->getBoundedContextConfig($domainObjectDto);
53        return $boundedContextConfig['entities_folder'] . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . 'Identifiers' . DIRECTORY_SEPARATOR . $domainObjectDto->name . 'Identifier.php';
54    }
55
56    public function getDomainObjectPath(DomainObjectDto $domainObjectDto): string
57    {
58        $boundedContextConfig = $this->getBoundedContextConfig($domainObjectDto);
59        return $boundedContextConfig['entities_folder'] . DIRECTORY_SEPARATOR . $domainObjectDto->name . '.php';
60    }
61
62    public function generateDomainIdentifierCode(DomainObjectDto $domainObjectDto): string
63    {
64        $boundedContextConfig = $this->getBoundedContextConfig($domainObjectDto);
65        $resourceNamespace = new EntityNamespace($boundedContextConfig['entities_namespace']);
66        $idNamespace = $resourceNamespace->getParentNamespace()->getChildNamespace('Identifiers');
67        
68        $file = new PhpFile();
69        if ($domainObjectDto->reuseExistingCode) {
70            $targetPath = $this->getIdentifierPath($domainObjectDto);
71            if (file_exists($targetPath)) {
72                $file = PhpFile::fromCode(file_get_contents($targetPath));
73            }
74        }
75
76        $code = Utils::addOrGetNamespace($file, $idNamespace);
77        Utils::addUseStatements($code, IdentifierInterface::class, $resourceNamespace . $domainObjectDto->name, $domainObjectDto->idType->value, ReflectionClass::class);
78        $classType = $code->getClasses()[$domainObjectDto->name . 'Identifier'] ?? $code->addClass($domainObjectDto->name . 'Identifier');
79        $classType->setExtends($domainObjectDto->idType->value);
80        if (!in_array(IdentifierInterface::class, $classType->getImplements())) {
81            $classType->addImplement(IdentifierInterface::class);
82        }
83        if (!$classType->hasMethod('getReferenceFor')) {
84            $classType->addMethod('getReferenceFor');
85        }
86        $classType->getMethod('getReferenceFor')
87            ->setStatic(true)
88            ->setBody('return new ReflectionClass(' . $domainObjectDto->name. '::class);')
89            ->setReturnType(ReflectionClass::class);
90        $printer = new PsrPrinter();
91        return $printer->printFile($file);
92    }
93
94    public function generateDomainObjectCode(DomainObjectDto $domainObjectDto): string
95    {
96        $boundedContextConfig = $this->getBoundedContextConfig($domainObjectDto);
97        $resourceNamespace = new EntityNamespace($boundedContextConfig['entities_namespace']);
98        $idNamespace = $resourceNamespace->getParentNamespace()->getChildNamespace('Identifiers');
99        $file = new PhpFile();
100        if ($domainObjectDto->reuseExistingCode) {
101            $targetPath = $this->getDomainObjectPath($domainObjectDto);
102            if (file_exists($targetPath)) {
103                $file = PhpFile::fromCode(file_get_contents($targetPath));
104            }
105        }
106        $code = Utils::addOrGetNamespace($file, $resourceNamespace);
107        $classType = $code->getClasses()[(string) $domainObjectDto->name] ?? $code->addClass((string) $domainObjectDto->name);
108        if (!in_array(EntityInterface::class, $classType->getImplements())) {
109            $classType->addImplement(EntityInterface::class);
110        }
111        $shouldInitConstructor = !$classType->hasMethod('__construct') && $domainObjectDto->idType === IdType::Integer;
112        $idProperty = Utils::searchOrAddProperty(
113            $classType,
114            'id',
115            $domainObjectDto->idType === IdType::Integer || $domainObjectDto->idType === IdType::Uuid,
116            $domainObjectDto->idType === IdType::Uuid
117        );
118        $idClass = $idNamespace . $domainObjectDto->name . 'Identifier';
119        Utils::addUseStatements($code, $idClass);
120        $idProperty->setType($idClass);
121        if ($shouldInitConstructor && !$idProperty instanceof PromotedParameter) {
122            // constructor is created in Utils::searchOrAddProperty....
123            $classType->getMethod('__construct')
124                ->addBody('$this->id = $id ?? new ' . $domainObjectDto->name . 'Identifier(null);');
125        }
126        if (!$classType->hasMethod('getId')) {
127            $classType->addMethod('getId')->setBody('return $this->id;')->setReturnType($idClass);
128        }
129        $printer = new PsrPrinter();
130        return $printer->printFile($file);
131    }
132}