processFoundationServiceDefinition()   B
last analyzed

Complexity

Conditions 7
Paths 5

Size

Total Lines 38
Code Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 26
CRAP Score 7

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 7
eloc 25
c 1
b 0
f 0
nc 5
nop 3
dl 0
loc 38
ccs 26
cts 26
cp 1
crap 7
rs 8.5866
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * Derafu: Biblioteca PHP (Núcleo).
7
 * Copyright (C) Derafu <https://www.derafu.org>
8
 *
9
 * Este programa es software libre: usted puede redistribuirlo y/o modificarlo
10
 * bajo los términos de la Licencia Pública General Affero de GNU publicada por
11
 * la Fundación para el Software Libre, ya sea la versión 3 de la Licencia, o
12
 * (a su elección) cualquier versión posterior de la misma.
13
 *
14
 * Este programa se distribuye con la esperanza de que sea útil, pero SIN
15
 * GARANTÍA ALGUNA; ni siquiera la garantía implícita MERCANTIL o de APTITUD
16
 * PARA UN PROPÓSITO DETERMINADO. Consulte los detalles de la Licencia Pública
17
 * General Affero de GNU para obtener una información más detallada.
18
 *
19
 * Debería haber recibido una copia de la Licencia Pública General Affero de GNU
20
 * junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace Derafu\Lib\Core\Foundation;
26
27
use Derafu\Lib\Core\Foundation\Contract\ServiceInterface;
28
use Derafu\Lib\Core\Helper\Str;
29
use LogicException;
30
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
31
use Symfony\Component\DependencyInjection\ContainerBuilder;
32
use Symfony\Component\DependencyInjection\Definition;
33
34
/**
35
 * Clase para modificar servicios previo a la compilación del contenedor.
36
 */
37
class ServiceProcessingCompilerPass implements CompilerPassInterface
38
{
39
    /**
40
     * Prefijo con el que deben ser nombrados todos los servicios asociados a la
41
     * aplicación.
42
     *
43
     * Esto se utiliza especialmente al nombrar paquetes, componentes y workers.
44
     *
45
     * @var string
46
     */
47
    protected string $servicesPrefix;
48
49
    /**
50
     * Patrones de búsqueda de clases de servicios de Foundation.
51
     *
52
     * Estas clases deben implementar además la interfaz ServiceInterface.
53
     *
54
     * @var array<string, array>
55
     */
56
    protected array $servicesPatterns = [
57
        // Packages.
58
        'package' => [
59
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Contract\\\\(?P=package)PackageInterface$/",
60
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\(?P=package)Package$/",
61
        ],
62
        // Components.
63
        'component' => [
64
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?P=component)ComponentInterface$/",
65
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\(?P=component)Component$/",
66
        ],
67
        // Workers.
68
        'worker' => [
69
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<worker>[A-Za-z0-9_]+)WorkerInterface$/",
70
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Worker\\\\(?<worker>[A-Za-z0-9_]+)Worker$/",
71
        ],
72
        // Jobs.
73
        'job' => [
74
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<worker>[A-Za-z0-9_]+)\\\\Job\\\\(?<job>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)JobInterface$/",
75
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Worker\\\\(?<worker>[A-Za-z0-9_]+)\\\\Job\\\\(?<job>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)Job$/",
76
        ],
77
        // Handlers.
78
        'handler' => [
79
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<worker>[A-Za-z0-9_]+)\\\\Handler\\\\(?<handler>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)HandlerInterface$/",
80
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Worker\\\\(?<worker>[A-Za-z0-9_]+)\\\\Handler\\\\(?<handler>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)Handler$/",
81
        ],
82
        // Strategies.
83
        'strategy' => [
84
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<worker>[A-Za-z0-9_]+)\\\\Strategy\\\\(?<strategy>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)StrategyInterface$/",
85
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Worker\\\\(?<worker>[A-Za-z0-9_]+)\\\\Strategy\\\\(?<strategy>[A-Za-z0-9_]+(?:\\\\[A-Za-z0-9_]+)?)(?P=worker)Strategy$/",
86
        ],
87
    ];
88
89
    /**
90
     * Interfaces que son procesadas (manejadas) por este ServiceCompiler.
91
     *
92
     * @var array
93
     */
94
    protected array $handledInterfaces = [
95
        ServiceInterface::class,
96
    ];
97
98
    /**
99
     * Constructor de la clase.
100
     *
101
     * @param string $servicesPrefix
102
     */
103 17
    public function __construct(string $servicesPrefix)
104
    {
105 17
        $this->servicesPrefix = $servicesPrefix;
106
    }
107
108
    /**
109
     * Procesar servicios.
110
     *
111
     * @param ContainerBuilder $container
112
     * @return void
113
     */
114 17
    public function process(ContainerBuilder $container): void
115
    {
116 17
        foreach ($container->getDefinitions() as $id => $definition) {
117
            // Omitir servicios sintéticos y abstractos.
118
            //   - Sintéticos: si el contenedor no lo crea ni lo gestiona.
119
            //   - Abstractos: plantilla que otros servicios heredan.
120
            // Solo se procesarán servicios reales (ni sintéticos ni abstractos)
121
            // y que son gestionados directamente por el contenedor.
122 17
            if ($definition->isSynthetic() || $definition->isAbstract()) {
123 17
                continue;
124
            }
125
126
            // Procesar paquetes, componentes y workers.
127 17
            $this->processFoundationServiceDefinition(
128 17
                $id,
129 17
                $definition,
130 17
                $container
131 17
            );
132
133
            // Asignar los servicios como lazy.
134
            // Se creará un proxy y se cargará solo al acceder al servicio. Esto
135
            // es cuando se acceda a un método o propiedas (aunque no deberían
136
            // existir propiedades públicas en servicios). No se cargará el
137
            // servicio si solo se inyecta y guarda en el atributo de una clase.
138 17
            $definition->setLazy(true);
139
        }
140
    }
141
142
    /**
143
     * Procesa un servicio registrado en el contenedor.
144
     *
145
     * Este método permite realizar de manera automática:
146
     *
147
     *   - Crear alias para paquetes, componentes, workers, trabajos, handlers y
148
     *     estrategias.
149
     *   - Agregar un tag a paquetes, componentes, workers, trabajos, handlers y
150
     *     estrategias.
151
     *   - Marcar como servicio público los paquetes.
152
     *
153
     * @param string $id
154
     * @param Definition $definition
155
     * @param ContainerBuilder $container
156
     * @return void
157
     */
158 17
    private function processFoundationServiceDefinition(
159
        string $id,
160
        Definition $definition,
161
        ContainerBuilder $container
162
    ): void {
163
        // Solo se procesan servicios que implementen ciertas interfaces.
164
        if (
165 17
            str_contains($id, '.')
166 17
            || !str_contains($id, '\\')
167 17
            || !array_intersect(
168 17
                $this->handledInterfaces,
169 17
                (array) class_implements($id)
170 17
            )
171
        ) {
172 17
            return;
173
        }
174
175
        // Revisar si la clase hace match con alguno de los patrones de búsqueda
176
        // de clases de servicios de Foundation.
177 12
        foreach ($this->servicesPatterns as $type => $patterns) {
178 12
            foreach ($patterns as $pattern) {
179 12
                if (preg_match($pattern, $id, $matches)) {
180 12
                    $package = Str::snake($matches['package']);
181 12
                    $component = Str::snake($matches['component'] ?? '');
182 12
                    $worker = Str::snake($matches['worker'] ?? '');
183 12
                    $command = Str::snake($matches['command'] ?? '');
184 12
                    $action = str_replace('\\_', '.', Str::snake($matches[4] ?? ''));
185
186 12
                    $aliasId = $this->processFoundationServiceType(
0 ignored issues
show
Unused Code introduced by
The assignment to $aliasId is dead and can be removed.
Loading history...
187 12
                        $type,
188 12
                        $id,
189 12
                        $definition,
190 12
                        $container,
191 12
                        $package,
192 12
                        $component,
193 12
                        $worker,
194 12
                        $command,
195 12
                        $action
196 12
                    );
197
                }
198
            }
199
        }
200
    }
201
202
    /**
203
     * Procesa un servicio genérico basado en su tipo.
204
     *
205
     * @param string $type El tipo de servicio: package, component, worker, job,
206
     * handler, strategy.
207
     * @param string $serviceId
208
     * @param Definition $definition
209
     * @param ContainerBuilder $container
210
     * @param string $package
211
     * @param string|null $component
212
     * @param string|null $worker
213
     * @param string|null $command
214
     * @param string|null $action
215
     * @return string
216
     */
217 12
    private function processFoundationServiceType(
218
        string $type,
219
        string $serviceId,
220
        Definition $definition,
221
        ContainerBuilder $container,
222
        string $package,
223
        ?string $component = null,
224
        ?string $worker = null,
225
        ?string $command = null,
226
        ?string $action = null
227
    ): string {
228
        // Construir alias ID según el tipo.
229 12
        $aliasParts = [$package];
230 12
        if ($component) {
231 12
            $aliasParts[] = $component;
232
        }
233 12
        if ($worker) {
234 12
            $aliasParts[] = $worker;
235
        }
236 12
        if ($command) {
237
            $aliasParts[] = 'command:' . $command;
238
        }
239 12
        if ($action) {
240 12
            $aliasParts[] = $type . ':' . $action;
241
        }
242
243 12
        $aliasId = $this->servicesPrefix . implode('.', $aliasParts);
244 12
        $alias = $container->setAlias($aliasId, $serviceId);
245
246
        // Determinar el tag y atributos según el tipo.
247 12
        $tagName = match ($type) {
248 12
            'package' => 'package',
249 12
            'component' => "{$package}.component",
250 12
            'worker' => "{$package}.{$component}.worker",
251
            'command' => "{$package}.{$component}.command",
252
            'job' => "{$package}.{$component}.{$worker}.job",
253
            'handler' => "{$package}.{$component}.{$worker}.handler",
254 12
            'strategy' => "{$package}.{$component}.{$worker}.strategy",
255
            default => throw new LogicException(sprintf(
256
                'Tipo de servicio %s no es manejado por CompilerPass::processFoundationServiceType().',
257
                $type
258
            )),
259 12
        };
260
261 12
        $tagAttributes = [
262 12
            'name' => match ($type) {
263 12
                'package' => $package,
264 12
                'component' => $component,
265 12
                'worker' => $worker,
266
                'command' => $command,
267
                'job' => $action,
268
                'handler' => $action,
269 12
                'strategy' => $action,
270 12
                default => null,
271 12
            },
272 12
        ];
273
274 12
        if ($type === 'component') {
275 12
            $tagAttributes['package'] = $package;
276
        }
277
278 12
        if ($type === 'worker' || $type === 'command') {
279 12
            $tagAttributes['package'] = $package;
280 12
            $tagAttributes['component'] = $component;
281
        }
282
283
        // Agregar tag al servicio.
284 12
        $definition->addTag($tagName, $tagAttributes);
285 12
        $definition->addTag('service:' . $type, $tagAttributes);
286
287
        // Si el tipo es 'package', hacemos el alias público.
288 12
        if ($type === 'package' || $type === 'command') {
289 12
            $alias->setPublic(true);
290
        }
291
292
        // Entregar el ID del alias.
293 12
        return $aliasId;
294
    }
295
}
296