1
|
|
|
<?php |
2
|
|
|
/** |
3
|
|
|
* This file is part of phpab/phpab. (https://github.com/phpab/phpab) |
4
|
|
|
* |
5
|
|
|
* @link https://github.com/phpab/phpab for the canonical source repository |
6
|
|
|
* @copyright Copyright (c) 2015-2016 phpab. (https://github.com/phpab/) |
7
|
|
|
* @license https://raw.githubusercontent.com/phpab/phpab/master/LICENSE.md MIT |
8
|
|
|
*/ |
9
|
|
|
|
10
|
|
|
namespace PhpAb\Engine; |
11
|
|
|
|
12
|
|
|
use PhpAb\Event\DispatcherInterface; |
13
|
|
|
use PhpAb\Exception\EngineLockedException; |
14
|
|
|
use PhpAb\Exception\TestCollisionException; |
15
|
|
|
use PhpAb\Exception\TestNotFoundException; |
16
|
|
|
use PhpAb\Participation\FilterInterface; |
17
|
|
|
use PhpAb\Participation\ParticipationManagerInterface; |
18
|
|
|
use PhpAb\Test\Bag; |
19
|
|
|
use PhpAb\Variant; |
20
|
|
|
use PhpAb\Test\TestInterface; |
21
|
|
|
use PhpAb\Variant\ChooserInterface; |
22
|
|
|
use Webmozart\Assert\Assert; |
23
|
|
|
|
24
|
|
|
/** |
25
|
|
|
* The engine used to start tests. |
26
|
|
|
* |
27
|
|
|
* @package PhpAb |
28
|
|
|
*/ |
29
|
|
|
class Engine implements EngineInterface |
30
|
|
|
{ |
31
|
|
|
/** |
32
|
|
|
* A list with test bags. |
33
|
|
|
* |
34
|
|
|
* @var Bag[] |
35
|
|
|
*/ |
36
|
|
|
public $tests = []; |
37
|
|
|
|
38
|
|
|
/** |
39
|
|
|
* The participation manager used to check if a user particiaptes. |
40
|
|
|
* |
41
|
|
|
* @var ParticipationManagerInterface |
42
|
|
|
*/ |
43
|
|
|
private $participationManager; |
44
|
|
|
|
45
|
|
|
/** |
46
|
|
|
* The event dispatcher that dispatches events related to tests. |
47
|
|
|
* |
48
|
|
|
* @var DispatcherInterface |
49
|
|
|
*/ |
50
|
|
|
private $dispatcher; |
51
|
|
|
|
52
|
|
|
/** |
53
|
|
|
* The default filter that is used when a test bag has no filter set. |
54
|
|
|
* |
55
|
|
|
* @var FilterInterface |
56
|
|
|
*/ |
57
|
|
|
private $filter; |
58
|
|
|
|
59
|
|
|
/** |
60
|
|
|
* The default variant chooser that is used when a test bag has no variant chooser set. |
61
|
|
|
* |
62
|
|
|
* @var ChooserInterface |
63
|
|
|
*/ |
64
|
|
|
private $chooser; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* Locks the engine for further manipulaton |
68
|
|
|
* |
69
|
|
|
* @var boolean |
70
|
|
|
*/ |
71
|
|
|
private $locked = false; |
72
|
|
|
|
73
|
|
|
/** |
74
|
|
|
* Initializes a new instance of this class. |
75
|
|
|
* |
76
|
|
|
* @param ParticipationManagerInterface $participationManager Handles the Participation state |
77
|
|
|
* @param DispatcherInterface $dispatcher Dispatches events |
78
|
|
|
* @param FilterInterface|null $filter The default filter to use if no filter is provided for the test. |
79
|
|
|
* @param ChooserInterface|null $chooser The default chooser to use if no chooser is provided for the test. |
80
|
|
|
*/ |
81
|
17 |
|
public function __construct( |
82
|
|
|
ParticipationManagerInterface $participationManager, |
83
|
|
|
DispatcherInterface $dispatcher, |
84
|
|
|
FilterInterface $filter = null, |
85
|
|
|
ChooserInterface $chooser = null |
86
|
|
|
) { |
87
|
|
|
|
88
|
17 |
|
$this->participationManager = $participationManager; |
89
|
17 |
|
$this->dispatcher = $dispatcher; |
90
|
17 |
|
$this->filter = $filter; |
91
|
17 |
|
$this->chooser = $chooser; |
92
|
17 |
|
} |
93
|
|
|
|
94
|
|
|
/** |
95
|
|
|
* {@inheritDoc} |
96
|
|
|
*/ |
97
|
1 |
|
public function getTests() |
98
|
|
|
{ |
99
|
1 |
|
$tests = []; |
100
|
1 |
|
foreach ($this->tests as $bag) { |
101
|
1 |
|
$tests[] = $bag->getTest(); |
102
|
1 |
|
} |
103
|
|
|
|
104
|
1 |
|
return $tests; |
105
|
|
|
} |
106
|
|
|
|
107
|
|
|
/** |
108
|
|
|
* {@inheritDoc} |
109
|
|
|
* |
110
|
|
|
* @param string $test The identifier of the test |
111
|
|
|
*/ |
112
|
2 |
|
public function getTest($test) |
113
|
|
|
{ |
114
|
2 |
|
if (! isset($this->tests[$test])) { |
115
|
1 |
|
throw new TestNotFoundException('No test with identifier '.$test.' found'); |
116
|
|
|
} |
117
|
|
|
|
118
|
1 |
|
return $this->tests[$test]->getTest(); |
119
|
|
|
} |
120
|
|
|
|
121
|
|
|
/** |
122
|
|
|
* {@inheritDoc} |
123
|
|
|
* |
124
|
|
|
* @param TestInterface $test |
125
|
|
|
* @param array $options |
126
|
|
|
* @param FilterInterface $filter |
127
|
|
|
* @param ChooserInterface $chooser |
128
|
|
|
*/ |
129
|
14 |
|
public function addTest( |
130
|
|
|
TestInterface $test, |
131
|
|
|
$options = [], |
132
|
|
|
FilterInterface $filter = null, |
133
|
|
|
ChooserInterface $chooser = null |
134
|
|
|
) { |
135
|
|
|
|
136
|
14 |
|
if ($this->locked) { |
137
|
1 |
|
throw new EngineLockedException('The engine has been processed already. You cannot add other tests.'); |
138
|
|
|
} |
139
|
|
|
|
140
|
13 |
|
if (isset($this->tests[$test->getIdentifier()])) { |
141
|
1 |
|
throw new TestCollisionException('Duplicate test for identifier '.$test->getIdentifier()); |
142
|
|
|
} |
143
|
|
|
|
144
|
|
|
// If no filter/chooser is set use the ones from |
145
|
|
|
// the engine. |
146
|
13 |
|
$filter = $filter ? $filter : $this->filter; |
147
|
13 |
|
$chooser = $chooser ? $chooser : $this->chooser; |
148
|
|
|
|
149
|
13 |
|
Assert::notNull($filter, 'There must be at least one filter in the Engine or in the TestBag'); |
150
|
12 |
|
Assert::notNull($chooser, 'There must be at least one chooser in the Engine or in the TestBag'); |
151
|
|
|
|
152
|
11 |
|
$this->tests[$test->getIdentifier()] = new Bag($test, $filter, $chooser, $options); |
153
|
11 |
|
} |
154
|
|
|
|
155
|
|
|
/** |
156
|
|
|
* {@inheritDoc} |
157
|
|
|
*/ |
158
|
12 |
|
public function start() |
159
|
|
|
{ |
160
|
|
|
// Check if already locked |
161
|
12 |
|
if ($this->locked) { |
162
|
1 |
|
throw new EngineLockedException('The engine is already locked and could not be started once again.'); |
163
|
|
|
} |
164
|
|
|
|
165
|
|
|
// Lock the engine for further manipulation |
166
|
12 |
|
$this->locked = true; |
167
|
|
|
|
168
|
12 |
|
foreach ($this->tests as $testBag) { |
169
|
9 |
|
$this->handleTestBag($testBag); |
170
|
12 |
|
} |
171
|
12 |
|
} |
172
|
|
|
|
173
|
|
|
/** |
174
|
|
|
* Process the test bag |
175
|
|
|
* |
176
|
|
|
* @param Bag $bag |
177
|
|
|
* |
178
|
|
|
* @return bool true if the variant got executed, false otherwise |
179
|
|
|
*/ |
180
|
9 |
|
private function handleTestBag(Bag $bag) |
181
|
|
|
{ |
182
|
9 |
|
$test = $bag->getTest(); |
183
|
9 |
|
$testParticipation = $this->participationManager->participates($test->getIdentifier()); |
184
|
|
|
|
185
|
9 |
|
if (null === $testParticipation) { |
186
|
|
|
// is marked as "do not participate" |
187
|
1 |
|
$this->dispatcher->dispatch('phpab.participation.blocked', [$this, $bag]); |
188
|
|
|
|
189
|
1 |
|
return false; |
190
|
|
|
} |
191
|
|
|
|
192
|
8 |
|
if (false === $testParticipation) { |
193
|
|
|
// The user does not participate at the test |
194
|
|
|
// let him participate |
195
|
4 |
|
if (! $bag->getParticipationFilter()->shouldParticipate()) { |
196
|
|
|
// The user should not participate so let's set participation |
197
|
|
|
// to null so he will not participate in the future, too. |
198
|
2 |
|
$this->dispatcher->dispatch('phpab.participation.block', [$this, $bag]); |
199
|
|
|
|
200
|
2 |
|
$this->participationManager->participate($test->getIdentifier(), null); |
201
|
|
|
|
202
|
2 |
|
return false; |
203
|
|
|
} |
204
|
2 |
|
} |
205
|
|
|
|
206
|
|
|
// Let's try to recover a previously stored Variant |
207
|
6 |
|
if ($testParticipation) { |
208
|
4 |
|
$variant = $bag->getTest()->getVariant($testParticipation); |
|
|
|
|
209
|
|
|
// If we managed to identify a Variant by a previously stored |
210
|
|
|
// participation, do its magic again |
211
|
4 |
|
if ($variant instanceof Variant\VariantInterface) { |
212
|
2 |
|
$this->dispatcher->dispatch('phpab.participation.variant_run', [$this, $bag, $variant]); |
213
|
2 |
|
$variant->run(); |
214
|
|
|
|
215
|
2 |
|
return true; |
216
|
|
|
} |
217
|
2 |
|
} |
218
|
|
|
|
219
|
|
|
// Choose a variant for later usage. |
220
|
|
|
// If the user should participate this one will be used |
221
|
4 |
|
$chosen = $bag->getVariantChooser()->chooseVariant($test->getVariants()); |
222
|
|
|
|
223
|
4 |
|
if (null === $chosen || !$test->getVariant($chosen->getIdentifier())) { |
224
|
|
|
// The user has a stored participation, but it does not exist any more |
225
|
3 |
|
$this->dispatcher->dispatch('phpab.participation.variant_missing', [$this, $bag]); |
226
|
3 |
|
$this->participationManager->participate($test->getIdentifier(), null); |
227
|
|
|
|
228
|
3 |
|
return false; |
229
|
|
|
} |
230
|
|
|
|
231
|
|
|
// Store the chosen variant so he will not switch between different states |
232
|
1 |
|
$this->participationManager->participate($test->getIdentifier(), $chosen->getIdentifier()); |
233
|
|
|
|
234
|
1 |
|
$this->dispatcher->dispatch('phpab.participation.variant_run', [$this, $bag, $chosen]); |
235
|
1 |
|
$chosen->run(); |
236
|
|
|
|
237
|
1 |
|
return true; |
238
|
|
|
} |
239
|
|
|
} |
240
|
|
|
|
If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:
If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.