Test Failed
Push — master ( 4516e7...6ee9d0 )
by Esteban De La Fuente
04:23
created

ServiceProcessingCompilerPass   A

Complexity

Total Complexity 23

Size/Duplication

Total Lines 253
Duplicated Lines 0 %

Test Coverage

Coverage 21.43%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 100
dl 0
loc 253
ccs 18
cts 84
cp 0.2143
rs 10
c 1
b 0
f 0
wmc 23

4 Methods

Rating   Name   Duplication   Size   Complexity  
A process() 0 25 4
A __construct() 0 3 1
B processFoundationServiceDefinition() 0 38 8
B processFoundationServiceType() 0 77 10
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\CommandInterface;
28
use Derafu\Lib\Core\Foundation\Contract\ServiceInterface;
29
use Derafu\Lib\Core\Helper\Str;
30
use LogicException;
31
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
32
use Symfony\Component\DependencyInjection\ContainerBuilder;
33
use Symfony\Component\DependencyInjection\Definition;
34
35
/**
36
 * Clase para modificar servicios previo a la compilación del contenedor.
37
 */
38
class ServiceProcessingCompilerPass implements CompilerPassInterface
39
{
40
    /**
41
     * Prefijo con el que deben ser nombrados todos los servicios asociados a la
42
     * aplicación.
43
     *
44
     * Esto se utiliza especialmente al nombrar paquetes, componentes y workers.
45
     *
46
     * @var string
47
     */
48
    protected string $servicesPrefix;
49
50
    /**
51
     * Patrones de búsqueda de clases de servicios de Foundation.
52
     *
53
     * Estas clases deben implementar además la interfaz ServiceInterface.
54
     *
55
     * @var array<string, array>
56
     */
57
    protected array $servicesPatterns = [
58
        // Packages.
59
        'package' => [
60
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Contract\\\\(?P=package)PackageInterface$/",
61
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\(?P=package)Package$/",
62
        ],
63
        // Components.
64
        'component' => [
65
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?P=component)ComponentInterface$/",
66
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\(?P=component)Component$/",
67
        ],
68
        // Workers.
69
        'worker' => [
70
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<worker>[A-Za-z0-9_]+)WorkerInterface$/",
71
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Worker\\\\(?<worker>[A-Za-z0-9_]+)Worker$/",
72
        ],
73
        // Commands.
74
        'command' => [
75
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Contract\\\\(?<command>[A-Za-z0-9_]+)CommandInterface$/",
76
            "/Package\\\\(?<package>[A-Za-z0-9_]+)\\\\Component\\\\(?<component>[A-Za-z0-9_]+)\\\\Command\\\\(?<command>[A-Za-z0-9_]+)Command$/",
77
        ],
78
        // Jobs.
79
        'job' => [
80
            "/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$/",
81
            "/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$/",
82
        ],
83
        // Handlers.
84
        'handler' => [
85
            "/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$/",
86
            "/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$/",
87
        ],
88
        // Strategies.
89
        'strategy' => [
90
            "/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$/",
91
            "/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$/",
92
        ],
93
    ];
94
95
    /**
96
     * Constructor de la clase.
97
     *
98
     * @param string $servicesPrefix
99
     */
100 5
    public function __construct(string $servicesPrefix)
101
    {
102 5
        $this->servicesPrefix = $servicesPrefix;
103
    }
104
105
    /**
106
     * Procesar servicios.
107
     *
108
     * @param ContainerBuilder $container
109
     * @return void
110
     */
111 5
    public function process(ContainerBuilder $container): void
112
    {
113 5
        foreach ($container->getDefinitions() as $id => $definition) {
114
            // Omitir servicios sintéticos y abstractos.
115
            //   - Sintéticos: si el contenedor no lo crea ni lo gestiona.
116
            //   - Abstractos: plantilla que otros servicios heredan.
117
            // Solo se procesarán servicios reales (ni sintéticos ni abstractos)
118
            // y que son gestionados directamente por el contenedor.
119 5
            if ($definition->isSynthetic() || $definition->isAbstract()) {
120 5
                continue;
121
            }
122
123
            // Procesar paquetes, componentes y workers.
124 5
            $this->processFoundationServiceDefinition(
125 5
                $id,
126 5
                $definition,
127 5
                $container
128 5
            );
129
130
            // Asignar los servicios como lazy.
131
            // Se creará un proxy y se cargará solo al acceder al servicio. Esto
132
            // es cuando se acceda a un método o propiedas (aunque no deberían
133
            // existir propiedades públicas en servicios). No se cargará el
134
            // servicio si solo se inyecta y guarda en el atributo de una clase.
135 5
            $definition->setLazy(true);
136
        }
137
    }
138
139
    /**
140
     * Procesa un servicio registrado en el contenedor.
141
     *
142
     * Este método permite realizar de manera automática:
143
     *
144
     *   - Crear alias para paquetes, componentes, workers, trabajos, handlers y
145
     *     estrategias.
146
     *   - Agregar un tag a paquetes, componentes, workers, trabajos, handlers y
147
     *     estrategias.
148
     *   - Marcar como servicio público los paquetes.
149
     *
150
     * @param string $id
151
     * @param Definition $definition
152
     * @param ContainerBuilder $container
153
     * @return void
154
     */
155 5
    private function processFoundationServiceDefinition(
156
        string $id,
157
        Definition $definition,
158
        ContainerBuilder $container
159
    ): void {
160
        // Solo se procesan servicios que implementen `ServiceInterface`.
161
        if (
162 5
            str_contains($id, '.')
163 5
            || !str_contains($id, '\\')
164
            || (
165 5
                !in_array(ServiceInterface::class, (array) class_implements($id))
166 5
                && !in_array(CommandInterface::class, (array) class_implements($id))
167
            )
168
        ) {
169 5
            return;
170
        }
171
172
        // Revisar si la clase hace match con alguno de los patrones de búsqueda
173
        // de clases de servicios de Foundation.
174
        foreach ($this->servicesPatterns as $type => $patterns) {
175
            foreach ($patterns as $pattern) {
176
                if (preg_match($pattern, $id, $matches)) {
177
                    $package = Str::snake($matches['package']);
178
                    $component = Str::snake($matches['component'] ?? '');
179
                    $worker = Str::snake($matches['worker'] ?? '');
180
                    $command = Str::snake($matches['command'] ?? '');
181
                    $action = str_replace('\\_', '.', Str::snake($matches[4] ?? ''));
182
183
                    $aliasId = $this->processFoundationServiceType(
0 ignored issues
show
Unused Code introduced by
The assignment to $aliasId is dead and can be removed.
Loading history...
184
                        $type,
185
                        $id,
186
                        $definition,
187
                        $container,
188
                        $package,
189
                        $component,
190
                        $worker,
191
                        $command,
192
                        $action
193
                    );
194
                }
195
            }
196
        }
197
    }
198
199
    /**
200
     * Procesa un servicio genérico basado en su tipo.
201
     *
202
     * @param string $type El tipo de servicio: package, component, worker, job,
203
     * handler, strategy.
204
     * @param string $serviceId
205
     * @param Definition $definition
206
     * @param ContainerBuilder $container
207
     * @param string $package
208
     * @param string|null $component
209
     * @param string|null $worker
210
     * @param string|null $command
211
     * @param string|null $action
212
     * @return string
213
     */
214
    private function processFoundationServiceType(
215
        string $type,
216
        string $serviceId,
217
        Definition $definition,
218
        ContainerBuilder $container,
219
        string $package,
220
        ?string $component = null,
221
        ?string $worker = null,
222
        ?string $command = null,
223
        ?string $action = null
224
    ): string {
225
        // Construir alias ID según el tipo.
226
        $aliasParts = [$package];
227
        if ($component) {
228
            $aliasParts[] = $component;
229
        }
230
        if ($worker) {
231
            $aliasParts[] = $worker;
232
        }
233
        if ($command) {
234
            $aliasParts[] = 'command:' . $command;
235
        }
236
        if ($action) {
237
            $aliasParts[] = $type . ':' . $action;
238
        }
239
240
        $aliasId = $this->servicesPrefix . implode('.', $aliasParts);
241
        $alias = $container->setAlias($aliasId, $serviceId);
242
243
        // Determinar el tag y atributos según el tipo.
244
        $tagName = match ($type) {
245
            'package' => 'package',
246
            'component' => "{$package}.component",
247
            'worker' => "{$package}.{$component}.worker",
248
            'command' => "{$package}.{$component}.command",
249
            'job' => "{$package}.{$component}.{$worker}.job",
250
            'handler' => "{$package}.{$component}.{$worker}.handler",
251
            'strategy' => "{$package}.{$component}.{$worker}.strategy",
252
            default => throw new LogicException(sprintf(
253
                'Tipo de servicio %s no es manejado por CompilerPass::processFoundationServiceType().',
254
                $type
255
            )),
256
        };
257
258
        $tagAttributes = [
259
            'name' => match ($type) {
260
                'package' => $package,
261
                'component' => $component,
262
                'worker' => $worker,
263
                'command' => $command,
264
                'job' => $action,
265
                'handler' => $action,
266
                'strategy' => $action,
267
                default => null,
268
            },
269
        ];
270
271
        if ($type === 'component') {
272
            $tagAttributes['package'] = $package;
273
        }
274
275
        if ($type === 'worker' || $type === 'command') {
276
            $tagAttributes['package'] = $package;
277
            $tagAttributes['component'] = $component;
278
        }
279
280
        // Agregar tag al servicio.
281
        $definition->addTag($tagName, $tagAttributes);
282
        $definition->addTag('service:' . $type, $tagAttributes);
283
284
        // Si el tipo es 'package', hacemos el alias público.
285
        if ($type === 'package' || $type === 'command') {
286
            $alias->setPublic(true);
287
        }
288
289
        // Entregar el ID del alias.
290
        return $aliasId;
291
    }
292
}
293