Completed
Push — master ( ecaab3...bef2d0 )
by Patrick
03:32
created

Engine::addTest()   B

Complexity

Conditions 5
Paths 6

Size

Total Lines 25
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 11
CRAP Score 5

Importance

Changes 4
Bugs 2 Features 0
Metric Value
c 4
b 2
f 0
dl 0
loc 25
ccs 11
cts 11
cp 1
rs 8.439
cc 5
eloc 14
nc 6
nop 4
crap 5
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);
0 ignored issues
show
Bug introduced by
It seems like $testParticipation defined by $this->participationMana...$test->getIdentifier()) on line 183 can also be of type boolean; however, PhpAb\Test\TestInterface::getVariant() does only seem to accept string, maybe add an additional type check?

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:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
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