Passed
Pull Request — master (#203)
by Roberto
01:52
created

SoapBase   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 595
Duplicated Lines 0 %

Coupling/Cohesion

Components 8
Dependencies 4

Test Coverage

Coverage 14.19%

Importance

Changes 0
Metric Value
wmc 54
lcom 8
cbo 4
dl 0
loc 595
ccs 24
cts 169
cp 0.1419
rs 6.4799
c 0
b 0
f 0

27 Methods

Rating   Name   Duplication   Size   Complexity  
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 16 3
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
A getStringAttributes() 0 8 2
B saveTemporarilyKeyFiles() 0 51 6
A randomName() 0 8 2
B removeTemporarilyFiles() 0 24 6
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 array
144
     */
145
    public $soapinfo = [];
146
    /**
147
     * @var int
148
     */
149
    public $waitingTime = 45;
150
    
151
    /**
152
     * SoapBase constructor.
153
     * @param Certificate|null $certificate
154
     * @param LoggerInterface|null $logger
155
     */
156 5
    public function __construct(
157
        Certificate $certificate = null,
158
        LoggerInterface $logger = null
159
    ) {
160 5
        $this->logger = $logger;
161 5
        $this->loadCertificate($certificate);
162 4
    }
163
164
    /**
165
     * Check if certificate is valid to currently used date
166
     * @param Certificate $certificate
167
     * @return void
168
     * @throws Certificate\Exception\Expired
169
     */
170 5
    private function isCertificateExpired(Certificate $certificate = null)
171
    {
172 5
        if (!$this->disableCertValidation) {
173 5
            if (null !== $certificate && $certificate->isExpired()) {
174 1
                throw new Certificate\Exception\Expired($certificate);
175
            }
176
        }
177 4
    }
178
179
    /**
180
     * Destructor
181
     * Clean temporary files
182
     */
183 4
    public function __destruct()
184
    {
185 4
        $this->removeTemporarilyFiles();
186 4
    }
187
188
    /**
189
     * Disables the security checking of host and peer certificates
190
     * @param bool $flag
191
     * @return bool
192
     */
193 1
    public function disableSecurity($flag = false)
194
    {
195 1
        return $this->disablesec = $flag;
196
    }
197
198
    /**
199
     * ONlY for tests
200
     * @param bool $flag
201
     * @return bool
202
     */
203 1
    public function disableCertValidation($flag = true)
204
    {
205 1
        return $this->disableCertValidation = $flag;
206
    }
207
    
208
    /**
209
     * Force http protocol version
210
     *
211
     * @param null|string $version
212
     */
213
    public function httpVersion($version = null)
214
    {
215
        switch ($version) {
216
            case '1.0':
217
                $this->httpver = CURL_HTTP_VERSION_1_0;
218
                break;
219
            case '1.1':
220
                $this->httpver = CURL_HTTP_VERSION_1_1;
221
                break;
222
            case '2.0':
223
                $this->httpver = CURL_HTTP_VERSION_2_0;
224
                break;
225
            default:
226
                $this->httpver = CURL_HTTP_VERSION_NONE;
227
        }
228
    }
229
    
230
    /**
231
     * Load path to CA and enable to use on SOAP
232
     * @param string $capath
233
     * @return void
234
     */
235
    public function loadCA($capath)
236
    {
237
        if (is_file($capath)) {
238
            $this->casefaz = $capath;
239
        }
240
    }
241
242
    /**
243
     * Set option to encrypt private key before save in filesystem
244
     * for an additional layer of protection
245
     * @param bool $encript
246
     * @return bool
247
     */
248
    public function setEncriptPrivateKey($encript = true)
249
    {
250
        $this->encriptPrivateKey = $encript;
251
        return $this->encriptPrivateKey;
252
    }
253
   
254
    /**
255
     * Set another temporayfolder for saving certificates for SOAP utilization
256
     * @param string | null $folderRealPath
257
     * @return void
258
     */
259
    public function setTemporaryFolder($folderRealPath = null)
260
    {
261
        if (empty($folderRealPath)) {
262
            $path = '/sped-'
263
                . $this->uid()
264
                .'/'
265
                . $this->certificate->getCnpj()
266
                . '/' ;
267
            $folderRealPath = sys_get_temp_dir().$path;
268
        }
269
        if (substr($folderRealPath, -1) !== '/') {
270
            $folderRealPath .= '/';
271
        }
272
        $this->tempdir = $folderRealPath;
273
        $this->setLocalFolder($folderRealPath);
274
    }
275
    
276
    /**
277
     * Return uid from user
278
     * @return string
279
     */
280
    protected function uid()
281
    {
282
        if (function_exists('posix_getuid')) {
283
            return posix_getuid();
284
        } else {
285
            return getmyuid();
286
        }
287
    }
288
 
289
    /**
290
     * Set Local folder for flysystem
291
     * @param string $folder
292
     */
293
    protected function setLocalFolder($folder = '')
294
    {
295
        $this->adapter = new Local($folder);
296
        $this->filesystem = new Filesystem($this->adapter);
297
    }
298
299
    /**
300
     * Set debug mode, this mode will save soap envelopes in temporary directory
301
     * @param bool $value
302
     * @return bool
303
     */
304
    public function setDebugMode($value = false)
305
    {
306
        return $this->debugmode = $value;
307
    }
308
309
    /**
310
     * Set certificate class for SSL communications
311
     * @param Certificate $certificate
312
     * @return void
313
     */
314 5
    public function loadCertificate(Certificate $certificate = null)
315
    {
316 5
        $this->isCertificateExpired($certificate);
317 4
        if (null !== $certificate) {
318 1
            $this->certificate = $certificate;
319
        }
320 4
    }
321
322
    /**
323
     * Set logger class
324
     * @param LoggerInterface $logger
325
     * @return LoggerInterface
326
     */
327
    public function loadLogger(LoggerInterface $logger)
328
    {
329
        return $this->logger = $logger;
330
    }
331
332
    /**
333
     * Set timeout for communication
334
     * @param int $timesecs
335
     * @return int
336
     */
337
    public function timeout($timesecs)
338
    {
339
        return $this->soaptimeout = $timesecs;
340
    }
341
342
    /**
343
     * Set security protocol
344
     * @param int $protocol
345
     * @return int
346
     */
347
    public function protocol($protocol = self::SSL_DEFAULT)
348
    {
349
        return $this->soapprotocol = $protocol;
350
    }
351
352
    /**
353
     * Set prefixes
354
     * @param array $prefixes
355
     * @return string[]
356
     */
357
    public function setSoapPrefix($prefixes = [])
358
    {
359
        return $this->prefixes = $prefixes;
360
    }
361
362
    /**
363
     * Set proxy parameters
364
     * @param string $ip
365
     * @param int    $port
366
     * @param string $user
367
     * @param string $password
368
     * @return void
369
     */
370
    public function proxy($ip, $port, $user, $password)
371
    {
372
        $this->proxyIP = $ip;
373
        $this->proxyPort = $port;
374
        $this->proxyUser = $user;
375
        $this->proxyPass = $password;
376
    }
377
378
    /**
379
     * @param string $url
380
     * @param string $operation
381
     * @param string $action
382
     * @param int $soapver
383
     * @param array $parameters
384
     * @param array $namespaces
385
     * @param string $request
386
     * @param null $soapheader
387
     * @return mixed
388
     */
389
    abstract public function send(
390
        $url,
391
        $operation = '',
392
        $action = '',
393
        $soapver = SOAP_1_2,
394
        $parameters = [],
395
        $namespaces = [],
396
        $request = '',
397
        $soapheader = null
398
    );
399
400
    /**
401
     * Mount soap envelope
402
     * @param string $request
403
     * @param array $namespaces
404
     * @param int $soapVer
405
     * @param \SoapHeader $header
406
     * @return string
407
     */
408
    protected function makeEnvelopeSoap(
409
        $request,
410
        $namespaces,
411
        $soapVer = SOAP_1_2,
412
        $header = null
413
    ) {
414
        $prefix = $this->prefixes[$soapVer];
415
        $envelopeAttributes = $this->getStringAttributes($namespaces);
416
        return $this->mountEnvelopString(
417
            $prefix,
418
            $envelopeAttributes,
419
            $this->mountSoapHeaders($prefix, $header),
420
            $request
421
        );
422
    }
423
424
    /**
425
     * Create a envelop string
426
     * @param string $envelopPrefix
427
     * @param string $envelopAttributes
428
     * @param string $header
429
     * @param string $bodyContent
430
     * @return string
431
     */
432
    private function mountEnvelopString(
433
        $envelopPrefix,
434
        $envelopAttributes = '',
435
        $header = '',
436
        $bodyContent = ''
437
    ) {
438
        return sprintf(
439
            '<%s:Envelope %s>' . $header . '<%s:Body>%s</%s:Body></%s:Envelope>',
440
            $envelopPrefix,
441
            $envelopAttributes,
442
            $envelopPrefix,
443
            $bodyContent,
444
            $envelopPrefix,
445
            $envelopPrefix
446
        );
447
    }
448
449
    /**
450
     * Create a haeader tag
451
     * @param string $envelopPrefix
452
     * @param \SoapHeader $header
453
     * @return string
454
     */
455
    private function mountSoapHeaders($envelopPrefix, $header = null)
456
    {
457
        if (null === $header) {
458
            return '';
459
        }
460
        $headerItems = '';
461
        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...
462
            $headerItems .= '<' . $key . '>' . $value . '</' . $key . '>';
463
        }
464
        return sprintf(
465
            '<%s:Header><%s xmlns="%s">%s</%s></%s:Header>',
466
            $envelopPrefix,
467
            $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...
468
            $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...
469
            $headerItems,
470
            $header->name,
471
            $envelopPrefix
472
        );
473
    }
474
475
    /**
476
     * Get attributes
477
     * @param array $namespaces
478
     * @return string
479
     */
480
    private function getStringAttributes($namespaces = [])
481
    {
482
        $envelopeAttributes = '';
483
        foreach ($namespaces as $key => $value) {
484
            $envelopeAttributes .= $key . '="' . $value . '" ';
485
        }
486
        return $envelopeAttributes;
487
    }
488
489
    /**
490
     * Temporarily saves the certificate keys for use cURL or SoapClient
491
     * @return void
492
     */
493
    public function saveTemporarilyKeyFiles()
494
    {
495
        //certs already exists
496
        if (!empty($this->certsdir)) {
497
            return;
498
        }
499
        if (!is_object($this->certificate)) {
500
            throw new RuntimeException(
501
                'Certificate not found.'
502
            );
503
        }
504
        if (empty($this->filesystem)) {
505
            $this->setTemporaryFolder();
506
        }
507
        //clear dir cert
508
        $this->removeTemporarilyFiles();
509
        $this->certsdir = 'certs/';
510
        $this->prifile = $this->randomName();
511
        $this->pubfile = $this->randomName();
512
        $this->certfile = $this->randomName();
513
        $ret = true;
514
        //load private key pem
515
        $private = $this->certificate->privateKey;
516
        if ($this->encriptPrivateKey) {
517
            //replace private key pem with password
518
            $this->temppass = Strings::randomString(16);
519
            //encripta a chave privada entes da gravação do filesystem
520
            openssl_pkey_export(
521
                $this->certificate->privateKey,
522
                $private,
523
                $this->temppass
524
            );
525
        }
526
        $ret &= $this->filesystem->put(
527
            $this->prifile,
528
            $private
529
        );
530
        $ret &= $this->filesystem->put(
531
            $this->pubfile,
532
            $this->certificate->publicKey
533
        );
534
        $ret &= $this->filesystem->put(
535
            $this->certfile,
536
            $private . "{$this->certificate}"
537
        );
538
        if (!$ret) {
539
            throw new RuntimeException(
540
                'Unable to save temporary key files in folder.'
541
            );
542
        }
543
    }
544
    
545
    /**
546
     * Create a unique random file name
547
     * @param integer $n
548
     * @return string
549
     */
550
    protected function randomName($n = 10)
551
    {
552
        $name = $this->certsdir . Strings::randomString($n) . '.pem';
553
        if (!$this->filesystem->has($name)) {
554
            return $name;
555
        }
556
        $this->randomName($n+5);
557
    }
558
559
    /**
560
     * Delete all files in folder
561
     * @return void
562
     */
563 4
    public function removeTemporarilyFiles()
564
    {
565 4
        if (empty($this->filesystem) || empty($this->certsdir)) {
566 4
            return;
567
        }
568
        //remove os certificados
569
        $this->filesystem->delete($this->certfile);
570
        $this->filesystem->delete($this->prifile);
571
        $this->filesystem->delete($this->pubfile);
572
        //remove todos os arquivos antigos
573
        $contents = $this->filesystem->listContents($this->certsdir, true);
574
        $dt = new \DateTime();
575
        $tint = new \DateInterval("PT".$this->waitingTime."M");
576
        $tint->invert = 1;
577
        $tsLimit = $dt->add($tint)->getTimestamp();
578
        foreach ($contents as $item) {
579
            if ($item['type'] == 'file') {
580
                $timestamp = $this->filesystem->getTimestamp($item['path']);
581
                if ($timestamp < $tsLimit) {
582
                    $this->filesystem->delete($item['path']);
583
                }
584
            }
585
        }
586
    }
587
588
    /**
589
     * Save request envelope and response for debug reasons
590
     * @param string $operation
591
     * @param string $request
592
     * @param string $response
593
     * @return void
594
     */
595
    public function saveDebugFiles($operation, $request, $response)
596
    {
597
        if (!$this->debugmode) {
598
            return;
599
        }
600
        $this->debugdir = $this->certificate->getCnpj() . '/debug/';
601
        $now = \DateTime::createFromFormat('U.u', number_format(microtime(true), 6, '.', ''));
602
        $time = substr($now->format("ymdHisu"), 0, 16);
603
        try {
604
            $this->filesystem->put(
605
                $this->debugdir . $time . "_" . $operation . "_sol.txt",
606
                $request
607
            );
608
            $this->filesystem->put(
609
                $this->debugdir . $time . "_" . $operation . "_res.txt",
610
                $response
611
            );
612
        } catch (\Exception $e) {
613
            throw new RuntimeException(
614
                'Unable to create debug files.'
615
            );
616
        }
617
    }
618
}
619