Factory   F
last analyzed

Complexity

Total Complexity 69

Size/Duplication

Total Lines 389
Duplicated Lines 0 %

Test Coverage

Coverage 83.11%

Importance

Changes 7
Bugs 0 Features 0
Metric Value
eloc 163
dl 0
loc 389
ccs 123
cts 148
cp 0.8311
rs 2.88
c 7
b 0
f 0
wmc 69

41 Methods

Rating   Name   Duplication   Size   Complexity  
A getSums() 0 8 2
A parseMoney() 0 7 1
A getTime() 0 3 1
A getUnit() 0 3 1
A parseQuantity() 0 7 1
A getMoney() 0 3 1
A __construct() 0 3 1
A createMoney() 0 3 1
A createUnit() 0 3 1
A getCurrency() 0 3 1
A getQuantity() 0 3 1
A getType() 0 3 1
A createQuantity() 0 3 1
A getCharge() 0 3 1
A getPlan() 0 3 1
A getBill() 0 3 1
A getSale() 0 3 1
A getTargets() 0 3 1
A getCharges() 0 8 2
A parse() 0 5 2
A createTargets() 0 8 2
A getPrice() 0 3 1
A parseByUnique() 0 15 3
A find() 0 9 3
A clearEntitiesCache() 0 6 1
A createTime() 0 12 3
A getCustomer() 0 3 1
A getTarget() 0 3 1
A getAction() 0 3 1
A get() 0 15 4
A getMethod() 0 5 2
A getPrepareMethod() 0 10 5
A extractUnique() 0 20 5
A extractKeys() 0 6 1
A getEntityUniqueKeys() 0 9 2
A prepareValue() 0 8 3
A getDtoClass() 0 3 1
A getEntityClass() 0 8 1
A create() 0 14 3
A createDto() 0 10 2
A clearEntitiesCacheByType() 0 6 1

How to fix   Complexity   

Complex Class

Complex classes like Factory often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Factory, and based on these observations, apply Extract Interface, too.

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-2020, HiQDev (http://hiqdev.com/)
9
 */
10
11
namespace hiqdev\php\billing\tools;
12
13
use DateTimeImmutable;
14
use hiqdev\php\billing\Exception\UnknownEntityException;
15
use hiqdev\php\billing\target\TargetCollection;
16
use hiqdev\php\units\Quantity;
17
use hiqdev\php\units\Unit;
18
use Money\Currency;
19
use Money\Money;
20
21
/**
22
 * Generalized entity factory.
23
 *
24
 * @author Andrii Vasyliev <[email protected]>
25
 */
26
class Factory implements FactoryInterface
27
{
28
    private $entities = [];
29
30
    private $factories = [];
31
32 14
    public function __construct(array $factories)
33
    {
34 14
        $this->factories = $factories;
35 14
    }
36
37 3
    public function getMoney($data)
38
    {
39 3
        return $this->get('money', $data);
40
    }
41
42
    public function getSums($data)
43
    {
44
        $res = [];
45
        foreach ($data as $key => $value) {
46
            $res[$key] = $value*100;
47
        }
48
49
        return $res;
50
    }
51
52 4
    public function parseMoney($str)
53
    {
54 4
        [$amount, $currency] = explode(' ', $str);
55
56
        return [
57 4
            'amount' => $amount*100,
58 4
            'currency' => $currency,
59
        ];
60
    }
61
62 4
    public function createMoney($data)
63
    {
64 4
        return new Money($data['amount'], new Currency(strtoupper($data['currency'])));
65
    }
66
67 2
    public function getCurrency($data)
68
    {
69 2
        return new Currency($data);
70
    }
71
72 4
    public function getQuantity($data)
73
    {
74 4
        return $this->get('quantity', $data);
75
    }
76
77 5
    public function parseQuantity($str)
78
    {
79 5
        [$quantity, $unit] = explode(' ', $str);
80
81
        return [
82 5
            'quantity' => $quantity,
83 5
            'unit' => $unit,
84
        ];
85
    }
86
87 5
    public function createQuantity($data)
88
    {
89 5
        return Quantity::create($data['unit'], $data['quantity']);
90
    }
91
92
    public function getUnit($data)
93
    {
94
        return $this->get('unit', $data);
95
    }
96
97 1
    public function createUnit($data)
98
    {
99 1
        return Unit::create($data['name']);
100
    }
101
102 4
    public function getType($data)
103
    {
104 4
        return $this->get('type', $data);
105
    }
106
107 4
    public function getTime($data)
108
    {
109 4
        return $this->get('time', $data);
110
    }
111
112 5
    public function createTime($data)
113
    {
114 5
        if (empty($data['date'])) {
115 5
            return new DateTimeImmutable();
116
        }
117
118
        $str = $data['date'];
119 5
        if (!empty($data['timezone'])) {
120
            $str .= ' ' . $data['timezone'];
121
        }
122
123
        return new DateTimeImmutable($str);
124
    }
125
126
    public function getTargets($data)
127
    {
128
        return $this->get('targets', $data);
129
    }
130
131
    public function createTargets($data)
132
    {
133
        $targets = [];
134
        foreach ($data as $one) {
135
            $targets[] = $this->getTarget($one);
136
        }
137 5
138
        return new TargetCollection($targets);
139 5
    }
140
141
    public function getTarget($data)
142 1
    {
143
        return $this->get('target', $data);
144 1
    }
145
146
    public function getAction($data)
147
    {
148
        return $this->get('action', $data);
149
    }
150
151
    public function getCharges($data)
152
    {
153
        $res = [];
154
        foreach ($data as $key => $row) {
155
            $res[$key] = $this->getCharge($row);
156
        }
157
158
        return $res;
159
    }
160
161
    public function getCharge($data)
162 1
    {
163
        return $this->get('charge', $data);
164 1
    }
165
166
    public function getPrice($data)
167 1
    {
168
        return $this->get('price', $data);
169 1
    }
170
171
    public function getBill($data)
172 4
    {
173
        return $this->get('bill', $data);
174 4
    }
175
176
    public function getPlan($data)
177 2
    {
178
        return $this->get('plan', $data);
179 2
    }
180
181
    public function getSale($data)
182 6
    {
183
        return $this->get('sale', $data);
184 6
    }
185
186
    public function getCustomer($data)
187 14
    {
188
        return $this->get('customer', $data);
189 14
    }
190 14
191
    public function get(string $entity, $data)
192
    {
193 14
        if (is_scalar($data)) {
194
            $data = $this->parse($entity, $data);
195 14
        }
196
197 14
        $keys = $this->extractKeys($entity, $data);
198 14
199
        $res = $this->find($entity, $keys) ?: $this->create($entity, $data);
200
201 14
        foreach ($keys as $key) {
202
            $this->entities[$entity][$key] = $res;
203
        }
204 14
205
        return $res;
206 14
    }
207
208 14
    public function parse(string $entity, $str)
209
    {
210
        $method = $this->getMethod($entity, 'parse');
211 12
212
        return $method ? $this->{$method}($str) : $this->parseByUnique($entity, $str);
213 12
    }
214 12
215 10
    public function parseByUnique(string $entity, $str, $delimiter = ':')
216
    {
217 8
        $keys = $this->getEntityUniqueKeys($entity);
218 8
        if (count($keys) === 1) {
219 3
            return [reset($keys) => $str];
220
        }
221 7
        $parts = explode($delimiter, $str, count($keys));
222
        if (count($parts) === count($keys)) {
223 8
            $res = array_combine($keys, $parts);
224
        } else {
225 8
            $res = [];
226
        }
227
        $res['id'] = $str;
228 14
229
        return $res;
230 14
    }
231 14
232 14
    public function find(string $entity, array $keys)
233
    {
234
        foreach ($keys as $key) {
235
            if (!empty($this->entities[$entity][$key])) {
236 14
                return $this->entities[$entity][$key];
237
            }
238
        }
239 14
240
        return null;
241 14
    }
242 14
243 9
    /**
244
     * @return int how many entities were cleaned
245
     */
246 10
    public function clearEntitiesCache(): int
247
    {
248
        $count = count($this->entities);
249
        $this->entities = [];
250 10
251
        return $count;
252 10
    }
253
254
    public function clearEntitiesCacheByType(string $type): int
255 10
    {
256
        $count = count($this->entities[$type] ?? []);
257 10
        $this->entities[$type] = [];
258 10
259
        return $count;
260 10
    }
261 10
262
    public function create(string $entity, $data)
263
    {
264 10
        $method = $this->getMethod($entity, 'create');
265
        if ($method) {
266
            return $this->{$method}($data);
267 10
        }
268
269 10
        if (empty($this->factories[$entity])) {
270
            throw new FactoryNotFoundException($entity);
271
        }
272 10
273
        $factory = $this->factories[$entity];
274 10
275
        return $factory->create($this->createDto($entity, $data));
276
    }
277 10
278
    public function createDto(string $entity, array $data)
279 10
    {
280
        $class = $this->getDtoClass($entity);
281
        $dto = new $class();
282 14
283
        foreach ($data as $key => $value) {
284 14
            $dto->{$key} = $this->prepareValue($entity, $key, $value);
285
        }
286 14
287
        return $dto;
288
    }
289
290
    public function getDtoClass(string $entity)
291
    {
292
        return $this->getEntityClass($entity) . 'CreationDto';
293
    }
294
295
    public function prepareValue($entity, $key, $value)
296
    {
297
        if (is_object($value)) {
298
            return $value;
299
        }
300
        $method = $this->getPrepareMethod($entity, $key);
301
302
        return $method ? $this->{$method}($value) : $value;
303
    }
304
305
    private function getMethod(string $entity, string $op)
306
    {
307
        $method = $op . ucfirst($entity);
308
309
        return method_exists($this, $method) ? $method : null;
310
    }
311
312 10
    private $prepareMethods = [
313
        'action'    => 'getAction',
314 10
        'bill'      => 'getBill',
315 1
        'charge'    => 'getCharge',
316
        'charges'   => 'getCharges',
317 10
        'currency'  => 'getCurrency',
318 3
        'customer'  => 'getCustomer',
319
        'plan'      => 'getPlan',
320
        'prepaid'   => 'getQuantity',
321 10
        'price'     => 'getMoney',
322
        'quantity'  => 'getQuantity',
323
        'sale'      => 'getSale',
324 10
        'seller'    => 'getCustomer',
325
        'sum'       => 'getMoney',
326 10
        'sums'      => 'getSums',
327 10
        'target'    => 'getTarget',
328 10
        'targets'   => 'getTargets',
329 10
        'time'      => 'getTime',
330
        'closeTime' => 'getTime',
331 10
        'type'      => 'getType',
332
        'unit'      => 'getUnit',
333
        'usage'     => 'getQuantity',
334 14
    ];
335
336 14
    private function getPrepareMethod(string $entity, string $key)
337 14
    {
338
        if ($entity === 'charge' && $key === 'price') {
339 14
            return 'getPrice';
340
        }
341
        if ($entity === 'target' && $key === 'type') {
342 14
            return null;
343
        }
344 14
345 14
        return $this->prepareMethods[$key] ?? null;
346 5
    }
347
348
    public function getEntityClass(string $entity)
349 14
    {
350 14
        $parts = explode('\\', __NAMESPACE__);
351 14
        array_pop($parts);
352 8
        $parts[] = $entity;
353
        $parts[] = ucfirst($entity);
354 14
355
        return implode('\\', $parts);
356
    }
357 14
358
    public function extractKeys(string $entity, $data)
359
    {
360
        $id = $data['id'] ?? null;
361
        $unique = $this->extractUnique($entity, $data);
362
363
        return array_filter(['id' => $id, 'unique' => $unique]);
364
    }
365
366
    public function extractUnique(string $entity, $data)
367
    {
368
        $keys = $this->getEntityUniqueKeys($entity);
369
        if (empty($keys)) {
370
            return null;
371
        }
372
373
        $values = [];
374
        foreach ($keys as $key) {
375
            if (empty($data[$key])) {
376
                return null;
377 14
            }
378
            $value = $data[$key];
379 14
            if (is_array($value)) {
380
                $value = $this->extractUnique($key, $value);
381 14
            }
382
            $values[$key] = $value;
383
        }
384
385 14
        return implode(' ', $values);
386
    }
387
388
    private $uniqueKeys = [
389
        'action'    => [],
390
        'bill'      => [],
391
        'charge'    => [],
392
        'customer'  => ['login'],
393
        'seller'    => ['login'],
394
        'money'     => ['amount', 'currency'],
395
        'plan'      => ['name', 'seller'],
396
        'price'     => [],
397
        'quantity'  => ['quantity', 'unit'],
398
        'sale'      => [],
399
        'targets'   => [],
400
        'target'    => ['type', 'name'],
401
        'time'      => ['date'],
402
        'type'      => ['name'],
403
        'unit'      => ['name'],
404
    ];
405
406
    public function getEntityUniqueKeys(string $entity): array
407
    {
408
        $keys = $this->uniqueKeys[$entity] ?? null;
409
410
        if (is_null($keys)) {
411
            throw new UnknownEntityException($entity);
412
        }
413
414
        return $keys;
415
    }
416
}
417