Test Failed
Push — master ( adef33...24e1db )
by Roberto
04:59 queued 02:38
created

SoapBase   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 613
Duplicated Lines 0 %

Coupling/Cohesion

Components 8
Dependencies 4

Test Coverage

Coverage 13.71%

Importance

Changes 0
Metric Value
wmc 57
lcom 8
cbo 4
dl 0
loc 613
ccs 24
cts 175
cp 0.1371
rs 5.027
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
A getStringAttributes() 0 8 2
B saveTemporarilyKeyFiles() 0 51 6
A randomName() 0 8 2
A __construct() 0 7 1
A isCertificateExpired() 0 8 4
A __destruct() 0 4 1
A disableSecurity() 0 4 1
A disableCertValidation() 0 4 1
A httpVersion() 0 16 4
A loadCA() 0 6 2
A setEncriptPrivateKey() 0 5 1
A setTemporaryFolder() 0 22 4
A uid() 0 8 2
A setLocalFolder() 0 5 1
A setDebugMode() 0 4 1
A loadCertificate() 0 7 2
A loadLogger() 0 4 1
A timeout() 0 4 1
A protocol() 0 4 1
A setSoapPrefix() 0 4 1
A proxy() 0 7 1
send() 0 10 ?
A makeEnvelopeSoap() 0 15 1
A mountEnvelopString() 0 16 1
A mountSoapHeaders() 0 19 4
B removeTemporarilyFiles() 0 32 8
A saveDebugFiles() 0 23 3

How to fix   Complexity   

Complex Class

Complex classes like SoapBase 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. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

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 SoapBase, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
namespace NFePHP\Common\Soap;
4
5
use NFePHP\Common\Certificate;
6
use NFePHP\Common\Exception\RuntimeException;
7
use NFePHP\Common\Strings;
8
use League\Flysystem\Filesystem;
9
use League\Flysystem\Adapter\Local;
10
use Psr\Log\LoggerInterface;
11
12
/**
13
 * Soap base class
14
 *
15
 * @category  NFePHP
16
 * @package   NFePHP\Common\Soap\SoapBase
17
 * @copyright NFePHP Copyright (c) 2017-2019
18
 * @author    Roberto L. Machado <linux.rlm at gmail dot com>
19
 * @license   http://www.gnu.org/licenses/lgpl.txt LGPLv3+
20
 * @license   https://opensource.org/licenses/MIT MIT
21
 * @license   http://www.gnu.org/licenses/gpl.txt GPLv3+
22
 * @link      http://github.com/nfephp-org/sped-nfse for the canonical source repository
23
 */
24
abstract class SoapBase implements SoapInterface
25
{
26
    /**
27
     * @var int
28
     */
29
    protected $soapprotocol = self::SSL_DEFAULT;
30
    /**
31
     * @var int
32
     */
33
    protected $soaptimeout = 20;
34
    /**
35
     * @var string
36
     */
37
    protected $proxyIP;
38
    /**
39
     * @var int
40
     */
41
    protected $proxyPort;
42
    /**
43
     * @var string
44
     */
45
    protected $proxyUser;
46
    /**
47
     * @var string
48
     */
49
    protected $proxyPass;
50
    /**
51
     * @var array
52
     */
53
    protected $prefixes = [1 => 'soapenv', 2 => 'soap'];
54
    /**
55
     * @var Certificate
56
     */
57
    protected $certificate;
58
    /**
59
     * @var LoggerInterface|null
60
     */
61
    protected $logger;
62
    /**
63
     * @var string
64
     */
65
    protected $tempdir;
66
    /**
67
     * @var string
68
     */
69
    protected $certsdir;
70
    /**
71
     * @var string
72
     */
73
    protected $debugdir;
74
    /**
75
     * @var string
76
     */
77
    protected $prifile;
78
    /**
79
     * @var string
80
     */
81
    protected $pubfile;
82
    /**
83
     * @var string
84
     */
85
    protected $certfile;
86
    /**
87
     * @var string
88
     */
89
    protected $casefaz;
90
    /**
91
     * @var bool
92
     */
93
    protected $disablesec = false;
94
    /**
95
     * @var bool
96
     */
97
    protected $disableCertValidation = false;
98
    /**
99
     * @var \League\Flysystem\Adapter\Local
100
     */
101
    protected $adapter;
102
    /**
103
     * @var \League\Flysystem\Filesystem
104
     */
105
    protected $filesystem;
106
    /**
107
     * @var string
108
     */
109
    protected $temppass = '';
110
    /**
111
     * @var bool
112
     */
113
    protected $encriptPrivateKey = false;
114
    /**
115
     * @var integer
116
     */
117
    protected $httpver;
118
    /**
119
     * @var bool
120
     */
121
    protected $debugmode = false;
122
    /**
123
     * @var string
124
     */
125
    public $responseHead;
126
    /**
127
     * @var string
128
     */
129
    public $responseBody;
130
    /**
131
     * @var string
132
     */
133
    public $requestHead;
134
    /**
135
     * @var string
136
     */
137
    public $requestBody;
138
    /**
139
     * @var string
140
     */
141
    public $soaperror;
142
    /**
143
     * @var int
144
     */
145
    public $soaperror_code;
146
    /**
147
     * @var array
148
     */
149
    public $soapinfo = [];
150
    /**
151
     * @var int
152
     */
153
    public $waitingTime = 45;
154
    
155
    /**
156
     * SoapBase constructor.
157
     * @param Certificate|null $certificate
158
     * @param LoggerInterface|null $logger
159
     */
160 5
    public function __construct(
161
        Certificate $certificate = null,
162
        LoggerInterface $logger = null
163
    ) {
164 5
        $this->logger = $logger;
165 5
        $this->loadCertificate($certificate);
166 4
    }
167
168
    /**
169
     * Check if certificate is valid to currently used date
170
     * @param Certificate $certificate
171
     * @return void
172
     * @throws Certificate\Exception\Expired
173
     */
174 5
    private function isCertificateExpired(Certificate $certificate = null)
175
    {
176 5
        if (!$this->disableCertValidation) {
177 5
            if (null !== $certificate && $certificate->isExpired()) {
178 1
                throw new Certificate\Exception\Expired($certificate);
179
            }
180
        }
181 4
    }
182
183
    /**
184
     * Destructor
185
     * Clean temporary files
186
     */
187 4
    public function __destruct()
188
    {
189 4
        $this->removeTemporarilyFiles();
190 4
    }
191
192
    /**
193
     * Disables the security checking of host and peer certificates
194
     * @param bool $flag
195
     * @return bool
196
     */
197 1
    public function disableSecurity($flag = false)
198
    {
199 1
        return $this->disablesec = $flag;
200
    }
201
202
    /**
203
     * ONlY for tests
204
     * @param bool $flag
205
     * @return bool
206
     */
207 1
    public function disableCertValidation($flag = true)
208
    {
209 1
        return $this->disableCertValidation = $flag;
210
    }
211
    
212
    /**
213
     * Force http protocol version
214
     *
215
     * @param null|string $version
216
     */
217
    public function httpVersion($version = null)
218
    {
219
        switch ($version) {
220
            case '1.0':
221
                $this->httpver = CURL_HTTP_VERSION_1_0;
222
                break;
223
            case '1.1':
224
                $this->httpver = CURL_HTTP_VERSION_1_1;
225
                break;
226
            case '2.0':
227
                $this->httpver = CURL_HTTP_VERSION_2_0;
228
                break;
229
            default:
230
                $this->httpver = CURL_HTTP_VERSION_NONE;
231
        }
232
    }
233
    
234
    /**
235
     * Load path to CA and enable to use on SOAP
236
     * @param string $capath
237
     * @return void
238
     */
239
    public function loadCA($capath)
240
    {
241
        if (is_file($capath)) {
242
            $this->casefaz = $capath;
243
        }
244
    }
245
246
    /**
247
     * Set option to encrypt private key before save in filesystem
248
     * for an additional layer of protection
249
     * @param bool $encript
250
     * @return bool
251
     */
252
    public function setEncriptPrivateKey($encript = true)
253
    {
254
        $this->encriptPrivateKey = $encript;
255
        return $this->encriptPrivateKey;
256
    }
257
   
258
    /**
259
     * Set another temporayfolder for saving certificates for SOAP utilization
260
     * @param string | null $folderRealPath
261
     * @return void
262
     */
263
    public function setTemporaryFolder($folderRealPath = null)
264
    {
265
        $mapto = $this->certificate->getCnpj() ?? $this->certificate->getCpf();
266
        if (empty($mapto)) {
267
            throw new RuntimeException(
268
                'Foi impossivel identificar o OID do CNPJ ou do CPF.'
269
            );
270
        }
271
        if (empty($folderRealPath)) {
272
            $path = '/sped-'
273
                . $this->uid()
274
                .'/'
275
                . $mapto
276
                . '/' ;
277
            $folderRealPath = sys_get_temp_dir().$path;
278
        }
279
        if (substr($folderRealPath, -1) !== '/') {
280
            $folderRealPath .= '/';
281
        }
282
        $this->tempdir = $folderRealPath;
283
        $this->setLocalFolder($folderRealPath);
284
    }
285
    
286
    /**
287
     * Return uid from user
288
     * @return string
289
     */
290
    protected function uid()
291
    {
292
        if (function_exists('posix_getuid')) {
293
            return posix_getuid();
294
        } else {
295
            return getmyuid();
296
        }
297
    }
298
 
299
    /**
300
     * Set Local folder for flysystem
301
     * @param string $folder
302
     */
303
    protected function setLocalFolder($folder = '')
304
    {
305
        $this->adapter = new Local($folder);
306
        $this->filesystem = new Filesystem($this->adapter);
307
    }
308
309
    /**
310
     * Set debug mode, this mode will save soap envelopes in temporary directory
311
     * @param bool $value
312
     * @return bool
313
     */
314
    public function setDebugMode($value = false)
315
    {
316
        return $this->debugmode = $value;
317
    }
318
319
    /**
320
     * Set certificate class for SSL communications
321
     * @param Certificate $certificate
322
     * @return void
323
     */
324 5
    public function loadCertificate(Certificate $certificate = null)
325
    {
326 5
        $this->isCertificateExpired($certificate);
327 4
        if (null !== $certificate) {
328 1
            $this->certificate = $certificate;
329
        }
330 4
    }
331
332
    /**
333
     * Set logger class
334
     * @param LoggerInterface $logger
335
     * @return LoggerInterface
336
     */
337
    public function loadLogger(LoggerInterface $logger)
338
    {
339
        return $this->logger = $logger;
340
    }
341
342
    /**
343
     * Set timeout for communication
344
     * @param int $timesecs
345
     * @return int
346
     */
347
    public function timeout($timesecs)
348
    {
349
        return $this->soaptimeout = $timesecs;
350
    }
351
352
    /**
353
     * Set security protocol
354
     * @param int $protocol
355
     * @return int
356
     */
357
    public function protocol($protocol = self::SSL_DEFAULT)
358
    {
359
        return $this->soapprotocol = $protocol;
360
    }
361
362
    /**
363
     * Set prefixes
364
     * @param array $prefixes
365
     * @return string[]
366
     */
367
    public function setSoapPrefix($prefixes = [])
368
    {
369
        return $this->prefixes = $prefixes;
370
    }
371
372
    /**
373
     * Set proxy parameters
374
     * @param string $ip
375
     * @param int    $port
376
     * @param string $user
377
     * @param string $password
378
     * @return void
379
     */
380
    public function proxy($ip, $port, $user, $password)
381
    {
382
        $this->proxyIP = $ip;
383
        $this->proxyPort = $port;
384
        $this->proxyUser = $user;
385
        $this->proxyPass = $password;
386
    }
387
388
    /**
389
     * @param string $url
390
     * @param string $operation
391
     * @param string $action
392
     * @param int $soapver
393
     * @param array $parameters
394
     * @param array $namespaces
395
     * @param string $request
396
     * @param null $soapheader
397
     * @return mixed
398
     */
399
    abstract public function send(
400
        $url,
401
        $operation = '',
402
        $action = '',
403
        $soapver = SOAP_1_2,
404
        $parameters = [],
405
        $namespaces = [],
406
        $request = '',
407
        $soapheader = null
408
    );
409
410
    /**
411
     * Mount soap envelope
412
     * @param string $request
413
     * @param array $namespaces
414
     * @param int $soapVer
415
     * @param \SoapHeader $header
416
     * @return string
417
     */
418
    protected function makeEnvelopeSoap(
419
        $request,
420
        $namespaces,
421
        $soapVer = SOAP_1_2,
422
        $header = null
423
    ) {
424
        $prefix = $this->prefixes[$soapVer];
425
        $envelopeAttributes = $this->getStringAttributes($namespaces);
426
        return $this->mountEnvelopString(
427
            $prefix,
428
            $envelopeAttributes,
429
            $this->mountSoapHeaders($prefix, $header),
430
            $request
431
        );
432
    }
433
434
    /**
435
     * Create a envelop string
436
     * @param string $envelopPrefix
437
     * @param string $envelopAttributes
438
     * @param string $header
439
     * @param string $bodyContent
440
     * @return string
441
     */
442
    private function mountEnvelopString(
443
        $envelopPrefix,
444
        $envelopAttributes = '',
445
        $header = '',
446
        $bodyContent = ''
447
    ) {
448
        return sprintf(
449
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
450
            $envelopPrefix,
451
            $envelopAttributes,
452
            $envelopPrefix,
453
            $bodyContent,
454
            $envelopPrefix,
455
            $envelopPrefix
456
        );
457
    }
458
459
    /**
460
     * Create a haeader tag
461
     * @param string $envelopPrefix
462
     * @param \SoapHeader $header
463
     * @return string
464
     */
465
    private function mountSoapHeaders($envelopPrefix, $header = null)
466
    {
467
        if (null === $header) {
468
            return '';
469
        }
470
        $headerItems = '';
471
        foreach ($header->data as $key => $value) {
0 ignored issues
show
Bug introduced by
The property data does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
472
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
473
        }
474
        return sprintf(
475
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
476
            $envelopPrefix,
477
            $header->name,
0 ignored issues
show
Bug introduced by
The property name does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
478
            $header->namespace === null ? '' : $header->namespace,
0 ignored issues
show
Bug introduced by
The property namespace does not seem to exist in SoapHeader.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
479
            $headerItems,
480
            $header->name,
481
            $envelopPrefix
482
        );
483
    }
484
485
    /**
486
     * Get attributes
487
     * @param array $namespaces
488
     * @return string
489
     */
490
    private function getStringAttributes($namespaces = [])
491
    {
492
        $envelopeAttributes = '';
493
        foreach ($namespaces as $key => $value) {
494
            $envelopeAttributes .= $key . '="' . $value . '" ';
495
        }
496
        return $envelopeAttributes;
497
    }
498
499
    /**
500
     * Temporarily saves the certificate keys for use cURL or SoapClient
501
     * @return void
502
     */
503
    public function saveTemporarilyKeyFiles()
504
    {
505
        //certs already exists
506
        if (!empty($this->certsdir)) {
507
            return;
508
        }
509
        if (!is_object($this->certificate)) {
510
            throw new RuntimeException(
511
                'Certificate not found.'
512
            );
513
        }
514
        if (empty($this->filesystem)) {
515
            $this->setTemporaryFolder();
516
        }
517
        //clear dir cert
518
        $this->removeTemporarilyFiles();
519
        $this->certsdir = 'certs/';
520
        $this->prifile = $this->randomName();
521
        $this->pubfile = $this->randomName();
522
        $this->certfile = $this->randomName();
523
        $ret = true;
524
        //load private key pem
525
        $private = $this->certificate->privateKey;
526
        if ($this->encriptPrivateKey) {
527
            //replace private key pem with password
528
            $this->temppass = Strings::randomString(16);
529
            //encripta a chave privada entes da gravação do filesystem
530
            openssl_pkey_export(
531
                $this->certificate->privateKey,
532
                $private,
533
                $this->temppass
534
            );
535
        }
536
        $ret &= $this->filesystem->put(
537
            $this->prifile,
538
            $private
539
        );
540
        $ret &= $this->filesystem->put(
541
            $this->pubfile,
542
            $this->certificate->publicKey
543
        );
544
        $ret &= $this->filesystem->put(
545
            $this->certfile,
546
            $private . "{$this->certificate}"
547
        );
548
        if (!$ret) {
549
            throw new RuntimeException(
550
                'Unable to save temporary key files in folder.'
551
            );
552
        }
553
    }
554
    
555
    /**
556
     * Create a unique random file name
557
     * @param integer $n
558
     * @return string
559
     */
560
    protected function randomName($n = 10)
561
    {
562
        $name = $this->certsdir . Strings::randomString($n) . '.pem';
563
        if (!$this->filesystem->has($name)) {
564
            return $name;
565
        }
566
        $this->randomName($n+5);
567
    }
568
569
    /**
570
     * Delete all files in folder
571
     * @return void
572
     */
573 4
    public function removeTemporarilyFiles()
574
    {
575
        try {
576 4
            if (empty($this->filesystem) || empty($this->certsdir)) {
577 4
                return;
578
            }
579
            //remove os certificados
580
            $this->filesystem->delete($this->certfile);
581
            $this->filesystem->delete($this->prifile);
582
            $this->filesystem->delete($this->pubfile);
583
            //remove todos os arquivos antigos
584
            $contents = $this->filesystem->listContents($this->certsdir, true);
585
            $dt = new \DateTime();
586
            $tint = new \DateInterval("PT".$this->waitingTime."M");
587
            $tint->invert = 1;
588
            $tsLimit = $dt->add($tint)->getTimestamp();
589
            foreach ($contents as $item) {
590
                if ($item['type'] == 'file') {
591
                    if ($this->filesystem->fileExists($item['path'])) {
592
                        $timestamp = $this->filesystem->getTimestamp($item['path']);
593
                        if ($timestamp < $tsLimit) {
594
                            $this->filesystem->delete($item['path']);
595
                        }
596
                    }
597
                }
598
            }
599
        } catch (\Throwable $e) {
600
            //impedir de ocorrer exception em ambientes muito comporrentes
601
            //porem nesses casos devem ser feitas limpezas periodicas caso
602
            //não seja usado o diretorio /tmp pois não será auto limpante
603
        }
604
    }
605
606
    /**
607
     * Save request envelope and response for debug reasons
608
     * @param string $operation
609
     * @param string $request
610
     * @param string $response
611
     * @return void
612
     */
613
    public function saveDebugFiles($operation, $request, $response)
614
    {
615
        if (!$this->debugmode) {
616
            return;
617
        }
618
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
619
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
620
        $time = substr($now->format("ymdHisu"), 0, 16);
621
        try {
622
            $this->filesystem->put(
623
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
624
                $request
625
            );
626
            $this->filesystem->put(
627
                $this->debugdir . $time . "_" . $operation . "_res.txt",
628
                $response
629
            );
630
        } catch (\Exception $e) {
631
            throw new RuntimeException(
632
                'Unable to create debug files.'
633
            );
634
        }
635
    }
636
}
637