Completed
Push — master ( f39703...8f2f8c )
by Benjamin
08:17
created

AbTesting::pageView()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 11

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 7
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 11
ccs 7
cts 7
cp 1
rs 9.9
c 0
b 0
f 0
cc 2
nc 2
nop 0
crap 2
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 48
    public function __construct()
20
    {
21 48
        $this->experiments = new Collection;
22 48
    }
23
24
    /**
25
     * Validates the config items and puts them into models.
26
     *
27
     * @return void
28
     */
29 48
    protected function start()
30
    {
31 48
        $configExperiments = config('ab-testing.experiments');
32 48
        $configGoals = config('ab-testing.goals');
33
34 48
        if (count($configExperiments) !== count(array_unique($configExperiments))) {
35 3
            throw InvalidConfiguration::experiment();
36
        }
37
38 45
        if (count($configGoals) !== count(array_unique($configGoals))) {
39 3
            throw InvalidConfiguration::goal();
40
        }
41
42 42
        foreach ($configExperiments as $configExperiment) {
43 42
            $this->experiments[] = $experiment = Experiment::firstOrCreate([
44 42
                'name' => $configExperiment,
45
            ], [
46 42
                'visitors' => 0,
47
            ]);
48
49 42
            foreach ($configGoals as $configGoal) {
50 42
                $experiment->goals()->firstOrCreate([
51 42
                    'name' => $configGoal,
52
                ], [
53 42
                    'hit' => 0,
54
                ]);
55
            }
56
        }
57
58 42
        session([
59 42
            self::SESSION_KEY_GOALS => new Collection,
60
        ]);
61 42
    }
62
63
    /**
64
     * Triggers a new visitor. Picks a new experiment and saves it to the session.
65
     *
66
     * @return \Ben182\AbTesting\Models\Experiment|void
67
     */
68 48
    public function pageView()
69
    {
70 48
        if (! session(self::SESSION_KEY_EXPERIMENT)) {
71 48
            $this->start();
72 42
            $this->setNextExperiment();
73
74 42
            event(new ExperimentNewVisitor($this->getExperiment()));
75
76 42
            return $this->getExperiment();
77
        }
78 9
    }
79
80
    /**
81
     * Calculates a new experiment and sets it to the session.
82
     *
83
     * @return void
84
     */
85 42
    protected function setNextExperiment()
86
    {
87 42
        $next = $this->getNextExperiment();
88 42
        $next->incrementVisitor();
89
90 42
        session([
91 42
            self::SESSION_KEY_EXPERIMENT => $next,
92
        ]);
93 42
    }
94
95
    /**
96
     * Calculates a new experiment.
97
     *
98
     * @return \Ben182\AbTesting\Models\Experiment|null
99
     */
100 42
    protected function getNextExperiment()
101
    {
102 42
        $sorted = $this->experiments->sortBy('visitors');
103
104 42
        return $sorted->first();
105
    }
106
107
    /**
108
     * Checks if the currently active experiment is the given one.
109
     *
110
     * @param string $name The experiments name
111
     *
112
     * @return bool
113
     */
114 9
    public function isExperiment(string $name)
115
    {
116 9
        $this->pageView();
117
118 9
        return $this->getExperiment()->name === $name;
0 ignored issues
show
Documentation introduced by
The property name does not exist on object<Ben182\AbTesting\Models\Experiment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
119
    }
120
121
    /**
122
     * Completes a goal by incrementing the hit property of the model and setting its ID in the session.
123
     *
124
     * @param string $goal The goals name
125
     *
126
     * @return \Ben182\AbTesting\Models\Goal|false
127
     */
128 15
    public function completeGoal(string $goal)
129
    {
130 15
        if (! $this->getExperiment()) {
131 12
            $this->pageView();
132
        }
133
134 15
        $goal = $this->getExperiment()->goals->where('name', $goal)->first();
0 ignored issues
show
Documentation introduced by
The property goals does not exist on object<Ben182\AbTesting\Models\Experiment>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
135
136 15
        if (! $goal) {
137 3
            return false;
138
        }
139
140 12
        if (session(self::SESSION_KEY_GOALS)->contains($goal->id)) {
141 3
            return false;
142
        }
143
144 12
        session(self::SESSION_KEY_GOALS)->push($goal->id);
145
146 12
        $goal->incrementHit();
147 12
        event(new GoalCompleted($goal));
148
149 12
        return $goal;
150
    }
151
152
    /**
153
     * Returns the currently active experiment.
154
     *
155
     * @return \Ben182\AbTesting\Models\Experiment|null
156
     */
157 42
    public function getExperiment()
158
    {
159 42
        return session(self::SESSION_KEY_EXPERIMENT);
160
    }
161
162
    /**
163
     * Returns all the completed goals.
164
     *
165
     * @return \Illuminate\Support\Collection|false
166
     */
167 3
    public function getCompletedGoals()
168
    {
169 3
        if (! session(self::SESSION_KEY_GOALS)) {
170
            return false;
171
        }
172
173
        return session(self::SESSION_KEY_GOALS)->map(function ($goalId) {
174 3
            return Goal::find($goalId);
175 3
        });
176
    }
177
}
178