Completed
Pull Request — master (#17)
by
unknown
08:59 queued 07:53
created

AbTesting::getVisitor()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 12

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 6
CRAP Score 3

Importance

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