Skip to content

Commit fccbe5e

Browse files
committed
Put shared logic from Cfdi & Retenciones to XmlReaderTrait
1 parent cd305c5 commit fccbe5e

File tree

4 files changed

+138
-244
lines changed

4 files changed

+138
-244
lines changed

docs/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
## Version 2.14.0 2020-10-01
3232

3333
- Add `Retenciones` reader to work with *CFDI de retenciones e información de pagos*.
34+
- Refactor `Cfdi` and `Retenciones` to use recently created `XmlReaderTrait`.
3435

3536

3637
## Version 2.13.1 2020-10-01

src/CfdiUtils/Cfdi.php

Lines changed: 4 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,8 @@
22

33
namespace CfdiUtils;
44

5-
use CfdiUtils\Nodes\NodeInterface;
6-
use CfdiUtils\Nodes\XmlNodeUtils;
7-
use CfdiUtils\QuickReader\QuickReader;
8-
use CfdiUtils\QuickReader\QuickReaderImporter;
9-
use CfdiUtils\Utils\Xml;
5+
use CfdiUtils\Internals\XmlReaderTrait;
106
use DOMDocument;
11-
use DOMElement;
127

138
/**
149
* This class contains minimum helpers to read CFDI based on DOMDocument
@@ -27,128 +22,14 @@
2722
*/
2823
class Cfdi
2924
{
30-
/** @var DOMDocument */
31-
private $document;
32-
33-
/** @var string */
34-
private $version;
35-
36-
/** @var string|null */
37-
private $source;
38-
39-
/** @var NodeInterface|null */
40-
private $node;
41-
42-
/** @var QuickReader|null */
43-
private $quickReader;
25+
use XmlReaderTrait;
4426

4527
const CFDI_NAMESPACE = 'http://www.sat.gob.mx/cfd/3';
4628

4729
public function __construct(DOMDocument $document)
4830
{
49-
$rootElement = $this->extractValidRootElement($document, static::CFDI_NAMESPACE, 'cfdi', 'Comprobante');
50-
51-
$this->version = (new CfdiVersion())->getFromDOMElement($rootElement);
31+
$rootElement = self::checkRootElement($document, static::CFDI_NAMESPACE, 'cfdi', 'Comprobante');
5232
$this->document = clone $document;
53-
}
54-
55-
56-
private function extractValidRootElement(
57-
DOMDocument $document,
58-
string $expectedNamespace,
59-
string $expectedNsPrefix,
60-
string $expectedRootBaseNodeName
61-
): DOMElement {
62-
$rootElement = Xml::documentElement($document);
63-
64-
// is not docummented: lookupPrefix returns NULL instead of string when not found
65-
// this is why we are casting the value to string
66-
$nsPrefix = (string) $document->lookupPrefix($expectedNamespace);
67-
if ('' === $nsPrefix) {
68-
throw new \UnexpectedValueException(
69-
sprintf('Document does not implement namespace %s', $expectedNamespace)
70-
);
71-
}
72-
if ($expectedNsPrefix !== $nsPrefix) {
73-
throw new \UnexpectedValueException(
74-
sprintf('Prefix for namespace %s is not "%s"', $expectedNamespace, $expectedNsPrefix)
75-
);
76-
}
77-
78-
$expectedRootNodeName = $expectedNsPrefix . ':' . $expectedRootBaseNodeName;
79-
if ($rootElement->tagName !== $expectedRootNodeName) {
80-
throw new \UnexpectedValueException(sprintf('Root element is not %s', $expectedRootNodeName));
81-
}
82-
83-
return $rootElement;
84-
}
85-
86-
/**
87-
* Create a CFDI object from a xml string
88-
*
89-
* @param string $content
90-
* @return static
91-
*/
92-
public static function newFromString(string $content): self
93-
{
94-
$document = Xml::newDocumentContent($content);
95-
// populate source since it is already available
96-
// in this way we avoid the conversion from document to string
97-
$cfdi = new self($document);
98-
$cfdi->source = $content;
99-
return $cfdi;
100-
}
101-
102-
/**
103-
* Obtain the version from the CFDI, it is compatible with 3.2 and 3.3
104-
*/
105-
public function getVersion(): string
106-
{
107-
return $this->version;
108-
}
109-
110-
/**
111-
* Get a clone of the local DOM document
112-
*/
113-
public function getDocument(): DOMDocument
114-
{
115-
return clone $this->document;
116-
}
117-
118-
/**
119-
* Get the xml string source
120-
*/
121-
public function getSource(): string
122-
{
123-
if (null === $this->source) {
124-
// pass the document element to avoid xml header
125-
$this->source = $this->document->saveXML(Xml::documentElement($this->document));
126-
}
127-
128-
return $this->source;
129-
}
130-
131-
/**
132-
* Get the node object to iterate through the CFDI
133-
*/
134-
public function getNode(): NodeInterface
135-
{
136-
if (null === $this->node) {
137-
$this->node = XmlNodeUtils::nodeFromXmlElement(Xml::documentElement($this->document));
138-
}
139-
140-
return $this->node;
141-
}
142-
143-
/**
144-
* Get the quick reader object to iterate through the CFDI
145-
*/
146-
public function getQuickReader(): QuickReader
147-
{
148-
if (null === $this->quickReader) {
149-
$this->quickReader = (new QuickReaderImporter())->importDocument($this->document);
150-
}
151-
152-
return $this->quickReader;
33+
$this->version = (new CfdiVersion())->getFromDOMElement($rootElement);
15334
}
15435
}
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
<?php
2+
3+
namespace CfdiUtils\Internals;
4+
5+
use CfdiUtils\Nodes\NodeInterface;
6+
use CfdiUtils\Nodes\XmlNodeUtils;
7+
use CfdiUtils\QuickReader\QuickReader;
8+
use CfdiUtils\QuickReader\QuickReaderImporter;
9+
use CfdiUtils\Utils\Xml;
10+
use DOMDocument;
11+
use DOMElement;
12+
13+
/** @internal */
14+
trait XmlReaderTrait
15+
{
16+
/** @var DOMDocument */
17+
private $document;
18+
19+
/** @var string */
20+
private $version;
21+
22+
/** @var string|null */
23+
private $source;
24+
25+
/** @var NodeInterface|null */
26+
private $node;
27+
28+
/** @var QuickReader|null */
29+
private $quickReader;
30+
31+
private static function checkRootElement(
32+
DOMDocument $document,
33+
string $expectedNamespace,
34+
string $expectedNsPrefix,
35+
string $expectedRootBaseNodeName
36+
): DOMElement {
37+
$rootElement = Xml::documentElement($document);
38+
39+
// is not docummented: lookupPrefix returns NULL instead of string when not found
40+
// this is why we are casting the value to string
41+
$nsPrefix = (string) $document->lookupPrefix($expectedNamespace);
42+
if ('' === $nsPrefix) {
43+
throw new \UnexpectedValueException(
44+
sprintf('Document does not implement namespace %s', $expectedNamespace)
45+
);
46+
}
47+
if ($expectedNsPrefix !== $nsPrefix) {
48+
throw new \UnexpectedValueException(
49+
sprintf('Prefix for namespace %s is not "%s"', $expectedNamespace, $expectedNsPrefix)
50+
);
51+
}
52+
53+
$expectedRootNodeName = $expectedNsPrefix . ':' . $expectedRootBaseNodeName;
54+
if ($rootElement->tagName !== $expectedRootNodeName) {
55+
throw new \UnexpectedValueException(sprintf('Root element is not %s', $expectedRootNodeName));
56+
}
57+
58+
return $rootElement;
59+
}
60+
61+
/**
62+
* Create a CFDI object from a xml string
63+
*
64+
* @param string $content
65+
* @return self
66+
*/
67+
public static function newFromString(string $content): self
68+
{
69+
$document = Xml::newDocumentContent($content);
70+
// populate source since it is already available, in this way we avoid the conversion from document to string
71+
/** @noinspection PhpMethodParametersCountMismatchInspection */
72+
$cfdi = new self($document);
73+
$cfdi->source = $content;
74+
return $cfdi;
75+
}
76+
77+
/**
78+
* Obtain the version from the document, if the version was not detected returns an empty string
79+
*/
80+
public function getVersion(): string
81+
{
82+
return $this->version;
83+
}
84+
85+
/**
86+
* Get a clone of the local DOM Document
87+
*/
88+
public function getDocument(): DOMDocument
89+
{
90+
return clone $this->document;
91+
}
92+
93+
/**
94+
* Get the XML string source
95+
*/
96+
public function getSource(): string
97+
{
98+
if (null === $this->source) {
99+
// pass the document element to avoid xml header
100+
$this->source = (string) $this->document->saveXML(Xml::documentElement($this->document));
101+
}
102+
103+
return $this->source;
104+
}
105+
106+
/**
107+
* Get the node object to iterate through the document
108+
*/
109+
public function getNode(): NodeInterface
110+
{
111+
if (null === $this->node) {
112+
$this->node = XmlNodeUtils::nodeFromXmlElement(Xml::documentElement($this->document));
113+
}
114+
115+
return $this->node;
116+
}
117+
118+
/**
119+
* Get the quick reader object to iterate through the document
120+
*/
121+
public function getQuickReader(): QuickReader
122+
{
123+
if (null === $this->quickReader) {
124+
$this->quickReader = (new QuickReaderImporter())->importDocument($this->document);
125+
}
126+
127+
return $this->quickReader;
128+
}
129+
}

0 commit comments

Comments
 (0)