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