Passed
Push — master ( 018a15...3536a4 )
by De
01:53
created

AbstractDatabase::loadDatabase()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 23
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 23
rs 9.0856
c 0
b 0
f 0
cc 1
eloc 11
nc 1
nop 1
1
<?php
2
3
namespace Sokil\IsoCodes;
4
5
/**
6
 * Abstract collection of ISO entries
7
 */
8
abstract class AbstractDatabase implements \Iterator, \Countable
9
{
10
    /**
11
     * Path to ISO databases
12
     */
13
    const DATABASE_PATH = '/../databases';
14
15
    /**
16
     * Path to gettext localised messages
17
     */
18
    const MESSAGES_PATH = '/../messages';
19
20
    /**
21
     * Cluster index used for iteration by entries
22
     *
23
     * @var array[]
24
     */
25
    private $clusterIndex;
26
27
    /**
28
     * Index to search by entry field's values
29
     *
30
     * @var array
31
     */
32
    private $index;
33
34
    public function __construct()
35
    {
36
        $this->loadDatabase($this->getDatabaseFilePath());
37
    }
38
39
    /**
40
     * ISO Standard Number
41
     *
42
     * @return string
43
     *
44
     * @throws \Exception
45
     */
46
    public static function getISONumber()
47
    {
48
        // abstract static methods not allowed on PHP < 7.0
49
        throw new \Exception(
50
            sprintf(
51
                'Method "%s" must be inmpemented in class %s',
52
                __METHOD__,
53
                get_class()
54
            )
55
        );
56
    }
57
58
    /**
59
     * @param array $entry
60
     *
61
     * @return object
62
     */
63
    abstract protected function arrayToEntry(array $entry);
64
65
    /**
66
     * List of entry fields to be indexed and searched.
67
     * May be override in child classes to search by indexed fields.
68
     *
69
     * First index in array used as cluster index.
70
     *
71
     * @return array
72
     */
73
    protected function getIndexDefinition()
74
    {
75
        return [];
76
    }
77
78
    /**
79
     * @return string
80
     */
81
    private function getDatabaseFilePath()
82
    {
83
        return __DIR__ . self::DATABASE_PATH . '/iso_' . $this->getISONumber() . '.json';
84
    }
85
86
    /**
87
     * @return string
88
     */
89
    private function getLocalMessagesPath()
90
    {
91
        return __DIR__ . self::MESSAGES_PATH;
92
    }
93
94
    /**
95
     * Build cluster index for iteration
96
     *
97
     * @param string $databaseFile
98
     */
99
    private function loadDatabase($databaseFile)
100
    {
101
        $isoNumber = $this->getISONumber();
102
103
        // add gettext domain
104
        bindtextdomain(
105
            $isoNumber,
106
            $this->getLocalMessagesPath()
107
        );
108
109
        bind_textdomain_codeset(
110
            $isoNumber,
111
            'UTF-8'
112
        );
113
114
        // load database from json file
115
        $json = json_decode(
116
            file_get_contents($databaseFile),
117
            true
118
        );
119
120
        // build cluster index from database
121
        $this->clusterIndex = $json[$isoNumber];
122
    }
123
124
    /**
125
     * @param string $indexedFieldName
126
     *
127
     * @return array
128
     */
129
    private function getIndex($indexedFieldName)
130
    {
131
        // build index
132
        if ($this->index === null) {
133
            // init empty index
134
            $this->index = [];
135
136
            // get index definition
137
            $indexedFields = $this->getIndexDefinition();
138
139
            // build index for database
140
            if (!empty($indexedFields)) {
141
                // init all defined indexes
142
                foreach ($this->clusterIndex as &$entry) {
143
                    foreach ($indexedFields as $indexName => $indexDefinition) {
144
                        if (is_array($indexDefinition)) {
145
                            $reference = &$this->index[$indexName];
146
                            // compound index
147
                            foreach ($indexDefinition as $indexDefinitionPart) {
148
                                // limited length of field
149
                                if (is_array($indexDefinitionPart)) {
150
                                    $indexDefinitionPartValue = substr(
151
                                        $entry[$indexDefinitionPart[0]],
152
                                        0,
153
                                        $indexDefinitionPart[1]
154
                                    );
155
                                } else {
156
                                    $indexDefinitionPartValue = $entry[$indexDefinitionPart];
157
                                }
158
                                if (!isset($reference[$indexDefinitionPartValue])) {
159
                                    $reference[$indexDefinitionPartValue] = [];
160
                                }
161
                                $reference = &$reference[$indexDefinitionPartValue];
162
                            }
163
164
                            $reference = $this->arrayToEntry($entry);
165
                        } else {
166
                            // single index
167
                            $indexName = $indexDefinition;
168
                            // skip empty field
169
                            if (empty($entry[$indexDefinition])) {
170
                                continue;
171
                            }
172
                            // add to index
173
                            $this->index[$indexName][$entry[$indexDefinition]] = $this->arrayToEntry($entry);
174
                        }
175
                    }
176
                }
177
            }
178
        }
179
180
        // get index
181
        if (!isset($this->index[$indexedFieldName])) {
182
            throw new \InvalidArgumentException(
183
                sprintf(
184
                    'Unknown index "%s" in database "%s"',
185
                    $indexedFieldName,
186
                    get_class()
187
                )
188
            );
189
        }
190
191
        return $this->index[$indexedFieldName];
192
    }
193
194
    /**
195
     * @param string $indexedFieldName
196
     * @param string|int $fieldValue
197
     *
198
     * @return object|null
199
     */
200
    protected function find($indexedFieldName, $fieldValue)
201
    {
202
        $fieldIndex = $this->getIndex($indexedFieldName);
203
        if (!isset($fieldIndex[$fieldValue])) {
204
            throw new \InvalidArgumentException(sprintf('Unknown field %s', $indexedFieldName));
205
        }
206
207
        return $fieldIndex[$fieldValue];
208
    }
209
210
    /**
211
     * Builds array of entries.
212
     * Creates many entry objects in loop, use iterator instead.
213
     *
214
     * @return object[]
215
     */
216
    public function toArray()
217
    {
218
        return iterator_to_array($this);
219
    }
220
221
    /**
222
     * @return object
223
     */
224
    public function current()
225
    {
226
        return $this->arrayToEntry(current($this->clusterIndex));
227
    }
228
229
    /**
230
     * @return int|null
231
     */
232
    public function key()
233
    {
234
        return key($this->clusterIndex);
235
    }
236
237
    public function next()
238
    {
239
        next($this->clusterIndex);
240
    }
241
    
242
    public function rewind()
243
    {
244
        reset($this->clusterIndex);
245
    }
246
247
    /**
248
     * @return bool
249
     */
250
    public function valid()
251
    {
252
        return $this->key() !== null;
253
    }
254
255
    /**
256
     * @return int
257
     */
258
    public function count()
259
    {
260
        return count($this->clusterIndex);
261
    }
262
}
263