Completed
Push — master ( f79ff7...908aca )
by Dmitry
15:25 queued 10:35
created

src/formula/FormulaEngine.php (1 issue)

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 (FormulaSemanticsError $e) {
68
            throw FormulaSemanticsError::fromException($e, $formula);
69 1
        } catch (FormulaEngineException $e) {
70 1
            throw $e;
71
        } catch (\Hoa\Ruler\Exception\Asserter $e) {
72
            throw FormulaRuntimeError::fromException($e, $formula);
73
        } catch (\Exception $e) {
74
            throw FormulaRuntimeError::fromException($e, $formula, 'Formula run failed');
75
        }
76
77 5
        if (!$result instanceof ChargeModifier) {
0 ignored issues
show
$result is never a sub-type of hiqdev\php\billing\charge\ChargeModifier.
Loading history...
78 1
            throw FormulaRuntimeError::create($formula, 'Formula run returned unexpected result');
79
        }
80
81 4
        return $result;
82
    }
83
84
    /**
85
     * @param string $formula
86
     * @throws FormulaSyntaxError
87
     * @return Model\Model
88
     */
89 6
    public function interpret(string $formula): Model\Model
90
    {
91
        try {
92 6
            $rule = str_replace("\n", ' AND ', $this->normalize($formula));
93
94 6
            return $this->getRuler()->interpret($rule);
95 1
        } catch (\Hoa\Compiler\Exception\Exception $exception) {
96 1
            throw FormulaSyntaxError::fromException($exception, $formula);
97
        } catch (\Hoa\Ruler\Exception\Interpreter $exception) {
98
            throw FormulaSyntaxError::fromException($exception, $formula);
99
        } catch (\Throwable $exception) {
100
            throw FormulaSyntaxError::create($formula, 'Failed to interpret formula');
101
        }
102
    }
103
104 14
    public function normalize(string $formula): ?string
105
    {
106 14
        $lines = explode("\n", $formula);
107
        $normalized = array_map(function ($value) {
108 14
            $value = trim($value);
109 14
            if (strlen($value) === 0) {
110 5
                return null;
111
            }
112
113 10
            return $value;
114 14
        }, $lines);
115 14
        $cleared = array_filter($normalized);
116
117 14
        return empty($cleared) ? null : implode("\n", $cleared);
118
    }
119
120
    /**
121
     * Validates $formula.
122
     *
123
     * @param string $formula
124
     * @return null|string `null` when formula has no errors or string error message
125
     */
126 4
    public function validate(string $formula): ?string
127
    {
128
        try {
129 4
            $this->build($formula);
130
131 2
            return null;
132 2
        } catch (FormulaEngineException $e) {
133 2
            return $e->getMessage();
134
        }
135
    }
136
137 6
    public function getRuler(): Ruler
138
    {
139 6
        if ($this->ruler === null) {
140 6
            $this->ruler = new Ruler();
141 6
            $this->ruler->setAsserter($this->getAsserter());
142
        }
143
144 6
        return $this->ruler;
145
    }
146
147 6
    public function getAsserter(): Visit
148
    {
149 6
        if ($this->asserter === null) {
150 6
            $this->asserter = new Asserter();
151
        }
152
153 6
        return $this->asserter;
154
    }
155
156 5
    public function getContext(): Context
157
    {
158 5
        if ($this->context === null) {
159 5
            $this->context = $this->buildContext();
160
        }
161
162 5
        return $this->context;
163
    }
164
165 5
    protected function buildContext(): Context
166
    {
167 5
        $context = new Context();
168 5
        $context['discount'] = $this->getDiscount();
169 5
        $context['leasing'] = $this->getLeasing();
170
171 5
        return $context;
172
    }
173
174 5
    public function getDiscount(): ChargeModifier
175
    {
176 5
        if ($this->discount === null) {
177 5
            $this->discount = new Discount();
178
        }
179
180 5
        return $this->discount;
181
    }
182
183 5
    public function getLeasing(): ChargeModifier
184
    {
185 5
        if ($this->leasing === null) {
186 5
            $this->leasing = new Leasing();
187
        }
188
189 5
        return $this->leasing;
190
    }
191
}
192