Test Failed
Push — master ( 59e870...659f9a )
by Esteban De La Fuente
04:33
created

DocumentBagManagerWorker   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 380
Duplicated Lines 0 %

Test Coverage

Coverage 64.9%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 121
dl 0
loc 380
ccs 98
cts 151
cp 0.649
rs 9.1199
c 1
b 0
f 0
wmc 41

10 Methods

Rating   Name   Duplication   Size   Complexity  
A ensureDocument() 0 10 4
A ensureTipoDocumento() 0 26 4
A ensureReceptor() 0 39 6
A create() 0 44 5
A ensureParsedData() 0 10 3
A __construct() 0 12 1
A ensureXmlDocument() 0 32 4
A normalize() 0 19 2
A ensureNormalizedData() 0 41 6
A ensureEmisor() 0 39 6

How to fix   Complexity   

Complex Class

Complex classes like DocumentBagManagerWorker often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use DocumentBagManagerWorker, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * LibreDTE: Biblioteca PHP (Núcleo).
7
 * Copyright (C) LibreDTE <https://www.libredte.cl>
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
20
 * GNU junto a este programa.
21
 *
22
 * En caso contrario, consulte <http://www.gnu.org/licenses/agpl.html>.
23
 */
24
25
namespace libredte\lib\Core\Package\Billing\Component\Document\Worker;
26
27
use Derafu\Lib\Core\Foundation\Abstract\AbstractWorker;
28
use Derafu\Lib\Core\Package\Prime\Component\Entity\Contract\EntityComponentInterface;
29
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlComponentInterface;
30
use Derafu\Lib\Core\Package\Prime\Component\Xml\Contract\XmlInterface;
31
use libredte\lib\Core\Package\Billing\Component\Document\Contract\BuilderWorkerInterface;
32
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagInterface;
33
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentBagManagerWorkerInterface;
34
use libredte\lib\Core\Package\Billing\Component\Document\Contract\DocumentInterface;
35
use libredte\lib\Core\Package\Billing\Component\Document\Contract\NormalizerWorkerInterface;
36
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ParserWorkerInterface;
37
use libredte\lib\Core\Package\Billing\Component\Document\Contract\SanitizerWorkerInterface;
38
use libredte\lib\Core\Package\Billing\Component\Document\Contract\TipoDocumentoFactoryInterface;
39
use libredte\lib\Core\Package\Billing\Component\Document\Contract\TipoDocumentoInterface;
40
use libredte\lib\Core\Package\Billing\Component\Document\Contract\ValidatorWorkerInterface;
41
use libredte\lib\Core\Package\Billing\Component\Document\Exception\DocumentBagManagerException;
42
use libredte\lib\Core\Package\Billing\Component\Document\Support\DocumentBag;
43
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\EmisorFactoryInterface;
44
use libredte\lib\Core\Package\Billing\Component\TradingParties\Contract\ReceptorFactoryInterface;
45
46
/**
47
 * Clase para el administrador de la bolsa con los datos de un DTE.
48
 */
49
class DocumentBagManagerWorker extends AbstractWorker implements DocumentBagManagerWorkerInterface
50
{
51
    protected $documentBagClass = DocumentBag::class;
52
53 67
    public function __construct(
54
        private XmlComponentInterface $xmlComponent,
55
        private BuilderWorkerInterface $builderWorker,
56
        private ParserWorkerInterface $parserWorker,
57
        private NormalizerWorkerInterface $normalizerWorker,
58
        private SanitizerWorkerInterface $sanitizerWorker,
59
        private ValidatorWorkerInterface $validatorWorker,
60
        private EntityComponentInterface $entityComponent,
61
        private TipoDocumentoFactoryInterface $tipoDocumentoFactory,
62
        private EmisorFactoryInterface $emisorFactory,
63
        private ReceptorFactoryInterface $receptorFactory
64
    ) {
65 67
    }
66
67
    /**
68
     * {@inheritdoc}
69
     */
70 59
    public function create(
71
        string|array|XmlInterface|DocumentInterface $source,
72
        bool $normalizeAll = true
73
    ): DocumentBagInterface
74
    {
75
        // Asignar (si se pasó) o crear la bolsa.
76 59
        $class = $this->documentBagClass;
77 59
        $bag = new $class();
78
79
        // Si los datos vienen como string se deben parsear para asignar.
80 59
        if (is_string($source)) {
0 ignored issues
show
introduced by
The condition is_string($source) is always false.
Loading history...
81
            $aux = explode(':', $source, 1);
82
            $parserStrategy = str_replace('parser.strategy.', '', $aux[0]);
83
            $inputData = $aux[1] ?? '';
84
            $bag->setInputData($inputData);
85
            $parser = $this->parserWorker->setOptions([
86
                'strategy' => $parserStrategy,
87
            ]);
88
            $parser->parse($bag);
89
        }
90
91
        // Si los datos vienen como arreglo son los datos parseados.
92 59
        if (is_array($source)) {
0 ignored issues
show
introduced by
The condition is_array($source) is always true.
Loading history...
93 1
            $bag->setParsedData($source);
94
        }
95
96
        // Si los datos vienen como documento XML es un XML cargado desde un
97
        // string XML (carga realizada por LoaderWorker).
98 59
        if ($source instanceof XmlInterface) {
0 ignored issues
show
introduced by
$source is never a sub-type of Derafu\Lib\Core\Package\...l\Contract\XmlInterface.
Loading history...
99 59
            $bag->setXmlDocument($source);
100
        }
101
102
        // Si los datos vienen como documento tributario entonces es un
103
        // documento que ya está creado. Puede estar o no timbrado y firmado,
104
        // eso no se determina ni valida acá.
105 59
        if ($source instanceof DocumentInterface) {
0 ignored issues
show
introduced by
$source is never a sub-type of libredte\lib\Core\Packag...tract\DocumentInterface.
Loading history...
106
            $bag->setDocument($source);
107
        }
108
109
        // Normalizar los datos de la bolsa según otros datos que contenga.
110 59
        $bag = $this->normalize($bag, all: $normalizeAll);
111
112
        // Entregar la bolsa creada.
113 59
        return $bag;
114
    }
115
116
    /**
117
     * {@inheritdoc}
118
     */
119 67
    public function normalize(
120
        DocumentBagInterface $bag,
121
        bool $all = false
122
    ): DocumentBagInterface {
123
        // Datos esenciales que se normalizan.
124 67
        $this->ensureParsedData($bag);
125 67
        $this->ensureTipoDocumento($bag);
126
127
        // Datos extras que se pueden normalizar si se solicitó normalizar todo.
128 65
        if ($all === true) {
129 63
            $this->ensureNormalizedData($bag);
130 63
            $this->ensureXmlDocument($bag);
131 63
            $this->ensureDocument($bag);
132 63
            $this->ensureEmisor($bag);
133 63
            $this->ensureReceptor($bag);
134
        }
135
136
        // Entregar la bolsa normalizada.
137 65
        return $bag;
138
    }
139
140
    /**
141
     * Asegura que existan datos parseados en la bolsa si existen datos de
142
     * entrada para poder determinarlo y no está asignado previamente.
143
     *
144
     * Requiere: $bag->getInputData().
145
     *
146
     * Además, si los datos no usan la estrategia por defecto de parseo se debe
147
     * indicar en las opciones de la bolsa.
148
     *
149
     * @param DocumentBagInterface $bag
150
     * @return void
151
     */
152 67
    protected function ensureParsedData(DocumentBagInterface $bag): void
153
    {
154
        // Verificar si es necesario, y se puede, asignar.
155 67
        if ($bag->getParsedData() || !$bag->getInputData()) {
156 63
            return;
157
        }
158
159
        // Parsear los datos.
160 11
        $parser = $this->parserWorker->setOptions($bag->getParserOptions());
161 11
        $parser->parse($bag);
162
    }
163
164
    /**
165
     * Asegura que exista un tipo de documento en la bolsa si existen datos para
166
     * poder determinarlo y no está asignado previamente.
167
     *
168
     * Requiere: $bag->getParsedData().
169
     *
170
     * @param DocumentBagInterface $bag
171
     * @return void
172
     */
173 67
    protected function ensureTipoDocumento(DocumentBagInterface $bag): void
174
    {
175
        // Verificar si es necesario, y se puede, asignar.
176 67
        if ($bag->getTipoDocumento() || !$bag->getCodigoTipoDocumento()) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $bag->getCodigoTipoDocumento() of type integer|null is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
177 59
            return;
178
        }
179
180
        // Buscar el tipo de documento tributario que se desea construir.
181 64
        $codigoTipoDocumento = $bag->getCodigoTipoDocumento();
182 64
        $tipoDocumento = $this->entityComponent
183 64
            ->getRepository(
184 64
                TipoDocumentoInterface::class,
185 64
                $this->tipoDocumentoFactory
0 ignored issues
show
Unused Code introduced by
The call to Derafu\Lib\Core\Package\...erface::getRepository() has too many arguments starting with $this->tipoDocumentoFactory. ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-call  annotation

185
            ->/** @scrutinizer ignore-call */ getRepository(

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
186 64
            )
187 64
            ->find($codigoTipoDocumento)
188 64
        ;
189
190
        // Si el documento no existe error.
191 64
        if (!$tipoDocumento) {
192
            throw new DocumentBagManagerException(sprintf(
193
                'No se encontró un código de documento tributario válido en los datos del DTE.'
194
            ));
195
        }
196
197
        // Asignar el tipo documento a la bolsa.
198 64
        $bag->setTipoDocumento($tipoDocumento);
199
    }
200
201
    /**
202
     * Asegura que existan los datos normalizados en la bolsa si existen datos
203
     * parseados para poder determinarlo y no está asignado previamente.
204
     *
205
     * Los datos normalizados también se pueden crear si existe un XmlDocument
206
     * o un DTE.
207
     *
208
     * Requiere: $bag->getParsedData() o $bag->getXmlDocument() o $bag->getDocument().
209
     *
210
     * @param DocumentBagInterface $bag
211
     * @return void
212
     */
213 63
    protected function ensureNormalizedData(DocumentBagInterface $bag): void
214
    {
215
        // Verificar si es necesario, y se puede, asignar.
216 63
        if ($bag->getNormalizedData()) {
217 58
            return;
218
        }
219
220
        // Construir los datos normalizados a partir de los datos parseados.
221 6
        if ($bag->getParsedData()) {
222
            // Normalizar los datos del documento de la bolsa si corresponde.
223 1
            $normalize = $bag->getNormalizerOptions()['normalize'] ?? true;
224 1
            if ($normalize) {
225
                // Normalizar los datos parseados de la bolsa.
226 1
                $this->normalizerWorker->normalize($bag);
227
228
                // Sanitizar los datos normalizados de la bolsa.
229 1
                $this->sanitizerWorker->sanitize($bag);
230
231
                // Validar los datos normalizados y sanitizados de la bolsa.
232 1
                $this->validatorWorker->validate($bag);
233
            }
234
        }
235
236
        // Construir los datos normalizados a partir del documento XML.
237
        // Importante: se asume que el documento XML se cargó desde un XML que
238
        // es válido y por ende normalizado.
239 5
        elseif ($bag->getXmlDocument()) {
240 5
            $normalizedData = $this->xmlComponent->getDecoderWorker()->decode(
241 5
                $bag->getXmlDocument()
242 5
            );
243 5
            $bag->setNormalizedData($normalizedData);
244
        }
245
246
        // Construir los datos normalizados a partir del DTE.
247
        // Importante: se asume que el DTE se cargó desde un XML que
248
        // es válido y por ende normalizado.
249
        elseif ($bag->getDocument()) {
250
            $normalizedData = $this->xmlComponent->getDecoderWorker()->decode(
251
                $bag->getDocument()->getXmlDocument()
252
            );
253
            $bag->setNormalizedData($normalizedData);
254
        }
255
    }
256
257
    /**
258
     * Asegura que existan un documento XML en la bolsa si existen datos
259
     * normalizados para poder determinarlo y no está asignado previamente.
260
     *
261
     * El XmlDocument también se puede crear si existe DTE.
262
     *
263
     * Requiere: $bag->getNormalizedData() o $bag->getDocument().
264
     *
265
     * @param DocumentBagInterface $bag
266
     * @return void
267
     */
268 63
    protected function ensureXmlDocument(DocumentBagInterface $bag): void
269
    {
270
        // Verificar si es necesario, y se puede, asignar.
271 63
        if ($bag->getXmlDocument()) {
272 63
            return;
273
        }
274
275
        // Construir el XmlDocument a partir de los datos normalizados.
276 1
        if ($bag->getNormalizedData()) {
277 1
            $tagXml = $bag->getTipoDocumento()->getTagXml()->getNombre();
278 1
            $xmlDocumentData = [
279 1
                'DTE' => [
280 1
                    '@attributes' => [
281 1
                        'version' => '1.0',
282 1
                        'xmlns' => 'http://www.sii.cl/SiiDte',
283 1
                    ],
284 1
                    $tagXml => array_merge([
285 1
                        '@attributes' => [
286 1
                            'ID' => $bag->getId(),
287 1
                        ],
288 1
                    ], $bag->getNormalizedData()),
289 1
                ],
290 1
            ];
291 1
            $xmlDocument = $this->xmlComponent->getEncoderWorker()->encode(
292 1
                $xmlDocumentData
293 1
            );
294 1
            $bag->setXmlDocument($xmlDocument);
295
        }
296
297
        // Asignar el XmlDocument a partir del DTE.
298
        elseif ($bag->getDocument()) {
299
            $bag->setXmlDocument($bag->getDocument()->getXmlDocument());
300
        }
301
    }
302
303
    /**
304
     * Asegura que existan un DTE en la bolsa si existe un XmlDocument
305
     * para poder crearlo y no está asignado previamente.
306
     *
307
     * Requiere: $bag->getXmlDocument() y $bag->getTipoDocumento().
308
     *
309
     * @param DocumentBagInterface $bag
310
     * @return void
311
     */
312 63
    protected function ensureDocument(DocumentBagInterface $bag): void
313
    {
314
        // Verificar si es necesario, y se puede, asignar.
315 63
        if ($bag->getDocument() || !$bag->getXmlDocument() || !$bag->getTipoDocumento()) {
316 58
            return;
317
        }
318
319
        // Crear el DTE.
320 6
        $document = $this->builderWorker->create($bag);
321 6
        $bag->setDocument($document);
322
    }
323
324
    /**
325
     * Asegura que existan el emisor en la bolsa si existe el dTE
326
     * para poder determinarlo y no está asignado previamente.
327
     *
328
     * El emisor también se pueden crear si existen un XmlDocument, datos
329
     * normalizados o datos parseados.
330
     *
331
     * Requiere: $bag->getDocument(), $bag->getXmlDocument(),
332
     * $bag->getNormalizedData() o $bag->getParsedData().
333
     *
334
     * @param DocumentBagInterface $bag
335
     * @return void
336
     */
337 63
    protected function ensureEmisor(DocumentBagInterface $bag): void
338
    {
339
        // Verificar si es necesario, y se puede, asignar.
340 63
        if ($bag->getEmisor()) {
341 3
            return;
342
        }
343
344
        // Crear a partir de los datos del DTE (sería lo más normal).
345 63
        if ($bag->getDocument()) {
346 63
            $emisor = $this->emisorFactory->create(
347 63
                $bag->getDocument()->getEmisor()
348 63
            );
349 63
            $bag->setEmisor($emisor);
350
        }
351
352
        // Crear a partir de los datos del XmlDocument.
353
        elseif ($bag->getXmlDocument()) {
354
            $emisor = $this->emisorFactory->create(
355
                $bag->getXmlDocument()->query(
0 ignored issues
show
Bug introduced by
It seems like $bag->getXmlDocument()->...y('/Encabezado/Emisor') can also be of type null and string; however, parameter $data of libredte\lib\Core\Packag...toryInterface::create() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

355
                /** @scrutinizer ignore-type */ $bag->getXmlDocument()->query(
Loading history...
356
                    '/Encabezado/Emisor'
357
                )
358
            );
359
            $bag->setEmisor($emisor);
360
        }
361
362
        // Crear a partir de los datos normalizados.
363
        elseif ($bag->getNormalizedData()) {
364
            $emisor = $this->emisorFactory->create(
365
                $bag->getNormalizedData()['Encabezado']['Emisor']
366
            );
367
            $bag->setEmisor($emisor);
368
        }
369
370
        // Crear a partir de los datos parseados.
371
        elseif ($bag->getParsedData()) {
372
            $emisor = $this->emisorFactory->create(
373
                $bag->getParsedData()['Encabezado']['Emisor']
374
            );
375
            $bag->setEmisor($emisor);
376
        }
377
    }
378
379
    /**
380
     * Asegura que existan el emisor en la bolsa si existen datos
381
     * normalizados para poder determinarlo y no está asignado previamente.
382
     *
383
     * El emisor también se pueden crear si existe un XmlDocument o un DTE.
384
     *
385
     * Requiere: $bag->getNormalizedData() o $bag->getXmlDocument() o $bag->getDocument().
386
     *
387
     * @param DocumentBagInterface $bag
388
     * @return void
389
     */
390 63
    protected function ensureReceptor(DocumentBagInterface $bag): void
391
    {
392
        // Si ya está asignado el receptor no se hace nada.
393 63
        if ($bag->getReceptor()) {
394 3
            return;
395
        }
396
397
        // Crear a partir de los datos del DTE (sería lo más normal).
398 63
        if ($bag->getDocument()) {
399 63
            $emisor = $this->receptorFactory->create(
400 63
                $bag->getDocument()->getReceptor()
401 63
            );
402 63
            $bag->setReceptor($emisor);
403
        }
404
405
        // Crear a partir de los datos del XmlDocument.
406
        elseif ($bag->getXmlDocument()) {
407
            $emisor = $this->receptorFactory->create(
408
                $bag->getXmlDocument()->query(
0 ignored issues
show
Bug introduced by
It seems like $bag->getXmlDocument()->...'/Encabezado/Receptor') can also be of type null and string; however, parameter $data of libredte\lib\Core\Packag...toryInterface::create() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

408
                /** @scrutinizer ignore-type */ $bag->getXmlDocument()->query(
Loading history...
409
                    '/Encabezado/Receptor'
410
                )
411
            );
412
            $bag->setReceptor($emisor);
413
        }
414
415
        // Crear a partir de los datos normalizados.
416
        elseif ($bag->getNormalizedData()) {
417
            $emisor = $this->receptorFactory->create(
418
                $bag->getNormalizedData()['Encabezado']['Receptor']
419
            );
420
            $bag->setReceptor($emisor);
421
        }
422
423
        // Crear a partir de los datos parseados.
424
        elseif ($bag->getParsedData()) {
425
            $emisor = $this->receptorFactory->create(
426
                $bag->getParsedData()['Encabezado']['Receptor']
427
            );
428
            $bag->setReceptor($emisor);
429
        }
430
    }
431
}
432