Completed
Push — master ( fcb9c9...e84ec2 )
by Dmitry
03:36
created

FormulaEngine::setAsserter()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 4
nc 2
nop 1
dl 0
loc 8
ccs 0
cts 5
cp 0
crap 6
rs 10
c 0
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\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
use Psr\SimpleCache\CacheInterface;
21
22
/**
23
 * @author Andrii Vasyliev <[email protected]>
24
 */
25
class FormulaEngine implements FormulaEngineInterface
26
{
27
    /**
28
     * @var Ruler
29
     */
30
    protected $ruler;
31
32
    /**
33
     * @var Visit|Asserter
34
     */
35
    protected $asserter;
36
37
    /**
38
     * @var Context
39
     */
40
    protected $context;
41
42
    /**
43
     * @var ChargeModifier
44
     */
45
    protected $discount;
46
47
    /**
48
     * @var ChargeModifier
49
     */
50
    protected $leasing;
51
    /**
52
     * @var CacheInterface
53
     */
54
    private $cache;
55
56
    public function __construct(CacheInterface $cache)
57
    {
58
        if (!class_exists(Context::class)) {
59
            throw new \Exception('to use formula engine install `hoa/ruler`');
60
        }
61
62
        $this->cache = $cache;
63
    }
64
65
    /**
66
     * @param string $formula
67
     * @return ChargeModifier
68
     */
69
    public function build(string $formula): ChargeModifier
70
    {
71
        try {
72
            $model = $this->interpret($formula);
73
            $result = $this->getRuler()->assert($model, $this->getContext());
74
        } catch (FormulaSemanticsError $e) {
75
            throw FormulaSemanticsError::fromException($e, $formula);
76
        } catch (FormulaEngineException $e) {
77
            throw $e;
78
        } catch (\Hoa\Ruler\Exception\Asserter $e) {
79
            throw FormulaRuntimeError::fromException($e, $formula);
80
        } catch (\Exception $e) {
81
            throw FormulaRuntimeError::fromException($e, $formula, 'Formula run failed');
82
        }
83
84
        if (!$result instanceof ChargeModifier) {
0 ignored issues
show
introduced by
$result is never a sub-type of hiqdev\php\billing\charge\ChargeModifier.
Loading history...
85
            throw FormulaRuntimeError::create($formula, 'Formula run returned unexpected result');
86
        }
87
88
        return $result;
89
    }
90
91
    /**
92
     * @param string $formula
93
     * @return Model\Model
94
     * @throws FormulaEngineException
95
     */
96
    public function interpret(string $formula): Model\Model
97
    {
98
        try {
99
            $rule = str_replace("\n", ' AND ', $this->normalize($formula));
100
101
            $key = md5(__METHOD__ . $rule);
102
            $model = $this->cache->get($key);
103
            if ($model === null) {
104
                $model = $this->getRuler()->interpret($rule);
105
                $this->cache->set($key, $model);
106
            }
107
108
            return $model;
109
        } catch (\Hoa\Compiler\Exception\Exception $exception) {
110
            throw FormulaSyntaxError::fromException($exception, $formula);
111
        } catch (\Hoa\Ruler\Exception\Interpreter $exception) {
112
            throw FormulaSyntaxError::fromException($exception, $formula);
113
        } catch (\Throwable $exception) {
114
            throw FormulaSyntaxError::create($formula, 'Failed to interpret formula');
115
        }
116
    }
117
118
    public function normalize(string $formula): ?string
119
    {
120
        $lines = explode("\n", $formula);
121
        $normalized = array_map(function ($value) {
122
            $value = trim($value);
123
            if ('' === $value) {
124
                return null;
125
            }
126
127
            return $value;
128
        }, $lines);
129
        $cleared = array_filter($normalized);
130
131
        return empty($cleared) ? null : implode("\n", $cleared);
132
    }
133
134
    /**
135
     * Validates $formula.
136
     *
137
     * @param string $formula
138
     * @return null|string `null` when formula has no errors or string error message
139
     */
140
    public function validate(string $formula): ?string
141
    {
142
        try {
143
            $this->build($formula);
144
145
            return null;
146
        } catch (FormulaEngineException $e) {
147
            return $e->getMessage();
148
        }
149
    }
150
151
    public function getRuler(): Ruler
152
    {
153
        if ($this->ruler === null) {
154
            $this->ruler = new Ruler();
155
            $this->ruler->setAsserter($this->getAsserter());
156
        }
157
158
        return $this->ruler;
159
    }
160
161
    public function setAsserter(Visit $asserter): self
162
    {
163
        $this->asserter = $asserter;
164
        if ($this->ruler !== null) {
165
            $this->ruler->setAsserter($asserter);
166
        }
167
168
        return $this;
169
    }
170
171
    public function getAsserter(): Visit
172
    {
173
        if ($this->asserter === null) {
174
            $this->asserter = new Asserter();
175
        }
176
177
        return $this->asserter;
178
    }
179
180
    public function getContext(): Context
181
    {
182
        if ($this->context === null) {
183
            $this->context = $this->buildContext();
184
        }
185
186
        return $this->context;
187
    }
188
189
    protected function buildContext(): Context
190
    {
191
        $context = new Context();
192
        $context['discount'] = $this->getDiscount();
193
        $context['leasing'] = $this->getLeasing();
194
195
        return $context;
196
    }
197
198
    public function getDiscount(): ChargeModifier
199
    {
200
        if ($this->discount === null) {
201
            $this->discount = new Discount();
202
        }
203
204
        return $this->discount;
205
    }
206
207
    public function getLeasing(): ChargeModifier
208
    {
209
        if ($this->leasing === null) {
210
            $this->leasing = new Leasing();
211
        }
212
213
        return $this->leasing;
214
    }
215
}
216