Completed
Pull Request — master (#17)
by
unknown
14:51
created

AbTesting::getVisitor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 12

Importance

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