Completed
Push — master ( d2937f...2e2dbb )
by Dmitry
04:27
created

FormulaEngine   A

Complexity

Total Complexity 27

Size/Duplication

Total Lines 164
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 8

Test Coverage

Coverage 86.15%

Importance

Changes 0
Metric Value
wmc 27
lcom 1
cbo 8
dl 0
loc 164
ccs 56
cts 65
cp 0.8615
rs 10
c 0
b 0
f 0

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 6 2
B build() 0 19 5
A interpret() 0 13 4
A normalize() 0 15 3
A validate() 0 9 2
A getRuler() 0 9 2
A getAsserter() 0 8 2
A getContext() 0 8 2
A buildContext() 0 8 1
A getDiscount() 0 8 2
A getLeasing() 0 8 2
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\formula;
12
13
use hiqdev\php\billing\charge\ChargeModifier;
14
use hiqdev\php\billing\charge\modifiers\Discount;
15
use hiqdev\php\billing\charge\modifiers\Leasing;
16
use Hoa\Ruler\Context;
17
use Hoa\Ruler\Model;
18
use Hoa\Ruler\Ruler;
19
use Hoa\Visitor\Visit;
20
21
/**
22
 * @author Andrii Vasyliev <[email protected]>
23
 */
24
class FormulaEngine implements FormulaEngineInterface
25
{
26
    /**
27
     * @var Ruler
28
     */
29
    protected $ruler;
30
31
    /**
32
     * @var Visit|Asserter
33
     */
34
    protected $asserter;
35
36
    /**
37
     * @var Context
38
     */
39
    protected $context;
40
41
    /**
42
     * @var ChargeModifier
43
     */
44
    protected $discount;
45
46
    /**
47
     * @var ChargeModifier
48
     */
49
    protected $leasing;
50
51 14
    public function __construct()
52
    {
53 14
        if (!class_exists(Context::class)) {
54
            throw new \Exception('to use formula engine install `hoa/ruler`');
55
        }
56 14
    }
57
58
    /**
59
     * @param string $formula
60
     * @return ChargeModifier
61
     */
62 6
    public function build(string $formula): ChargeModifier
63
    {
64
        try {
65 6
            $model = $this->interpret($formula);
66 5
            $result = $this->getRuler()->assert($model, $this->getContext());
67 1
        } catch (FormulaEngineException $e) {
68 1
            throw $e;
69
        } catch (\Hoa\Ruler\Exception\Asserter $e) {
70
            throw FormulaRuntimeError::fromException($e, $formula);
71
        } catch (\Exception $exception) {
72
            throw FormulaRuntimeError::create($formula, 'Formula run failed');
73
        }
74
75 5
        if (!$result instanceof ChargeModifier) {
76 1
            throw FormulaRuntimeError::create($formula, 'Formula run returned unexpected result');
77
        }
78
79 4
        return $result;
80
    }
81
82
    /**
83
     * @param string $formula
84
     * @return Model\Model
85
     * @throws
86
     */
87 6
    public function interpret(string $formula): Model\Model
88
    {
89
        try {
90 6
            $rule = str_replace("\n", ' AND ', $this->normalize($formula));
91
            return $this->getRuler()->interpret($rule);
92 6
        } catch (\Hoa\Compiler\Exception\Exception $exception) {
93 1
            throw FormulaSyntaxError::fromException($exception, $formula);
94 1
        } catch (\Hoa\Ruler\Exception\Interpreter $exception) {
95
            throw FormulaSyntaxError::fromException($exception, $formula);
96
        } catch (\Throwable $exception) {
97
            throw FormulaSyntaxError::create($formula, 'Failed to interpret formula');
98
        }
99
    }
100
101
    public function normalize(string $formula): ?string
102 14
    {
103
        $lines = explode("\n", $formula);
104 14
        $normalized = array_map(function ($value) {
105
            $value = trim($value);
106 14
            if (strlen($value) === 0) {
107 14
                return null;
108 5
            }
109
110
            return $value;
111 10
        }, $lines);
112 14
        $cleared = array_filter($normalized);
113 14
114
        return empty($cleared) ? null : implode("\n", $cleared);
115 14
    }
116
117
    /**
118
     * Validates $formula
119
     *
120
     * @param string $formula
121
     * @return null|string `null` when formula has no errors or string error message
122
     */
123
    public function validate(string $formula): ?string
124 4
    {
125
        try {
126
            $this->build($formula);
127 4
            return null;
128
        } catch (FormulaEngineException $e) {
129 2
            return $e->getMessage();
130 2
        }
131 2
    }
132
133
    public function getRuler(): Ruler
134
    {
135 6
        if ($this->ruler === null) {
136
            $this->ruler = new Ruler();
137 6
            $this->ruler->setAsserter($this->getAsserter());
138 6
        }
139 6
140
        return $this->ruler;
141
    }
142 6
143
    public function getAsserter(): Visit
144
    {
145 6
        if ($this->asserter === null) {
146
            $this->asserter = new Asserter();
147 6
        }
148 6
149
        return $this->asserter;
150
    }
151 6
152
    public function getContext(): Context
153
    {
154 5
        if ($this->context === null) {
155
            $this->context = $this->buildContext();
156 5
        }
157 5
158
        return $this->context;
159
    }
160 5
161
    protected function buildContext(): Context
162
    {
163 5
        $context = new Context();
164
        $context['discount'] = $this->getDiscount();
165 5
        $context['leasing'] = $this->getLeasing();
166 5
167 5
        return $context;
168
    }
169 5
170
    public function getDiscount(): ChargeModifier
171
    {
172 5
        if ($this->discount === null) {
173
            $this->discount = new Discount();
174 5
        }
175 5
176
        return $this->discount;
177
    }
178 5
179
    public function getLeasing(): ChargeModifier
180
    {
181 5
        if ($this->leasing === null) {
182
            $this->leasing = new Leasing();
183 5
        }
184 5
185
        return $this->leasing;
186
    }
187
}
188