Completed
Push — master ( fdd60b...baa5b4 )
by Roberto
08:05 queued 05:14
created

Factory::propertiesToLower()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 14
Code Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 3.0123

Importance

Changes 0
Metric Value
dl 0
loc 14
ccs 8
cts 9
cp 0.8889
rs 9.4285
c 0
b 0
f 0
cc 3
eloc 9
nc 3
nop 1
crap 3.0123
1
<?php
2
3
namespace NFePHP\eSocial\Common;
4
5
use DateTime;
6
use DOMDocument;
7
use DOMElement;
8
use JsonSchema\Validator as JsonValid;
9
use NFePHP\Common\Certificate;
10
use NFePHP\Common\DOMImproved as Dom;
11
use NFePHP\Common\Signer;
12
use NFePHP\Common\Strings;
13
use NFePHP\Common\Validator;
14
use stdClass;
15
16
abstract class Factory
17
{
18
    /**
19
     * @var int
20
     */
21
    public $tpInsc;
22
23
    /**
24
     * @var string
25
     */
26
    public $nrInsc;
27
28
    /**
29
     * @var string
30
     */
31
    public $nmRazao;
32
33
    /**
34
     * @var DateTime
35
     */
36
    public $date;
37
38
    /**
39
     * @var int
40
     */
41
    public $tpAmb = 3;
42
43
    /**
44
     * @var int
45
     */
46
    public $procEmi = 1;
47
48
    /**
49
     * @var string
50
     */
51
    public $verProc = '';
52
53
    /**
54
     * @var string
55
     */
56
    public $layout = '2.2.2';
57
58
    /**
59
     * @var string
60
     */
61
    public $layoutStr = '';
62
63
    /**
64
     * @var string
65
     */
66
    public $schema = '';
67
68
    /**
69
     * @var string
70
     */
71
    public $jsonschema = '';
72
73
    /**
74
     * @var string
75
     */
76
    public $evtid = '';
77
78
    /**
79
     * @var string
80
     */
81
    protected $xmlns = "http://www.esocial.gov.br/schema/evt/";
82
83
    /**
84
     * @var string
85
     */
86
    protected $xsi = "http://www.w3.org/2001/XMLSchema-instance";
87
88
    /**
89
     * @var Dom
90
     */
91
    protected $dom;
92
93
    /**
94
     * @var stdClass
95
     */
96
    protected $std;
97
98
    /**
99
     * @var string
100
     */
101
    protected $xml;
102
103
    /**
104
     * @var DOMNode
105
     */
106
    protected $eSocial;
107
108
    /**
109
     * @var DOMElement
110
     */
111
    protected $node;
112
113
    /**
114
     * @var array
115
     */
116
    protected $parameters = [];
117
118
    /**
119
     * @var string
120
     */
121
    protected $evtName = '';
122
123
    /**
124
     * @var string
125
     */
126
    protected $evtAlias = '';
127
128
    /**
129
     * @var Certificate
130
     */
131
    protected $certificate;
132
133
    /**
134
     * Constructor
135
     *
136
     * @param string $config
137
     * @param stdClass $std
138
     * @param Certificate $certificate
139
     * @param string $date
140
     */
141 5
    public function __construct(
142
        $config,
143
        stdClass $std,
144
        Certificate $certificate = null,
145
        $date = ''
146
    ) {
147
        //set properties from config
148 5
        $stdConf    = json_decode($config);
149 5
        $this->date = new DateTime();
150 5
        if (! empty($date)) {
151 5
            $this->date = new DateTime($date);
152
        }
153 5
        $this->tpAmb       = $stdConf->tpAmb;
154 5
        $this->verProc     = $stdConf->verProc;
155 5
        $this->layout      = $stdConf->eventoVersion;
156 5
        $this->tpInsc      = $stdConf->empregador->tpInsc;
157 5
        $this->nrInsc      = $stdConf->empregador->nrInsc;
158 5
        $this->nmRazao     = $stdConf->empregador->nmRazao;
159 5
        $this->layoutStr   = $this->strLayoutVer($this->layout);
160 5
        $this->certificate = $certificate;
161 5
        if (empty($std) || ! is_object($std)) {
162
            throw new \InvalidArgumentException(
163
                'Você deve passar os parâmetros num stdClass.'
164
            );
165
        }
166 5
        $this->jsonschema = realpath(
167
            __DIR__
168 5
            ."/../../jsonSchemes/$this->layoutStr/"
169 5
            .$this->evtName
170 5
            .".schema"
171
        );
172 5
        $this->schema     = realpath(
173
            __DIR__
174 5
            ."/../../schemes/$this->layoutStr/"
175 5
            .$this->evtName
176 5
            .".xsd"
177
        );
178
        //convert all data fields to lower case
179 5
        $this->std = $this->propertiesToLower($std);
180
        //validate input data with schema
181 5
        $this->validInputData($this->std);
182
        //Adding forgotten or unnecessary fields.
183
        //This is done for standardization purposes.
184
        //Fields with no value will not be included by the builder.
185
        //$this->std = $this->standardizeProperties($this->std);
0 ignored issues
show
Unused Code Comprehensibility introduced by
60% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
186 5
        $this->init();
187 5
    }
188
189
    /**
190
     * Stringfy layout number
191
     *
192
     * @param type $layout
193
     *
194
     * @return string
195
     */
196 5
    protected function strLayoutVer($layout)
197
    {
198 5
        $fils = explode('.', $layout);
199 5
        $str  = 'v';
200 5
        foreach ($fils as $fil) {
201 5
            $str .= str_pad($fil, 2, '0', STR_PAD_LEFT).'_';
202
        }
203
204 5
        return substr($str, 0, -1);
205
    }
206
207
    /**
208
     * Change properties names of stdClass to lower case
209
     *
210
     * @param stdClass $data
211
     *
212
     * @return stdClass
213
     */
214 5
    protected static function propertiesToLower(stdClass $data)
215
    {
216 5
        $properties = get_object_vars($data);
217 5
        $clone      = new stdClass();
218 5
        foreach ($properties as $key => $value) {
219 5
            if ($value instanceof stdClass) {
220
                $value = self::propertiesToLower($value);
221
            }
222 5
            $nk           = strtolower($key);
223 5
            $clone->{$nk} = $value;
224
        }
225
226 5
        return $clone;
227
    }
228
229
    /**
230
     * Validation json data from json Schema
231
     *
232
     * @param stdClass $data
233
     *
234
     * @return boolean
235
     * @throws \RuntimeException
236
     */
237 5
    protected function validInputData($data)
238
    {
239 5
        if (! is_file($this->jsonschema)) {
240
            return true;
241
        }
242 5
        $validator = new JsonValid();
243 5
        $validator->check($data, (object) ['$ref' => 'file://'.$this->jsonschema]);
244 5
        if (! $validator->isValid()) {
245
            $msg = "JSON does not validate. Violations:\n";
246
            foreach ($validator->getErrors() as $error) {
247
                $msg .= sprintf("[%s] %s\n", $error['property'], $error['message']);
248
            }
249
            throw new \RuntimeException($msg);
250
        }
251
252 5
        return true;
253
    }
254
255
    /**
256
     * Initialize DOM
257
     */
258 5
    protected function init()
259
    {
260 5
        if (empty($this->dom)) {
261 5
            $this->dom                     = new Dom('1.0', 'UTF-8');
262 5
            $this->dom->preserveWhiteSpace = false;
263 5
            $this->dom->formatOutput       = false;
264
            $xml = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
265 5
                . "<eSocial xmlns=\"$this->xmlns"
266 5
                . $this->evtName
267 5
                . "/$this->layoutStr\">"
268 5
                . "</eSocial>";
269
            //."xmlns:xsi=\"$this->xsi\">"
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
270 5
            $this->dom->loadXML($xml);
271 5
            $this->eSocial = $this->dom->getElementsByTagName('eSocial')->item(0);
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->dom->getElementsB...ame('eSocial')->item(0) of type object<DOMNode> is incompatible with the declared type object<NFePHP\eSocial\Common\DOMNode> of property $eSocial.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
272 5
            $this->evtid   = FactoryId::build(
273 5
                $this->tpInsc,
274 5
                $this->nrInsc,
275 5
                $this->date,
276 5
                $this->std->sequencial
277
            );
278 5
            $this->node    = $this->dom->createElement($this->evtName);
279 5
            $att           = $this->dom->createAttribute('Id');
280 5
            $att->value    = $this->evtid;
281 5
            $this->node->appendChild($att);
282
283 5
            $ideEmpregador = $this->dom->createElement("ideEmpregador");
284 5
            $this->dom->addChild(
285
                $ideEmpregador,
286 5
                "tpInsc",
287 5
                $this->tpInsc,
288 5
                true
289
            );
290 5
            $this->dom->addChild(
291
                $ideEmpregador,
292 5
                "nrInsc",
293 5
                $this->nrInsc,
294 5
                true
295
            );
296 5
            $this->node->appendChild($ideEmpregador);
297
        }
298 5
    }
299
300
    public function alias()
301
    {
302
        return $this->evtAlias;
303
    }
304
305
    public function getCertificate()
306
    {
307
        return $this->certificate;
308
    }
309
310
    public function setCertificate(Certificate $certificate)
311
    {
312
        $this->certificate = $certificate;
313
    }
314
315
    /**
316
     * Return xml of event
317
     *
318
     * @return string
319
     */
320 1
    public function toXML()
321
    {
322 1
        if (empty($this->xml)) {
323 1
            $this->toNode();
324
        }
325
326 1
        return $this->clearXml($this->xml);
327
    }
328
329
    abstract protected function toNode();
330
331
    /**
332
     * Remove XML declaration from XML string
333
     *
334
     * @param string $xml
335
     *
336
     * @return string
337
     */
338 1
    protected function clearXml($xml)
339
    {
340 1
        $dom                      = new DOMDocument('1.0', 'UTF-8');
341 1
        $this->formatOutput       = false;
0 ignored issues
show
Bug introduced by
The property formatOutput does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
342 1
        $this->preserveWhiteSpace = false;
0 ignored issues
show
Bug introduced by
The property preserveWhiteSpace does not exist. Did you maybe forget to declare it?

In PHP it is possible to write to properties without declaring them. For example, the following is perfectly valid PHP code:

class MyClass { }

$x = new MyClass();
$x->foo = true;

Generally, it is a good practice to explictly declare properties to avoid accidental typos and provide IDE auto-completion:

class MyClass {
    public $foo;
}

$x = new MyClass();
$x->foo = true;
Loading history...
343 1
        $dom->loadXML($xml);
344
345 1
        return $dom->saveXML($dom->documentElement);
346
    }
347
348
    /**
349
     * Convert xml of event to array
350
     *
351
     * @return array
352
     */
353 1
    public function toArray()
354
    {
355 1
        return json_decode($this->toJson(), true);
356
    }
357
358
    /**
359
     * Convert xml to json string
360
     *
361
     * @return string
362
     */
363 3
    public function toJson()
364
    {
365 3
        if (empty($this->xml)) {
366 3
            $this->toNode();
367
        }
368
        //a assinatura só faz sentido no XML, os demais formatos
369
        //não devem conter dados da assinatura
370 3
        $xml = Signer::removeSignature($this->xml);
371 3
        $dom = new \DOMDocument();
372 3
        $dom->loadXML($xml);
373 3
        $sxml = simplexml_load_string($dom->saveXML());
374
375 3
        return str_replace(
376 3
            '@attributes',
377 3
            'attributes',
378 3
            json_encode($sxml, JSON_PRETTY_PRINT)
379
        );
380
    }
381
382
    /**
383
     * Convert xml to stdClass
384
     *
385
     * @return stdClass
386
     */
387 1
    public function toStd()
388
    {
389 1
        return json_decode($this->toJson());
390
    }
391
392
    /**
393
     * Adjust missing properties form original data according schema
394
     *
395
     * @param \stdClass $data
396
     *
397
     * @return \stdClass
398
     */
399
    public function standardizeProperties(stdClass $data)
400
    {
401
        if (! is_file($this->jsonschema)) {
402
            return $data;
403
        }
404
        $jsonSchemaObj = json_decode(file_get_contents($this->jsonschema));
405
        $sc = new ParamsStandardize($jsonSchemaObj);
406
407
        return $sc->stdData($data);
408
    }
409
410
    /**
411
     * Sign and validate XML with XSD, can throw Exception
412
     */
413 4
    protected function sign()
414
    {
415 4
        $xml = $this->dom->saveXML($this->eSocial);
416 4
        $xml = Strings::clearXmlString($xml);
417 4
        if (! empty($this->certificate)) {
418 4
            $xml = Signer::sign(
419 4
                $this->certificate,
420
                $xml,
421 4
                'eSocial',
422 4
                '',
423 4
                OPENSSL_ALGO_SHA256,
424 4
                [true, false, null, null]
425
            );
426
            //validation by XSD schema throw Exception if dont pass
427 4
            Validator::isValid($xml, $this->schema);
428
        }
429 4
        $this->xml = $xml;
430 4
    }
431
}
432