AbTesting::completeGoal()   A
last analyzed

Complexity

Conditions 4
Paths 6

Size

Total Lines 23

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 4

Importance

Changes 0
Metric Value
dl 0
loc 23
ccs 9
cts 9
cp 1
rs 9.552
c 0
b 0
f 0
cc 4
nc 6
nop 1
crap 4
1
<?php
2
3
namespace Ben182\AbTesting;
4
5
use Ben182\AbTesting\Models\Goal;
6
use Illuminate\Support\Collection;
7
use Ben182\AbTesting\Models\Experiment;
8
use Ben182\AbTesting\Events\GoalCompleted;
9
use Jaybizzle\CrawlerDetect\CrawlerDetect;
10
use Ben182\AbTesting\Events\ExperimentNewVisitor;
11
use Ben182\AbTesting\Exceptions\InvalidConfiguration;
12
13
class AbTesting
14
{
15
    protected $experiments;
16
17
    const SESSION_KEY_EXPERIMENT = 'ab_testing_experiment';
18
    const SESSION_KEY_GOALS = 'ab_testing_goals';
19 51
20
    public function __construct()
21 51
    {
22 51
        $this->experiments = new Collection;
23
    }
24
25
    /**
26
     * Validates the config items and puts them into models.
27
     *
28
     * @return void
29 51
     */
30
    protected function start()
31 51
    {
32 51
        $configExperiments = config('ab-testing.experiments');
33
        $configGoals = config('ab-testing.goals');
34 51
35 3
        if (! count($configExperiments)) {
36
            throw InvalidConfiguration::noExperiment();
37
        }
38 48
39 3
        if (count($configExperiments) !== count(array_unique($configExperiments))) {
40
            throw InvalidConfiguration::experiment();
41
        }
42 45
43 3
        if (count($configGoals) !== count(array_unique($configGoals))) {
44
            throw InvalidConfiguration::goal();
45
        }
46 42
47 42
        foreach ($configExperiments as $configExperiment) {
48 42
            $this->experiments[] = $experiment = Experiment::firstOrCreate([
49
                'name' => $configExperiment,
50 42
            ], [
51
                'visitors' => 0,
52
            ]);
53 42
54 42
            foreach ($configGoals as $configGoal) {
55 42
                $experiment->goals()->firstOrCreate([
56
                    'name' => $configGoal,
57 42
                ], [
58
                    'hit' => 0,
59
                ]);
60
            }
61
        }
62 42
63 42
        session([
64
            self::SESSION_KEY_GOALS => new Collection,
65 42
        ]);
66
    }
67
68
    /**
69
     * Triggers a new visitor. Picks a new experiment and saves it to the session.
70
     *
71
     * @return \Ben182\AbTesting\Models\Experiment|void
72 51
     */
73
    public function pageView()
74 51
    {
75 51
        if (config('ab-testing.ignore_crawlers') && (new CrawlerDetect)->isCrawler()) {
76 42
            return;
77
        }
78 42
79
        if (session(self::SESSION_KEY_EXPERIMENT)) {
80 42
            return;
81
        }
82 9
83
        $this->start();
84
        $this->setNextExperiment();
85
86
        event(new ExperimentNewVisitor($this->getExperiment()));
87
88
        return $this->getExperiment();
89 42
    }
90
91 42
    /**
92 42
     * Calculates a new experiment and sets it to the session.
93
     *
94 42
     * @return void
95 42
     */
96
    protected function setNextExperiment()
97 42
    {
98
        $next = $this->getNextExperiment();
99
        $next->incrementVisitor();
100
101
        session([
102
            self::SESSION_KEY_EXPERIMENT => $next,
103
        ]);
104 42
    }
105
106 42
    /**
107
     * Calculates a new experiment.
108 42
     *
109
     * @return \Ben182\AbTesting\Models\Experiment|null
110
     */
111
    protected function getNextExperiment()
112
    {
113
        $sorted = $this->experiments->sortBy('visitors');
114
115
        return $sorted->first();
116
    }
117
118 9
    /**
119
     * Checks if the currently active experiment is the given one.
120 9
     *
121
     * @param string $name The experiments name
122 9
     *
123
     * @return bool
124
     */
125
    public function isExperiment(string $name)
126
    {
127
        $this->pageView();
128
129
        return $this->getExperiment()->name === $name;
130
    }
131
132 15
    /**
133
     * Completes a goal by incrementing the hit property of the model and setting its ID in the session.
134 15
     *
135 12
     * @param string $goal The goals name
136
     *
137
     * @return \Ben182\AbTesting\Models\Goal|false
138 15
     */
139
    public function completeGoal(string $goal)
140 15
    {
141 3
        if (! $this->getExperiment()) {
142
            $this->pageView();
143
        }
144 12
145 3
        $goal = $this->getExperiment()->goals->where('name', $goal)->first();
146
147
        if (! $goal) {
148 12
            return false;
149
        }
150 12
151 12
        if (session(self::SESSION_KEY_GOALS)->contains($goal->id)) {
152
            return false;
153 12
        }
154
155
        session(self::SESSION_KEY_GOALS)->push($goal->id);
156
157
        $goal->incrementHit();
158
        event(new GoalCompleted($goal));
159
160
        return $goal;
161 42
    }
162
163 42
    /**
164
     * Returns the currently active experiment.
165
     *
166
     * @return \Ben182\AbTesting\Models\Experiment|null
167
     */
168
    public function getExperiment()
169
    {
170
        return session(self::SESSION_KEY_EXPERIMENT);
171 3
    }
172
173 3
    /**
174
     * Returns all the completed goals.
175
     *
176
     * @return \Illuminate\Support\Collection|false
177
     */
178 3
    public function getCompletedGoals()
179 3
    {
180
        if (! session(self::SESSION_KEY_GOALS)) {
181
            return false;
182
        }
183
184
        return session(self::SESSION_KEY_GOALS)->map(function ($goalId) {
185
            return Goal::find($goalId);
186
        });
187
    }
188
}
189