Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
96.12% covered (success)
96.12%
99 / 103
84.62% covered (success)
84.62%
11 / 13
CRAP
0.00% covered (danger)
0.00%
0 / 1
Exchanger
96.12% covered (success)
96.12%
99 / 103
84.62% covered (success)
84.62%
11 / 13
41
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 new
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 key
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 value
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 arrayDefine
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 execute
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 defineRoutine
94.64% covered (success)
94.64%
53 / 56
0.00% covered (danger)
0.00%
0 / 1
19.06
 inputRoutine
100.00% covered (success)
100.00%
24 / 24
100.00% covered (success)
100.00%
1 / 1
6
 isExchanges
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 isKeyExchanges
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 isValueExchanges
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 keyExchange
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 valueExchange
80.00% covered (success)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
3.07
1<?php
2
3/**
4 * @license MIT
5 * @author hazuki3417<hazuki3417@gmail.com>
6 * @copyright 2022 hazuki3417 all rights reserved.
7 */
8
9namespace Selen\Schema;
10
11use Selen\Schema\Exchange\ArrayDefine;
12use Selen\Schema\Exchange\KeyExchangeInterface;
13use Selen\Schema\Exchange\ValueExchangeInterface;
14
15class Exchanger
16{
17    /** @var KeyExchangeInterface|callable|null */
18    private $keyExchangesExecute;
19
20    /** @var ValueExchangeInterface|callable|null */
21    private $valueExchangesExecute;
22
23    /** @var ArrayDefine|null */
24    private $arrayDefine;
25
26    /**
27     * インスタンスを生成します
28     *
29     * @return Exchanger
30     */
31    private function __construct()
32    {
33    }
34
35    /**
36     * インスタンスを生成します
37     */
38    public static function new(): Exchanger
39    {
40        return new self();
41    }
42
43    /**
44     * keyの変換処理を設定します(全体設定)
45     */
46    public function key(KeyExchangeInterface|callable|null $execute): Exchanger
47    {
48        $this->keyExchangesExecute = $execute;
49        return $this;
50    }
51
52    /**
53     * valueの変換処理を設定します(全体設定)
54     */
55    public function value(ValueExchangeInterface|callable|null $execute): Exchanger
56    {
57        $this->valueExchangesExecute = $execute;
58        return $this;
59    }
60
61    /**
62     * key・valueの変換処理を設定します(個別設定)
63     */
64    public function arrayDefine(ArrayDefine $arrayDefine = null): Exchanger
65    {
66        $this->arrayDefine = $arrayDefine;
67        return $this;
68    }
69
70    /**
71     * 変換処理を実行します
72     *
73     * @param array<mixed,mixed> $input 変換する配列を渡します
74     *
75     * @return array<mixed,mixed> 変換した配列を返します
76     */
77    public function execute(array $input): array
78    {
79        $input = $this->defineRoutine($input, $this->arrayDefine);
80        return $this->inputRoutine($input);
81    }
82
83    /**
84     * 定義した配列形式に変換します(個別設定)
85     *
86     * @param array<mixed,mixed> $input       変換する配列を渡します
87     * @param ArrayDefine|null   $arrayDefine 変換の定義を渡します
88     *
89     * @return array<mixed,mixed> 変換した配列を返します
90     */
91    private function defineRoutine(
92        array $input,
93        ArrayDefine $arrayDefine = null
94    ): array {
95        if ($arrayDefine === null) {
96            // 変換の定義がないときの処理
97            return $input;
98        }
99
100        // 変換の定義があるときの処理
101
102        /** @var Exchange\Define $define */
103        foreach ($arrayDefine->defines as $define) {
104            if ($define->nestedTypeDefineExists()) {
105                // ネストされた定義なら再帰処理を行う
106
107                if ($define->key->getName() !== null) {
108                    // ネストされた定義 + keyがnull以外のとき = keyありのArrayObject形式の定義
109                    // assoc配列の変換処理
110                    if (!\array_key_exists($define->key->getName(), $input)) {
111                        continue;
112                    }
113
114                    // 定義したkeyに一致するkeyがinput側にあったとき
115                    $input[$define->key->getName()] = $this->defineRoutine(
116                        $input[$define->key->getName()],
117                        $define->arrayDefine
118                    );
119                    continue;
120                }
121                // ネストされた定義 + keyがnullのとき = keyなしのArrayObject形式の定義
122                $inputItems = $input;
123
124                if (!\is_array($inputItems)) {
125                    // inputItemsが配列以外のときは何もしない
126                    continue;
127                }
128
129                foreach ($inputItems as $index => $inputItem) {
130                    if (!\is_array($inputItem)) {
131                        // inputItemが配列以外のときは何もしない
132                        continue;
133                    }
134                    $input[$index] = $this->defineRoutine(
135                        $input[$index],
136                        $define->arrayDefine
137                    );
138                }
139                continue;
140            }
141
142            // ネストされていない定義なら変換処理へ進む
143            if ($define->isKeyExchange()) {
144                // keyの変換処理
145                if ($define->key->isRemoveKey()) {
146                    unset($input[$define->key->getName()]);
147                    // NOTE: keyを消した場合は値の変換は必要ないので抜ける
148                    continue;
149                }
150
151                if ($define->key->isAddKey()) {
152                    $addKeyDefaultValue = null;
153
154                    if (!\array_key_exists($define->key->getName(), $input)) {
155                        // NOTE: 存在しない場合はkeyを追加して値を初期化する。
156                        $input[$define->key->getName()] = $addKeyDefaultValue;
157                    }
158                }
159
160                if ($define->key->isRenameKey()) {
161                    $beforeName = $define->key->getName();
162                    $afterName  = $this->keyExchange(
163                        $define->keyExchangeExecute,
164                        $beforeName
165                    );
166                    $tmpValue = $input[$beforeName];
167                    unset($input[$beforeName]);
168                    $input[$afterName] = $tmpValue;
169                    $define->key->setName($afterName);
170                }
171            }
172
173            if ($define->isValueExchange()) {
174                // valueの変換処理
175                if ($define->isAssocArrayDefine()) {
176                    // key定義ありのときの処理
177
178                    $keyExists = \array_key_exists($define->key->getName(), $input);
179
180                    if (!$keyExists) {
181                        // NOTE: 定義側にkeyが存在し、入力側にkeyがないときは処理しない
182                        continue;
183                    }
184
185                    $input[$define->key->getName()] = $this->valueExchange(
186                        $define->valueExchangeExecute,
187                        $input[$define->key->getName()]
188                    );
189                    continue;
190                }
191
192                if ($define->isIndexArrayDefine()) {
193                    // key定義なしのときの処理
194                    foreach ($input as $key => $value) {
195                        $input[$key] = $this->valueExchange($define->valueExchangeExecute, $value);
196                    }
197                    continue;
198                }
199            }
200        }
201        return $input;
202    }
203
204    /**
205     * 定義した配列形式に変換します(全体設定)
206     *
207     * @param array<mixed,mixed> $input 変換する配列を渡します
208     *
209     * @return array<mixed,mixed> 変換した配列を返します
210     */
211    private function inputRoutine(array $input): array
212    {
213        if (!$this->isExchanges()) {
214            // 全体の変換処理が定義されていないなら入力値をそのまま返す
215            return $input;
216        }
217
218        // 全体の変換処理が定義されているなら入力側のループ処理を行う
219        foreach ($input as $key => $value) {
220            if ($this->isKeyExchanges()) {
221                $beforeName = $key;
222                $afterName  = $this->keyExchange(
223                    $this->keyExchangesExecute,
224                    $beforeName
225                );
226                $tmpValue = $input[$beforeName];
227                unset($input[$beforeName]);
228                $input[$afterName] = $tmpValue;
229                $key               = $afterName;
230            }
231
232            if ($this->isValueExchanges()) {
233                $input[$key] = $this->valueExchange(
234                    $this->valueExchangesExecute,
235                    $input[$key]
236                );
237            }
238
239            if (\is_array($value)) {
240                // 値が配列なら再帰処理を行う
241                $input[$key] = $this->inputRoutine(
242                    $input[$key]
243                );
244                continue;
245            }
246        }
247        return $input;
248    }
249
250    /**
251     * 変換処理を実行するかどうか判定します。
252     *
253     * @return bool 変換する場合はtrueを、それ以外の場合はfalseを返します
254     */
255    private function isExchanges()
256    {
257        return $this->isKeyExchanges() || $this->isValueExchanges();
258    }
259
260    /**
261     * keyの変換処理を実行するかどうか判定します。
262     *
263     * @return bool 変換する場合はtrueを、それ以外の場合はfalseを返します
264     */
265    private function isKeyExchanges()
266    {
267        return $this->keyExchangesExecute !== null;
268    }
269
270    /**
271     * valueの変換処理を実行するかどうか判定します。
272     *
273     * @return bool 変換する場合はtrueを、それ以外の場合はfalseを返します
274     */
275    private function isValueExchanges()
276    {
277        return $this->valueExchangesExecute !== null;
278    }
279
280    /**
281     * keyの変換処理を行います
282     *
283     * @param Exchange\KeyExchangeInterface|callable|null $execute
284     * @param string                                      $key
285     *
286     * @return string
287     */
288    private function keyExchange($execute, $key)
289    {
290        if ($execute instanceof KeyExchangeInterface) {
291            return $execute->execute($key);
292        }
293
294        if (\is_callable($execute)) {
295            return $execute($key);
296        }
297
298        return $key;
299    }
300
301    /**
302     * 値の変換処理を行います
303     *
304     * @param Exchange\ValueExchangeInterface|callable|null $execute
305     * @param mixed                                         $value
306     *
307     * @return mixed
308     */
309    private function valueExchange($execute, $value)
310    {
311        if ($execute instanceof ValueExchangeInterface) {
312            return $execute->execute($value);
313        }
314
315        if (\is_callable($execute)) {
316            return $execute($value);
317        }
318
319        return $value;
320    }
321}