Completed
Push — master ( 678089...988510 )
by Andrii
02:43
created

Factory::find()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 9
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 3
eloc 4
nc 3
nop 2
dl 0
loc 9
rs 10
c 1
b 0
f 0
1
<?php
2
/**
3
 * PHP Billing Library
4
 *
5
 * @link      https://github.com/hiqdev/php-billing
6
 * @package   php-billing
7
 * @license   BSD-3-Clause
8
 * @copyright Copyright (c) 2017-2018, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\tools;
12
13
use DateTimeImmutable;
14
use Money\Currency;
15
use hiqdev\php\units\Quantity;
16
use hiqdev\php\units\Unit;
17
use Money\Parser\DecimalMoneyParser;
18
use Money\Currencies\ISOCurrencies;
19
use hiqdev\php\billing\Exception\UnknownEntityException;
20
21
/**
22
 * Generalized entity factory.
23
 *
24
 * @author Andrii Vasyliev <[email protected]>
25
 */
26
class Factory
27
{
28
    private $entities = [];
29
30
    private $factories = [];
31
32
    protected $moneyParser;
33
34
    public function __construct(array $factories)
35
    {
36
        $this->factories = $factories;
37
        $this->moneyParser = new DecimalMoneyParser(new ISOCurrencies());
38
    }
39
40
    public function getMoney($data)
41
    {
42
        return $this->get('money', $data);
43
    }
44
45
    public function parseMoney($str)
46
    {
47
        [$amount, $currency] = explode(' ', $str);
48
49
        return [
50
            'amount' => $amount,
51
            'currency' => $currency,
52
        ];
53
    }
54
55
    public function createMoney($data)
56
    {
57
        return $this->moneyParser->parse($data['amount'], $data['currency']);
58
    }
59
60
    public function getCurrency($data)
61
    {
62
        return new Currency($data);
63
    }
64
65
    public function getQuantity($data)
66
    {
67
        return $this->get('quantity', $data);
68
    }
69
70
    public function parseQuantity($str)
71
    {
72
        [$quantity, $unit] = explode(' ', $str);
73
74
        return [
75
            'quantity' => $quantity,
76
            'unit' => $unit,
77
        ];
78
    }
79
80
    public function createQuantity($data)
81
    {
82
        return Quantity::create($data['unit'], $data['quantity']);
83
    }
84
85
    public function getUnit($data)
86
    {
87
        return $this->get('unit', $data);
88
    }
89
90
    public function createUnit($data)
91
    {
92
        return Unit::create($data['name']);
93
    }
94
95
    public function getType($data)
96
    {
97
        return $this->get('type', $data);
98
    }
99
100
    public function getTime($data)
101
    {
102
        return $this->get('time', $data);
103
    }
104
105
    public function createTime($data)
106
    {
107
        return new DateTimeImmutable($data['time']);
108
    }
109
110
    public function getTarget($data)
111
    {
112
        return $this->get('target', $data);
113
    }
114
115
    public function getPlan($data)
116
    {
117
        return $this->get('plan', $data);
118
    }
119
120
    public function getSale($data)
121
    {
122
        return $this->get('sale', $data);
123
    }
124
125
    public function getCustomer($data)
126
    {
127
        return $this->get('customer', $data);
128
    }
129
130
    public function get(string $entity, $data)
131
    {
132
        if (is_scalar($data)) {
133
            $data = $this->parse($entity, $data);
134
        }
135
136
        $keys = $this->extractKeys($entity, $data);
137
138
        $res = $this->find($entity, $keys) ?: $this->create($entity, $data);
139
140
        foreach ($keys as $key) {
141
            $this->entities[$entity][$key] = $res;
142
        }
143
144
        return $res;
145
    }
146
147
    public function parse(string $entity, $str)
148
    {
149
        $method = $this->getMethod($entity, 'parse');
150
151
        return $method ? $this->{$method}($str) : $this->parseByUnique($entity, $str);
152
    }
153
154
    public function parseByUnique(string $entity, $str)
155
    {
156
        $keys = $this->getEntityUniqueKeys($entity);
157
        if (count($keys) === 1) {
158
            return [reset($keys) => $str];
159
        }
160
161
        return ['id' => $str];
162
    }
163
164
    public function find(string $entity, array $keys)
165
    {
166
        foreach ($keys as $key) {
167
            if (!empty($this->entities[$entity][$key])) {
168
                return $this->entities[$entity][$key];
169
            }
170
        }
171
172
        return null;
173
    }
174
175
    public function create(string $entity, $data)
176
    {
177
        $method = $this->getMethod($entity, 'create');
178
        if ($method) {
179
            return $this->{$method}($data);
180
        }
181
182
        if (empty($this->factories[$entity])) {
183
            throw new FactoryNotFoundException($entity);
184
        }
185
186
        $factory = $this->factories[$entity];
187
188
        return $factory->create($this->createDto($entity, $data));
189
    }
190
191
    public function createDto(string $entity, array $data)
192
    {
193
        $class = $this->getDtoClass($entity);
194
        $dto = new $class();
195
196
        foreach ($data as $key => $value) {
197
            $dto->{$key} = $this->prepareValue($entity, $key, $value);
198
        }
199
200
        return $dto;
201
    }
202
203
    public function getDtoClass(string $entity)
204
    {
205
        return $this->getEntityClass($entity) . 'CreationDto';
206
    }
207
208
    public function prepareValue($entity, $key, $value)
209
    {
210
        $method = $this->getPrepareMethod($entity, $key);
211
212
        return $method ? $this->{$method}($value) : $value;
213
    }
214
215
    private function getMethod(string $entity, string $op)
216
    {
217
        $method = $op . ucfirst($entity);
218
219
        return method_exists($this, $method) ? $method : null;
220
    }
221
222
    private $prepareMethods = [
223
        'seller'    => 'getCustomer',
224
        'customer'  => 'getCustomer',
225
        'plan'      => 'getPlan',
226
        'sale'      => 'getSale',
227
        'type'      => 'getType',
228
        'target'    => 'getTarget',
229
        'price'     => 'getMoney',
230
        'currency'  => 'getCurrency',
231
        'prepaid'   => 'getQuantity',
232
        'quantity'  => 'getQuantity',
233
        'unit'      => 'getUnit',
234
        'time'      => 'getTime',
235
    ];
236
237
    private function getPrepareMethod(string $entity, string $key)
238
    {
239
        return $this->prepareMethods[$key] ?? null;
240
    }
241
242
    public function getEntityClass(string $entity)
243
    {
244
        $parts = explode('\\', __NAMESPACE__);
245
        array_pop($parts);
246
        $parts[] = $entity;
247
        $parts[] = ucfirst($entity);
248
249
        return implode('\\', $parts);
250
    }
251
252
    public function extractKeys(string $entity, $data)
253
    {
254
        $id = $data['id'] ?? null;
255
        $unique = $this->extractUnique($entity, $data);
256
257
        return array_filter(['id' => $id, 'unique' => $unique]);
258
    }
259
260
    public function extractUnique(string $entity, $data)
261
    {
262
        $keys = $this->getEntityUniqueKeys($entity);
263
        if (empty($keys)) {
264
            return null;
265
        }
266
267
        $values = [];
268
        foreach ($keys as $key) {
269
            if (empty($data[$key])) {
270
                return null;
271
            }
272
            $values[$key] = $data[$key];
273
        }
274
275
        return implode(' ', $values);
276
    }
277
278
279
    private $uniqueKeys = [
280
        'customer'  => ['login'],
281
        'type'      => ['name'],
282
        'plan'      => ['name', 'seller'],
283
        'sale'      => [],
284
        'action'    => [],
285
        'price'     => [],
286
        'target'    => ['type', 'name'],
287
        'money'     => ['amount', 'currency'],
288
        'time'      => ['time'],
289
        'unit'      => ['name'],
290
        'quantity'  => ['quantity', 'unit'],
291
    ];
292
293
    public function getEntityUniqueKeys(string $entity): array
294
    {
295
        $keys = $this->uniqueKeys[$entity] ?? null;
296
297
        if (is_null($keys)) {
298
            throw new UnknownEntityException($entity);
299
        }
300
301
        return $keys;
302
    }
303
}
304