Passed
Push — master ( ae3156...6fb22a )
by Esteban De La Fuente
03:24
created

processFoundationServiceDefinition()   B

Complexity

Conditions 7
Paths 5

Size

Total Lines 33
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 21
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 21
c 0
b 0
f 0
nc 5
nop 3
dl 0
loc 33
ccs 21
cts 21
cp 1
crap 7
rs 8.6506
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>[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
     * Constructor de la clase.
91
     *
92
     * @param string $servicesPrefix
93
     */
94 16
    public function __construct(string $servicesPrefix)
95
    {
96 16
        $this->servicesPrefix = $servicesPrefix;
97
    }
98
99
    /**
100
     * Procesar servicios.
101
     *
102
     * @param ContainerBuilder $container
103
     * @return void
104
     */
105 16
    public function process(ContainerBuilder $container): void
106
    {
107 16
        foreach ($container->getDefinitions() as $id => $definition) {
108
            // Omitir servicios sintéticos y abstractos.
109
            //   - Sintéticos: si el contenedor no lo crea ni lo gestiona.
110
            //   - Abstractos: plantilla que otros servicios heredan.
111
            // Solo se procesarán servicios reales (ni sintéticos ni abstractos)
112
            // y que son gestionados directamente por el contenedor.
113 16
            if ($definition->isSynthetic() || $definition->isAbstract()) {
114 16
                continue;
115
            }
116
117
            // Procesar paquetes, componentes y workers.
118 16
            $this->processFoundationServiceDefinition(
119 16
                $id,
120 16
                $definition,
121 16
                $container
122 16
            );
123
124
            // Asignar los servicios como lazy.
125
            // Se creará un proxy y se cargará solo al acceder al servicio. Esto
126
            // es cuando se acceda a un método o propiedas (aunque no deberían
127
            // existir propiedades públicas en servicios). No se cargará el
128
            // servicio si solo se inyecta y guarda en el atributo de una clase.
129 16
            $definition->setLazy(true);
130
        }
131
    }
132
133
    /**
134
     * Procesa un servicio registrado en el contenedor.
135
     *
136
     * Este método permite realizar de manera automática:
137
     *
138
     *   - Crear alias para paquetes, componentes, workers, trabajos, handlers y
139
     *     estrategias.
140
     *   - Agregar un tag a paquetes, componentes, workers, trabajos, handlers y
141
     *     estrategias.
142
     *   - Marcar como servicio público los paquetes.
143
     *
144
     * @param string $id
145
     * @param Definition $definition
146
     * @param ContainerBuilder $container
147
     * @return void
148
     */
149 16
    private function processFoundationServiceDefinition(
150
        string $id,
151
        Definition $definition,
152
        ContainerBuilder $container
153
    ): void {
154
        // Solo se procesan servicios que implementen `ServiceInterface`.
155
        if (
156 16
            str_contains($id, '.')
157 16
            || !str_contains($id, '\\')
158 16
            || !in_array(ServiceInterface::class, (array) class_implements($id))
159
        ) {
160 6
            return;
161
        }
162
163
        // Revisar si la clase hace match con alguno de los patrones de búsqueda
164
        // de clases de servicios de Foundation.
165 10
        foreach ($this->servicesPatterns as $type => $patterns) {
166 10
            foreach ($patterns as $pattern) {
167 10
                if (preg_match($pattern, $id, $matches)) {
168 10
                    $package = Str::snake($matches['package']);
169 10
                    $component = Str::snake($matches['component'] ?? '');
170 10
                    $worker = Str::snake($matches['worker'] ?? '');
171 10
                    $action = str_replace('\\_', '.', Str::snake($matches[4] ?? ''));
172
173 10
                    $aliasId = $this->processFoundationServiceType(
0 ignored issues
show
Unused Code introduced by
The assignment to $aliasId is dead and can be removed.
Loading history...
174 10
                        $type,
175 10
                        $id,
176 10
                        $definition,
177 10
                        $container,
178 10
                        $package,
179 10
                        $component,
180 10
                        $worker,
181 10
                        $action
182 10
                    );
183
                }
184
            }
185
        }
186
    }
187
188
    /**
189
     * Procesa un servicio genérico basado en su tipo.
190
     *
191
     * @param string $type El tipo de servicio: package, component, worker, job,
192
     * handler, strategy.
193
     * @param string $serviceId
194
     * @param Definition $definition
195
     * @param ContainerBuilder $container
196
     * @param string $package
197
     * @param string|null $component
198
     * @param string|null $worker
199
     * @param string|null $action
200
     * @return string
201
     */
202 10
    private function processFoundationServiceType(
203
        string $type,
204
        string $serviceId,
205
        Definition $definition,
206
        ContainerBuilder $container,
207
        string $package,
208
        ?string $component = null,
209
        ?string $worker = null,
210
        ?string $action = null
211
    ): string {
212
        // Construir alias ID según el tipo.
213 10
        $aliasParts = [$package];
214 10
        if ($component) {
215 10
            $aliasParts[] = $component;
216
        }
217 10
        if ($worker) {
218 10
            $aliasParts[] = $worker;
219
        }
220 10
        if ($action) {
221
            $aliasParts[] = $type . ':' . $action;
222
        }
223
224 10
        $aliasId = $this->servicesPrefix . implode('.', $aliasParts);
225 10
        $alias = $container->setAlias($aliasId, $serviceId);
226
227
        // Determinar el tag y atributos según el tipo.
228 10
        $tagName = match ($type) {
229 10
            'package' => 'package',
230 10
            'component' => "{$package}.component",
231 10
            'worker' => "{$package}.{$component}.worker",
232
            'job' => "{$package}.{$component}.{$worker}.job",
233
            'handler' => "{$package}.{$component}.{$worker}.handler",
234
            'strategy' => "{$package}.{$component}.{$worker}.strategy",
235
            default => throw new LogicException(sprintf(
236
                'Tipo de servicio %s no es manejado por CompilerPass::processFoundationServiceType().',
237
                $type
238
            )),
239 10
        };
240
241 10
        $tagAttributes = [
242 10
            'name' => match ($type) {
243 10
                'package' => $package,
244 10
                'component' => $component,
245 10
                'worker' => $worker,
246
                'job' => $action,
247
                'handler' => $action,
248
                'strategy' => $action,
249 10
                default => null,
250 10
            },
251 10
        ];
252
253 10
        if ($type === 'component') {
254 10
            $tagAttributes['package'] = $package;
255
        }
256
257 10
        if ($type === 'worker') {
258 10
            $tagAttributes['package'] = $package;
259 10
            $tagAttributes['component'] = $component;
260
        }
261
262
        // Agregar tag al servicio.
263 10
        $definition->addTag($tagName, $tagAttributes);
264 10
        $definition->addTag('service:' . $type, $tagAttributes);
265
266
        // Si el tipo es 'package', hacemos el alias público.
267 10
        if ($type === 'package') {
268 10
            $alias->setPublic(true);
269
        }
270
271
        // Entregar el ID del alias.
272 10
        return $aliasId;
273
    }
274
}
275