Passed
Branch master (609f2f)
by Dorian
08:59
created

XmlLibraryFactory::buildBooks()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11
Code Lines 6

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 2
eloc 6
c 1
b 0
f 0
nc 2
nop 1
dl 0
loc 11
rs 10
1
<?php
2
3
declare(strict_types=1);
4
5
namespace Gnutix\Library\LibraryFactory;
6
7
use DateTime;
8
use Gnutix\Library\Dumper\YamlLibraryDumper;
9
use Gnutix\Library\LibraryFactoryInterface;
10
use Gnutix\Library\LibraryInterface;
11
use Gnutix\Library\Loader\XmlFileLoader;
12
use SimpleXMLElement;
13
use Webmozart\Assert\Assert;
14
15
class XmlLibraryFactory implements LibraryFactoryInterface
16
{
17
    protected array $classes;
18
    private LibraryInterface $library;
19
20
    public function __construct(XmlFileLoader $loader, array $classes)
21
    {
22
        $this->classes = $classes;
23
        $this->library = new $this->classes['library']($this->getLibraryDependencies($loader->getData()));
24
    }
25
26
    public function getLibrary(): LibraryInterface
27
    {
28
        return $this->library;
29
    }
30
31
    public function getLibraryDumper(): YamlLibraryDumper
32
    {
33
        return new YamlLibraryDumper();
34
    }
35
36
    protected function getLibraryDependencies(SimpleXMLElement $data): array
37
    {
38
        return [
39
            'books' => $this->buildBooks($data),
40
            'categories' => $this->buildClassInstanceFromNodeAttributes(
41
                $data,
42
                '//information/types/type',
43
                'category',
44
                ['code' => 'id']
45
            ),
46
            'editors' => $this->buildClassInstanceFromNodeAttributes(
47
                $data,
48
                '//information/editors/editor',
49
                'editor',
50
                ['code' => 'id', 'lang' => 'preferredLanguage']
51
            ),
52
        ];
53
    }
54
55
    protected function renameArrayKeys(array $data, array $keys): array
56
    {
57
        foreach ($keys as $old => $new) {
58
            if (!isset($data[$old])) {
59
                continue;
60
            }
61
62
            $data[$new] = $data[$old];
63
            unset($data[$old]);
64
        }
65
66
        return $data;
67
    }
68
69
    protected function buildClassInstanceFromNodeAttributes(
70
        SimpleXMLElement $data,
71
        string $xpathSelector,
72
        string $targetClass,
73
        array $renameKeys = []
74
    ): array {
75
        $editors = [];
76
        $className = $this->classes[$targetClass];
77
        $elements = $data->xpath($xpathSelector);
78
        Assert::isIterable($elements);
79
80
        foreach ($elements as $element) {
81
            $dependencies = $this->getSimpleXmlElementAttributesAsArray($element);
82
83
            if (!empty($renameKeys)) {
84
                $dependencies = $this->renameArrayKeys($dependencies, $renameKeys);
85
            }
86
87
            $editors[] = new $className($dependencies);
88
        }
89
90
        return $editors;
91
    }
92
93
    protected function getSimpleXmlElementAttributesAsArray(?SimpleXMLElement $xmlElement = null): array
94
    {
95
        if (null === $xmlElement) {
96
            return [];
97
        }
98
99
        $attributes = (array) $xmlElement->attributes();
100
101
        return $attributes['@attributes'] ?? [];
102
    }
103
104
    protected function buildBooks(SimpleXMLElement $data): array
105
    {
106
        $books = [];
107
        $elements = $data->xpath('//books/era/book');
108
        Assert::isIterable($elements);
109
110
        foreach ($elements as $book) {
111
            $books[] = new $this->classes['book']($this->getBooksDependencies($data, $book));
112
        }
113
114
        return $books;
115
    }
116
117
    protected function getBooksDependencies(SimpleXMLElement $data, SimpleXMLElement $book): array
118
    {
119
        // Book attributes
120
        $bookAttributes = $this->getSimpleXmlElementAttributesAsArray($book);
121
122
        // Series attributes
123
        $series = $book->{'series'}[0];
124
        $seriesAttributes = $this->getSimpleXmlElementAttributesAsArray($series);
125
126
        // Publish attributes
127
        $publish = $book->{'publish'}[0];
128
        $publishAttributes = $this->getSimpleXmlElementAttributesAsArray($publish);
129
130
        // Editor attributes
131
        $releaseEditor = $book->{'editor'}[0];
132
        $releaseEditorAttributes = $this->getSimpleXmlElementAttributesAsArray($releaseEditor);
133
134
        // Categories data
135
        $categories = $data->xpath('//information/types/type[@code="'.$bookAttributes['type'].'"]');
136
        Assert::isIterable($categories);
137
        $category = reset($categories);
138
        Assert::isInstanceOf($category, SimpleXMLElement::class);
139
        $categoryAttributes = $this->getSimpleXmlElementAttributesAsArray($category);
140
141
        // Releases data
142
        $releases = [];
143
        foreach (array_keys($releaseEditorAttributes) as $release) {
144
            $releaseNode = $book->{$release};
145
146
            // Categories data
147
            $editors = $data->xpath('//information/editors/editor[@code="'.$releaseEditorAttributes[$release].'"]');
148
            Assert::isIterable($editors);
149
            $editor = reset($editors);
150
            Assert::isInstanceOf($editor, SimpleXMLElement::class);
151
            $editorAttributes = $this->getSimpleXmlElementAttributesAsArray($editor);
152
153
            $releases[] = new $this->classes['release'](
154
                [
155
                    'title' => (string) $book->{'title'},
156
                    'editor' => new $this->classes['editor'](
157
                        $this->renameArrayKeys($editorAttributes, ['code' => 'id', 'lang' => 'preferredLanguage'])
158
                    ),
159
                    'publicationDate' => isset($publishAttributes[$release])
160
                        ? $this->transformToDateTime($publishAttributes[$release])
161
                        : null,
162
                    'language' => new $this->classes['language'](
163
                        [
164
                            'id' => $editorAttributes['lang'],
165
                            'name' => $editorAttributes['lang'],
166
                        ]
167
                    ),
168
                    'owner' => new $this->classes['owner'](
169
                        [
170
                            'nbCopies' => isset($releaseNode['copies']) ? (int) $releaseNode['copies'] : null,
171
                            'nbReadings' => isset($releaseNode['readings']) ? (int) $releaseNode['readings'] : null,
172
                        ]
173
                    ),
174
                    'format' => new $this->classes['format'](),
175
                    'series' => new $this->classes['series'](
176
                        null !== $series
177
                            ? [
178
                                'title' => (string) $series,
179
                                'bookId' => $seriesAttributes['number'] ?? null,
180
                            ]
181
                            : []
182
                    ),
183
                ]
184
            );
185
        }
186
187
        $authors = [];
188
        foreach (explode(',', (string) $book->{'author'}) as $author) {
189
            $authors[] = new $this->classes['author'](['name' => trim($author)]);
190
        }
191
192
        return [
193
            'category' => new $this->classes['category'](
194
                [
195
                    'id' => $bookAttributes['type'],
196
                    'name' => $categoryAttributes['name'],
197
                ]
198
            ),
199
            'authors' => $authors,
200
            'releases' => $releases,
201
        ];
202
    }
203
204
    private function transformToDateTime(string $date): DateTime
205
    {
206
        $explodedDate = explode('.', $date);
207
        $dateTime = new DateTime();
208
209
        return $dateTime->setDate((int) $explodedDate[2], (int) $explodedDate[1], (int) $explodedDate[0]);
210
    }
211
}
212