Completed
Pull Request — master (#21)
by Benjamin
04:30
created

AbTesting::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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