Issues (3)

src/AbstractDatabase.php (1 issue)

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 $entryArray) {
143
                    $entry = $this->arrayToEntry($entryArray);
144
                    foreach ($indexedFields as $indexName => $indexDefinition) {
145
                        if (is_array($indexDefinition)) {
146
                            // compound index
147
                            $reference = &$this->index[$indexName];
148
                            foreach ($indexDefinition as $indexDefinitionPart) {
149
                                // limited length of field
150
                                if (is_array($indexDefinitionPart)) {
151
                                    $indexDefinitionPartValue = substr(
152
                                        $entryArray[$indexDefinitionPart[0]],
153
                                        0,
154
                                        $indexDefinitionPart[1]
155
                                    );
156
                                } else {
157
                                    $indexDefinitionPartValue = $entryArray[$indexDefinitionPart];
158
                                }
159
                                if (!isset($reference[$indexDefinitionPartValue])) {
160
                                    $reference[$indexDefinitionPartValue] = [];
161
                                }
162
                                $reference = &$reference[$indexDefinitionPartValue];
163
                            }
164
165
                            $reference = $entry;
166
                        } else {
167
                            // single index
168
                            $indexName = $indexDefinition;
169
                            // skip empty field
170
                            if (empty($entryArray[$indexDefinition])) {
171
                                continue;
172
                            }
173
                            // add to index
174
                            $this->index[$indexName][$entryArray[$indexDefinition]] = $entry;
175
                        }
176
                    }
177
                }
178
            }
179
        }
180
181
        // get index
182
        if (!isset($this->index[$indexedFieldName])) {
183
            throw new \InvalidArgumentException(
184
                sprintf(
185
                    'Unknown index "%s" in database "%s"',
186
                    $indexedFieldName,
187
                    get_class()
188
                )
189
            );
190
        }
191
192
        return $this->index[$indexedFieldName];
193
    }
194
195
    /**
196
     * @param string $indexedFieldName
197
     * @param string|int $fieldValue
198
     *
199
     * @return object|null
200
     */
201
    protected function find($indexedFieldName, $fieldValue)
202
    {
203
        $fieldIndex = $this->getIndex($indexedFieldName);
204
205
        return isset($fieldIndex[$fieldValue]) ? $fieldIndex[$fieldValue] : null;
206
    }
207
208
    /**
209
     * Builds array of entries.
210
     * Creates many entry objects in loop, use iterator instead.
211
     *
212
     * @return object[]
213
     */
214
    public function toArray()
215
    {
216
        return iterator_to_array($this);
217
    }
218
219
    /**
220
     * @return object
221
     */
222
    public function current()
223
    {
224
        return $this->arrayToEntry(current($this->clusterIndex));
225
    }
226
227
    /**
228
     * @return int|null
229
     */
230
    public function key()
231
    {
232
        return key($this->clusterIndex);
0 ignored issues
show
Bug Best Practice introduced by
The expression return key($this->clusterIndex) also could return the type string which is incompatible with the documented return type null|integer.
Loading history...
233
    }
234
235
    public function next()
236
    {
237
        next($this->clusterIndex);
238
    }
239
    
240
    public function rewind()
241
    {
242
        reset($this->clusterIndex);
243
    }
244
245
    /**
246
     * @return bool
247
     */
248
    public function valid()
249
    {
250
        return $this->key() !== null;
251
    }
252
253
    /**
254
     * @return int
255
     */
256
    public function count()
257
    {
258
        return count($this->clusterIndex);
259
    }
260
}
261