Passed
Pull Request — master (#4)
by Alex
02:44
created

EntityRepositoryFactory::__invoke()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 42
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 26
c 0
b 0
f 0
dl 0
loc 42
rs 9.504
cc 2
nc 2
nop 3
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Arp\LaminasDoctrine\Factory\Repository;
6
7
use Arp\Entity\EntityInterface;
8
use Arp\LaminasDoctrine\Repository\EntityRepository;
9
use Arp\LaminasDoctrine\Repository\EntityRepositoryInterface;
10
use Arp\LaminasDoctrine\Repository\Persistence\PersistService;
11
use Arp\LaminasDoctrine\Repository\Persistence\PersistServiceInterface;
12
use Arp\LaminasDoctrine\Repository\Query\QueryService;
13
use Arp\LaminasDoctrine\Repository\Query\QueryServiceInterface;
14
use Arp\LaminasDoctrine\Repository\Query\QueryServiceManager;
15
use Arp\LaminasFactory\AbstractFactory;
16
use Arp\LaminasMonolog\Factory\FactoryLoggerProviderTrait;
17
use Laminas\ServiceManager\Exception\ContainerModificationsNotAllowedException;
18
use Laminas\ServiceManager\Exception\InvalidServiceException;
19
use Laminas\ServiceManager\Exception\ServiceNotCreatedException;
20
use Laminas\ServiceManager\Exception\ServiceNotFoundException;
21
use Laminas\ServiceManager\ServiceLocatorInterface;
22
use Psr\Container\ContainerExceptionInterface;
23
use Psr\Container\ContainerInterface;
24
use Psr\Container\NotFoundExceptionInterface;
25
26
final class EntityRepositoryFactory extends AbstractFactory
27
{
28
    use FactoryLoggerProviderTrait;
29
30
    /**
31
     * The default configuration for all entity repositories
32
     *
33
     * @var array<mixed>
34
     */
35
    private array $defaultOptions = [
36
        'logger' => null,
37
        'query_service' => [
38
            'service_name' => QueryService::class,
39
            'logger' => null,
40
        ],
41
        'persist_service' => [
42
            'service_name' => PersistService::class,
43
            'logger' => null,
44
        ],
45
    ];
46
47
    /**
48
     * @param ContainerInterface&ServiceLocatorInterface $container
49
     * @param string                                     $requestedName
50
     * @param array<string, mixed>|null                  $options
51
     *
52
     * @return EntityRepositoryInterface<EntityInterface>
53
     *
54
     * @throws ServiceNotCreatedException
55
     * @throws ServiceNotFoundException
56
     * @throws ContainerExceptionInterface
57
     * @throws NotFoundExceptionInterface
58
     */
59
    public function __invoke(
60
        ContainerInterface $container,
61
        string $requestedName,
62
        array $options = null
63
    ): EntityRepositoryInterface {
64
        $options = array_replace_recursive(
65
            $this->defaultOptions,
66
            $this->getServiceOptions($container, $requestedName, 'repositories'),
67
            $options ?? []
68
        );
69
70
        $entityName = $options['entity_name'] ?? $requestedName;
71
        if (empty($entityName)) {
72
            throw new ServiceNotCreatedException(
73
                sprintf(
74
                    'The required \'entity_name\' configuration option is missing for service \'%s\'',
75
                    $requestedName
76
                )
77
            );
78
        }
79
80
        $queryService = $this->getQueryService(
81
            $container,
82
            $entityName,
83
            $options['query_service'] ?? [],
84
            $requestedName
85
        );
86
87
        $persistService = $this->getPersistService(
88
            $container,
89
            $entityName,
90
            $options['persist_service'] ?? [],
91
            $requestedName
92
        );
93
94
        $className = $this->resolveClassName($entityName, $options);
95
96
        return new $className(
97
            $entityName,
98
            $queryService,
99
            $persistService,
100
            $this->getLogger($container, $options['logger'] ?? null, $requestedName)
101
        );
102
    }
103
104
    /**
105
     * @param string               $entityName
106
     * @param array<string, mixed> $options
107
     *
108
     * @return class-string<EntityRepositoryInterface<EntityInterface>>
0 ignored issues
show
Documentation Bug introduced by
The doc comment class-string<EntityRepos...rface<EntityInterface>> at position 0 could not be parsed: Unknown type name 'class-string' at position 0 in class-string<EntityRepositoryInterface<EntityInterface>>.
Loading history...
109
     */
110
    private function resolveClassName(string $entityName, array $options = []): string
111
    {
112
        $className = $options['class_name'] ?? EntityRepository::class;
113
        if (empty($options['class_name'])) {
114
            $generatedClassNames = [
115
                str_replace('Entity', 'Repository', $entityName) . 'Repository',
116
                str_replace('Entity', 'Entity\\Repository', $entityName) . 'Repository',
117
            ];
118
            foreach ($generatedClassNames as $generatedClassName) {
119
                if (
120
                    class_exists($generatedClassName, true)
121
                    && is_subclass_of($generatedClassName, EntityRepositoryInterface::class, true)
122
                ) {
123
                    return $generatedClassName;
124
                }
125
            }
126
        }
127
128
        return $className;
129
    }
130
131
    /**
132
     * @param ServiceLocatorInterface $container
133
     * @param string                  $entityName
134
     * @param array<string, mixed>    $options
135
     * @param string                  $serviceName
136
     *
137
     * @return PersistServiceInterface
138
     *
139
     * @throws ServiceNotCreatedException
140
     * @throws ServiceNotFoundException
141
     * @throws ContainerExceptionInterface
142
     */
143
    private function getPersistService(
144
        ServiceLocatorInterface $container,
145
        string $entityName,
146
        array $options,
147
        string $serviceName
148
    ): PersistServiceInterface {
149
        $options = array_replace_recursive(
150
            $this->getServiceOptions($container, PersistService::class),
151
            $options
152
        );
153
        $options['entity_name'] ??= $entityName;
154
155
        return $this->buildService(
156
            $container,
157
            $options['service_name'] ?? PersistService::class,
158
            $options,
159
            $serviceName
160
        );
161
    }
162
163
    /**
164
     * @param ServiceLocatorInterface $container
165
     * @param string                  $entityName
166
     * @param array<string, mixed>    $options
167
     * @param string                  $serviceName
168
     *
169
     * @return QueryServiceInterface<EntityInterface>
170
     *
171
     * @throws ContainerExceptionInterface
172
     * @throws ServiceNotCreatedException
173
     * @throws ServiceNotFoundException
174
     * @throws ContainerModificationsNotAllowedException
175
     * @throws InvalidServiceException
176
     */
177
    private function getQueryService(
178
        ServiceLocatorInterface $container,
179
        string $entityName,
180
        array $options,
181
        string $serviceName
182
    ): QueryServiceInterface {
183
        /** @var QueryServiceManager $queryServiceManager */
184
        $queryServiceManager = $this->getService($container, QueryServiceManager::class, $serviceName);
185
186
        if ($queryServiceManager->has($entityName)) {
187
            return $queryServiceManager->get($entityName);
0 ignored issues
show
Bug Best Practice introduced by
The expression return $queryServiceManager->get($entityName) could return the type array which is incompatible with the type-hinted return Arp\LaminasDoctrine\Repo...y\QueryServiceInterface. Consider adding an additional type-check to rule them out.
Loading history...
188
        }
189
190
        $options = array_replace_recursive($this->getServiceOptions($container, QueryService::class), $options);
191
        $options['entity_name'] ??= $entityName;
192
193
        $queryService = $this->buildService(
194
            $container,
195
            $options['service_name'] ?? QueryService::class,
196
            $options,
197
            $serviceName
198
        );
199
200
        $queryServiceManager->setService($entityName, $queryService);
201
202
        return $queryService;
203
    }
204
205
    /**
206
     * @param array<mixed> $defaultOptions
207
     */
208
    public function setDefaultOptions(array $defaultOptions): void
209
    {
210
        $this->defaultOptions = $defaultOptions;
211
    }
212
}
213