Completed
Push — master ( da0794...b20e0d )
by Carlos C
02:40
created

AbstractXmlRetriever::doRetrieve()   B

Complexity

Conditions 5
Paths 7

Size

Total Lines 33
Code Lines 20

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 20
CRAP Score 5

Importance

Changes 0
Metric Value
dl 0
loc 33
ccs 20
cts 20
cp 1
rs 8.439
c 0
b 0
f 0
nc 7
cc 5
eloc 20
nop 1
crap 5
1
<?php
2
namespace XmlResourceRetriever;
3
4
use DOMDocument;
5
6
/**
7
 * This is an abstract implementation of Retriever interface when working with XML contents
8
 * Both, XsdRetriever and XsltRetriver depends on this class
9
 */
10
abstract class AbstractXmlRetriever extends AbstractBaseRetriever implements RetrieverInterface
11
{
12
    /**
13
     * Must return a string with the namespace to search for
14
     *
15
     * @return string
16
     */
17
    abstract protected function searchNamespace(): string;
18
19
    /**
20
     * Must return a table with rows (array of array)
21
     * every row must contain the keys element and attribute
22
     * "element" is the tag name to search for
23
     * "attribute" is the attribute name that contains the url
24
     *
25
     * @return array
26
     */
27
    abstract protected function searchElements(): array;
28
29 4
    public function retrieve(string $resource): string
30
    {
31 4
        $this->clearHistory();
32 4
        return $this->doRetrieve($resource);
33
    }
34
35
    /**
36
     * @param string $resource
37
     * @return string
38
     */
39 4
    private function doRetrieve(string $resource): string
40
    {
41 4
        $localFilename = $this->download($resource);
42 4
        $this->addToHistory($resource, $localFilename);
43
44 4
        $document = new DOMDocument();
45
        // this error silenced call is intentional,
46
        // don't need to change the value of libxml_use_internal_errors for this
47 4
        if (false === @$document->load($localFilename)) {
48 1
            unlink($localFilename);
49 1
            throw new \RuntimeException("The source $resource contains errors");
50
        }
51
52
        // call recursive get searching on specified the elements
53 3
        $changed = false;
54 3
        foreach ($this->searchElements() as $search) {
55 3
            $recursiveRetrieve = $this->recursiveRetrieve(
56 3
                $document,
57 3
                $search['element'],
58 3
                $search['attribute'],
59 3
                $resource,
60 3
                $localFilename
61
            );
62 3
            if ($recursiveRetrieve) {
63 3
                $changed = true;
64
            }
65
        }
66
67 3
        if ($changed) {
68 3
            $document->save($localFilename);
69
        }
70 3
        return $localFilename;
71
    }
72
73 3
    private function recursiveRetrieve(
74
        DOMDocument $document,
75
        string $tagName,
76
        string $attributeName,
77
        string $currentUrl,
78
        string $currentFile
79
    ): bool {
80 3
        $modified = false;
81 3
        $elements = $document->getElementsByTagNameNS($this->searchNamespace(), $tagName);
82 3
        foreach ($elements as $element) {
83
            /** @var \DOMElement $element */
84 3
            if (! $element->hasAttribute($attributeName)) {
85 1
                continue;
86
            }
87 3
            $location = $element->getAttribute($attributeName);
88 3
            if ('' === $location) {
89 1
                continue;
90
            }
91 3
            $location = $this->relativeToAbsoluteUrl($location, $currentUrl);
92 3
            if (array_key_exists($location, $this->retrieveHistory())) {
93 1
                continue;
94
            }
95 3
            $downloadedChild = $this->doRetrieve($location);
96 3
            $relative = Utils::relativePath($currentFile, $downloadedChild);
97 3
            $element->setAttribute($attributeName, $relative);
98 3
            $modified = true;
99
        }
100 3
        return $modified;
101
    }
102
103
    /**
104
     * This method checks if the recently downloaded file from $source located at $path
105
     * is a valid resource, if not will remove the file and throw an exception
106
     *
107
     * @param string $source
108
     * @param string $path
109
     * @throws \RuntimeException when the source is not valid
110
     */
111 7
    protected function checkIsValidDownloadedFile(string $source, string $path)
112
    {
113
        // check content is xml
114 7
        $mimetype = (new \finfo())->file($path, FILEINFO_MIME_TYPE);
115 7
        if (! in_array($mimetype, ['text/xml', 'application/xml'])) {
116 1
            unlink($path);
117 1
            throw new \RuntimeException("The source $source ($mimetype) is not an xml file");
118
        }
119 6
    }
120
121 3
    private function relativeToAbsoluteUrl(string $url, string $currentUrl)
122
    {
123 3
        if (false !== $this->urlParts($url)) {
124 3
            return $url;
125
        }
126 1
        $currentParts = $this->urlParts($currentUrl);
127 1
        $currentParts['port'] = (isset($currentParts['port'])) ? ':' . $currentParts['port'] : '';
128 1
        return implode('', [
129 1
            $currentParts['scheme'],
130 1
            '://',
131 1
            $currentParts['host'],
132 1
            $currentParts['port'],
133 1
            implode('/', Utils::simplifyPath(dirname($currentParts['path']) . '/' . $url)),
134
        ]);
135
    }
136
}
137