Completed
Push — master ( ce2d89...6a70b2 )
by Walter
06:01 queued 03:52
created

Engine::activateVariant()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 6
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 3
nc 1
nop 2
crap 1
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\Filter\FilterInterface;
17
use PhpAb\Participation\ManagerInterface;
18
use PhpAb\Test\Bag;
19
use PhpAb\Variant;
20
use PhpAb\Test\TestInterface;
21
use PhpAb\Variant\Chooser\ChooserInterface;
22
use PhpAb\Variant\VariantInterface;
23
use Webmozart\Assert\Assert;
24
25
/**
26
 * The engine used to start tests.
27
 *
28
 * @package PhpAb
29
 */
30
class Engine implements EngineInterface
31
{
32
    /**
33
     * A list with test bags.
34
     *
35
     * @var Bag[]
36
     */
37
    public $tests = [];
38
39
    /**
40
     * The participation manager used to check if a user particiaptes.
41
     *
42
     * @var ManagerInterface
43
     */
44
    private $participationManager;
45
46
    /**
47
     * The event dispatcher that dispatches events related to tests.
48
     *
49
     * @var DispatcherInterface
50
     */
51
    private $dispatcher;
52
53
    /**
54
     * The default filter that is used when a test bag has no filter set.
55
     *
56
     * @var FilterInterface
57
     */
58
    private $filter;
59
60
    /**
61
     * The default variant chooser that is used when a test bag has no variant chooser set.
62
     *
63
     * @var ChooserInterface
64
     */
65
    private $chooser;
66
67
    /**
68
     * Locks the engine for further manipulaton
69
     *
70
     * @var boolean
71
     */
72
    private $locked = false;
73
74
    /**
75
     * Initializes a new instance of this class.
76
     *
77
     * @param ManagerInterface $participationManager Handles the Participation state
78
     * @param DispatcherInterface $dispatcher Dispatches events
79
     * @param FilterInterface|null $filter The default filter to use if no filter is provided for the test.
80
     * @param ChooserInterface|null $chooser The default chooser to use if no chooser is provided for the test.
81
     */
82 17
    public function __construct(
83
        ManagerInterface $participationManager,
84
        DispatcherInterface $dispatcher,
85
        FilterInterface $filter = null,
86
        ChooserInterface $chooser = null
87
    ) {
88
89 17
        $this->participationManager = $participationManager;
90 17
        $this->dispatcher = $dispatcher;
91 17
        $this->filter = $filter;
92 17
        $this->chooser = $chooser;
93 17
    }
94
95
    /**
96
     * {@inheritDoc}
97
     */
98 1
    public function getTests()
99
    {
100 1
        $tests = [];
101 1
        foreach ($this->tests as $bag) {
102 1
            $tests[] = $bag->getTest();
103 1
        }
104
105 1
        return $tests;
106
    }
107
108
    /**
109
     * {@inheritDoc}
110
     *
111
     * @param string $test The identifier of the test
112
     */
113 2
    public function getTest($test)
114
    {
115 2
        if (! isset($this->tests[$test])) {
116 1
            throw new TestNotFoundException('No test with identifier '.$test.' found');
117
        }
118
119 1
        return $this->tests[$test]->getTest();
120
    }
121
122
    /**
123
     * {@inheritDoc}
124
     *
125
     * @param TestInterface $test
126
     * @param array $options
127
     * @param FilterInterface $filter
128
     * @param ChooserInterface $chooser
129
     */
130 14
    public function addTest(
131
        TestInterface $test,
132
        $options = [],
133
        FilterInterface $filter = null,
134
        ChooserInterface $chooser = null
135
    ) {
136
137 14
        if ($this->locked) {
138 1
            throw new EngineLockedException('The engine has been processed already. You cannot add other tests.');
139
        }
140
141 13
        if (isset($this->tests[$test->getIdentifier()])) {
142 1
            throw new TestCollisionException('Duplicate test for identifier '.$test->getIdentifier());
143
        }
144
145
        // If no filter/chooser is set use the ones from
146
        // the engine.
147 13
        $filter = $filter ? $filter : $this->filter;
148 13
        $chooser = $chooser ? $chooser : $this->chooser;
149
150 13
        Assert::notNull($filter, 'There must be at least one filter in the Engine or in the TestBag');
151 12
        Assert::notNull($chooser, 'There must be at least one chooser in the Engine or in the TestBag');
152
153 11
        $this->tests[$test->getIdentifier()] = new Bag($test, $filter, $chooser, $options);
154 11
    }
155
156
    /**
157
     * {@inheritDoc}
158
     */
159 12
    public function start()
160
    {
161
        // Check if already locked
162 12
        if ($this->locked) {
163 1
            throw new EngineLockedException('The engine is already locked and could not be started once again.');
164
        }
165
166
        // Lock the engine for further manipulation
167 12
        $this->locked = true;
168
169 12
        foreach ($this->tests as $testBag) {
170 9
            $this->handleTestBag($testBag);
171 12
        }
172 12
    }
173
174
    /**
175
     * Process the test bag
176
     *
177
     * @param Bag $bag
178
     *
179
     * @return bool true if the variant got executed, false otherwise
180
     */
181 9
    private function handleTestBag(Bag $bag)
182
    {
183 9
        $test = $bag->getTest();
184
185 9
        $isParticipating = $this->participationManager->participates($test->getIdentifier());
186 9
        $testParticipation = $this->participationManager->getParticipatingVariant($test->getIdentifier());
187
188
        // Check if the user is marked as "do not participate".
189 9
        if ($isParticipating && null === $testParticipation) {
190 1
            $this->dispatcher->dispatch('phpab.participation.blocked', [$this, $bag]);
191 1
            return;
192
        }
193
194
        // When the user does not participate at the test, let him participate.
195 8
        if (!$isParticipating && !$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 2
            return;
202
        }
203
204
        // Let's try to recover a previously stored Variant
205 6
        if ($isParticipating && $testParticipation !== null) {
206 3
            $variant = $bag->getTest()->getVariant($testParticipation);
207
208
            // If we managed to identify a Variant by a previously stored participation, do its magic again.
209 3
            if ($variant instanceof VariantInterface) {
210 2
                $this->activateVariant($bag, $variant);
211 2
                return;
212
            }
213 1
        }
214
215
        // Choose a variant for later usage. If the user should participate this one will be used
216 4
        $chosen = $bag->getVariantChooser()->chooseVariant($test->getVariants());
217
218
        // Check if user participation should be blocked. Or maybe the variant does not exists anymore?
219 4
        if (null === $chosen || !$test->getVariant($chosen->getIdentifier())) {
220 3
            $this->dispatcher->dispatch('phpab.participation.variant_missing', [$this, $bag]);
221
222 3
            $this->participationManager->participate($test->getIdentifier(), null);
223 3
            return;
224
        }
225
226
        // Store the chosen variant so he will not switch between different states
227 1
        $this->participationManager->participate($test->getIdentifier(), $chosen->getIdentifier());
228
229 1
        $this->activateVariant($bag, $chosen);
230 1
    }
231
232 3
    private function activateVariant(Bag $bag, VariantInterface $variant)
233
    {
234 3
        $this->dispatcher->dispatch('phpab.participation.variant_run', [$this, $bag, $variant]);
235
236 3
        $variant->run();
237 3
    }
238
}
239