Passed
Push — master ( 035e32...ae3156 )
by Esteban De La Fuente
03:22
created

CompilerPass   A

Complexity

Total Complexity 19

Size/Duplication

Total Lines 227
Duplicated Lines 0 %

Test Coverage

Coverage 84%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 86
dl 0
loc 227
ccs 63
cts 75
cp 0.84
rs 10
c 1
b 0
f 0
wmc 19

4 Methods

Rating   Name   Duplication   Size   Complexity  
A processFoundationServiceType() 0 57 5
B processFoundationServiceDefinition() 0 38 9
A process() 0 25 4
A __construct() 0 3 1
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 durante la compilación.
36
 */
37
class CompilerPass 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
        // Strategies.
58
        'strategy' => [
59
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Contract\\\\([A-Za-z0-9]+)StrategyInterface$/",
60
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Worker\\\\([A-Za-z0-9_]+)\\\\Strategy\\\\([A-Za-z0-9_]+)\\\\([A-Za-z0-9_]+)Strategy$/",
61
        ],
62
        // Handlers.
63
        'handler' => [
64
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Contract\\\\([A-Za-z0-9]+)HandlerInterface$/",
65
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Worker\\\\([A-Za-z0-9_]+)\\\\Handler\\\\([A-Za-z0-9_]+)Handler$/",
66
        ],
67
        // Jobs.
68
        'job' => [
69
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Contract\\\\([A-Za-z0-9]+)JobInterface$/",
70
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Worker\\\\([A-Za-z0-9_]+)\\\\Job\\\\([A-Za-z0-9_]+)Job$/",
71
        ],
72
        // Workers.
73
        'worker' => [
74
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Contract\\\\([A-Za-z0-9]+)WorkerInterface$/",
75
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Worker\\\\([A-Za-z0-9_]+)Worker$/",
76
        ],
77
        // Components.
78
        'component' => [
79
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)\\\\Contract\\\\(?:[A-Z][a-zA-Z0-9]+)ComponentInterface$/",
80
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Component\\\\([A-Za-z0-9_]+)Component$/",
81
        ],
82
        // Packages.
83
        'package' => [
84
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\Contract\\\\(?:[A-Z][a-zA-Z0-9]+)PackageInterface$/",
85
            "/\\\\Package\\\\([A-Za-z0-9_]+)\\\\(?:[A-Za-z0-9_]+)Package$/",
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 en tiempo de compilación.
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 10
            || !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[1]);
169 10
                    $component = Str::snake($matches[2] ?? '');
170 10
                    $worker = Str::snake($matches[3] ?? '');
171 10
                    $action1 = Str::snake($matches[4] ?? ''); // Job, Handler o Strategy.
172 10
                    $action2 = Str::snake($matches[5] ?? ''); // Usado solo para estrategias por ahora.
173 10
                    $action = ($action1 && $action2)
174
                        ? $action1 . '.' . $action2
175 10
                        : $action1
176 10
                    ;
177
178 10
                    $this->processFoundationServiceType(
179 10
                        $type,
180 10
                        $id,
181 10
                        $definition,
182 10
                        $container,
183 10
                        $package,
184 10
                        $component,
185 10
                        $worker,
186 10
                        $action
187 10
                    );
188
                }
189
            }
190
        }
191
    }
192
193
    /**
194
     * Procesa un servicio genérico basado en su tipo.
195
     *
196
     * @param string $type El tipo de servicio: package, component, worker, job,
197
     * handler, strategy.
198
     * @param string $serviceId
199
     * @param Definition $definition
200
     * @param ContainerBuilder $container
201
     * @param string $package
202
     * @param string|null $component
203
     * @param string|null $worker
204
     * @param string|null $action
205
     * @return void
206
     */
207 10
    private function processFoundationServiceType(
208
        string $type,
209
        string $serviceId,
210
        Definition $definition,
211
        ContainerBuilder $container,
212
        string $package,
213
        ?string $component = null,
214
        ?string $worker = null,
215
        ?string $action = null
216
    ): void {
217
        // Construir alias ID según el tipo.
218 10
        $aliasParts = [$package];
219 10
        if ($component) {
220 10
            $aliasParts[] = $component;
221
        }
222 10
        if ($worker) {
223 10
            $aliasParts[] = $worker;
224
        }
225 10
        if ($action) {
226
            $aliasParts[] = $action;
227
        }
228
229 10
        $aliasId = $this->servicesPrefix . implode('.', $aliasParts);
230 10
        $alias = $container->setAlias($aliasId, $serviceId);
231
232
        // Determinar el tag y atributos según el tipo.
233 10
        $tagName = match ($type) {
234 10
            'package' => 'package',
235 10
            'component' => "{$package}.component",
236 10
            'worker' => "{$package}.{$component}.worker",
237
            'job' => "{$package}.{$component}.{$worker}.job",
238
            'handler' => "{$package}.{$component}.{$worker}.handler",
239
            'strategy' => "{$package}.{$component}.{$worker}.strategy",
240
            default => throw new LogicException(sprintf(
241
                'Tipo de servicio %s no es manejado por CompilerPass::processFoundationServiceType().',
242
                $type
243
            )),
244 10
        };
245
246 10
        $tagAttributes = [
247 10
            'name' => match ($type) {
248 10
                'package' => $package,
249 10
                'component' => $component,
250 10
                'worker' => $worker,
251
                'job' => $action,
252
                'handler' => $action,
253
                'strategy' => $action,
254 10
                default => null,
255 10
            },
256 10
        ];
257
258
        // Agregar tag al servicio.
259 10
        $definition->addTag($tagName, $tagAttributes);
260
261
        // Si el tipo es 'package', hacemos el alias público.
262 10
        if ($type === 'package') {
263 10
            $alias->setPublic(true);
264
        }
265
    }
266
}
267