Completed
Pull Request — master (#8)
by Jeremy
04:54
created

Preparation::createPropertyExperiment()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 22
Code Lines 14

Duplication

Lines 7
Ratio 31.82 %

Code Coverage

Tests 15
CRAP Score 3

Importance

Changes 1
Bugs 0 Features 1
Metric Value
c 1
b 0
f 1
dl 7
loc 22
ccs 15
cts 15
cp 1
rs 9.2
cc 3
eloc 14
nc 3
nop 4
crap 3
1
<?php
2
3
4
namespace Scientist\Blind;
5
6
7
use ReflectionClass;
8
use ReflectionProperty;
9
use Scientist\Blind;
10
use Scientist\Experiment;
11
use Scientist\SideEffects\MissingMethod;
12
use Scientist\SideEffects\MissingProperty;
13
use Scientist\Study;
14
use Zend\Code\Generator\ClassGenerator;
15
use Zend\Code\Generator\MethodGenerator;
16
use Zend\Code\Reflection\ClassReflection;
17
18
class Preparation
19
{
20
    /**
21
     * @param Study $study
22
     * @param mixed $control
23
     * @param mixed[] $trials
24
     * @param string[] $interfaces that blind should satisfy
25
     * @return Blind
26
     */
27 10
    public function prepare(Study $study, $control, array $trials, array $interfaces = [])
28
    {
29 10
        $experiments = $this->createExperiments($study, $control, $trials);
30 10
        return $this->createBlind($study, $control, $experiments, $interfaces);
31
    }
32
33
    /**
34
     * @param $study
35
     * @param mixed $control
36
     * @param Experiment[] $experiments
37
     * @param string[] $interfaces
38
     * @return Blind
39
     */
40 10
    private function createBlind($study, $control, array $experiments, array $interfaces = [])
0 ignored issues
show
Unused Code introduced by
The parameter $control is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
41
    {
42 10
        $blind_name = sprintf("Blind_%s", str_replace('.', '', uniqid('', true)));
43
44 10
        array_push($interfaces, Blind::class);
45
46 10
        foreach($interfaces as &$interface) {
47 10
            if(strpos($interface, '\\') !== 0) {
48 10
                $interface = '\\'.$interface;
49 10
            }
50 10
        }
51
52 10
        $generator = new ClassGenerator($blind_name, 'Scientist\Blind');
53 10
        $generator->setImplementedInterfaces($interfaces);
54 10
        $generator->addTrait('\\'.DecoratorTrait::class);
55
56 10
        $this->addInterfaceMethods($generator, $interfaces);
57
58 10
        $code = $generator->generate();
59 10
        eval($code);
0 ignored issues
show
Coding Style introduced by
It is generally not recommended to use eval unless absolutely required.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
60
61 10
        $class_name = sprintf('Scientist\Blind\%s', $blind_name);
62 10
        return new $class_name($study, $experiments);
63
    }
64
65
    /**
66
     * @param $study
67
     * @param $control
68
     * @param array $trials
69
     * @return array
70
     */
71 10
    private function createExperiments(Study $study, $control, array $trials)
72
    {
73 10
        $experiments = [];
74 10
        $reflection = new ReflectionClass($control);
75 10
        foreach($reflection->getMethods() as $method) {
76 10
            $experiments[] = $this->createMethodExperiment($method->getName(), $study, $control, $trials);
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
77 10
        }
78 10
        foreach($reflection->getProperties(ReflectionProperty::IS_PUBLIC) as $property) {
79 10
            $experiments[] = $this->createPropertyExperiment($property->getName(), $study, $control, $trials);
80 10
        }
81 10
        return $experiments;
82
    }
83
84
    /**
85
     * @param string $name
86
     * @param Study $study
87
     * @param mixed $control
88
     * @param mixed[] $trials
89
     * @return Experiment
90
     */
91 10
    private function createMethodExperiment($name, Study $study, $control, array $trials)
92
    {
93 10
        $experiment_name = sprintf('%s::%s', $study->getName(), $name);
94
        /** @var Experiment $experiment */
95 10
        $experiment = $study->getLaboratory()
96 10
            ->experiment($experiment_name);
97
98 10
        $experiment->control([$control, $name]);
99
100 10 View Code Duplication
        foreach($trials as $trial_name => $trial) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
101 10
            if(method_exists($trial, $name)){
102 8
                $experiment->trial($trial_name, [$trial, $name]);
103 8
            } else {
104 2
                $experiment->trial($trial_name, $this->createMissingMethodCallable($trial, $name));
105
            }
106 10
        }
107
108 10
        $experiment->matcher($study->getMatcher());
109 10
        $experiment->chance($study->getChance());
110 10
        $study->addExperiment($experiment);
111 10
        return $experiment;
112
    }
113
114 10
    private function createPropertyExperiment($name, Study $study, $control, array $trials)
115
    {
116 10
        $experiment_name = sprintf('%s::$%s', $study->getName(), $name);
117
        /** @var Experiment $experiment */
118 10
        $experiment = $study->getLaboratory()
119 10
            ->experiment($experiment_name);
120
121 10
        $experiment->control($this->createPropertyCallable($control, $name));
122
123 10 View Code Duplication
        foreach($trials as $trial_name => $trial) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
124 10
            if(property_exists($trial, $name)){
125 7
                $experiment->trial($trial_name, $this->createPropertyCallable($trial, $name));
126 7
            } else {
127 3
                $experiment->trial($trial_name, $this->createMissingPropertyCallable($trial, $name));
128
            }
129 10
        }
130
131 10
        $experiment->matcher($study->getMatcher());
132 10
        $experiment->chance($study->getChance());
133 10
        $study->addExperiment($experiment);
134 10
        return $experiment;
135
    }
136
137
    /**
138
     * @param mixed $instance
139
     * @param string $name
140
     * @return callable
141
     */
142 10
    private function createPropertyCallable($instance, $name)
143
    {
144
        return function() use ($instance, $name) {
145 3
            $args = func_get_args();
146
            // __set
147 3
            if(count($args) > 1) {
148 2
                list($name, $value) = $args;
149 2
                return $instance->{$name} = $value;
150
            }
151
            // __get
152 1
            $name = $args[0];
0 ignored issues
show
Bug introduced by
Consider using a different name than the imported variable $name, or did you forget to import by reference?

It seems like you are assigning to a variable which was imported through a use statement which was not imported by reference.

For clarity, we suggest to use a different name or import by reference depending on whether you would like to have the change visibile in outer-scope.

Change not visible in outer-scope

$x = 1;
$callable = function() use ($x) {
    $x = 2; // Not visible in outer scope. If you would like this, how
            // about using a different variable name than $x?
};

$callable();
var_dump($x); // integer(1)

Change visible in outer-scope

$x = 1;
$callable = function() use (&$x) {
    $x = 2;
};

$callable();
var_dump($x); // integer(2)
Loading history...
153 1
            return $instance->{$name};
154 10
        };
155
    }
156
157 3
    private function createMissingPropertyCallable($instance, $name)
158
    {
159
        return function() use ($instance, $name) {
160 2
            throw new MissingProperty($instance, $name);
161 3
        };
162
    }
163
164
    private function createMissingMethodCallable($instance, $name)
165
    {
166 2
        return function() use ($instance, $name) {
167 1
            throw new MissingMethod($instance, $name);
168 2
        };
169
    }
170
171 10
    private function addInterfaceMethods(Classgenerator $classGenerator, array $interfaces)
172
    {
173 1
        $template = <<<'PHP'
174
    $arguments = func_get_args();
175
    $experiment = $this->getExperiment($this->getExperimentNameForMethod('%s'));
176
    return call_user_func_array([$experiment, 'run'], $arguments);
177 10
PHP;
178
179 10
        foreach($interfaces as $interface) {
180 10
            $reflection = new ClassReflection($interface);
181 10
            foreach($reflection->getMethods() as $method) {
182 3
                $generator = MethodGenerator::fromReflection($method);
183 3
                $generator->setInterface(false);
184 3
                $generator->setBody(sprintf($template, $method->getName()));
0 ignored issues
show
Bug introduced by
Consider using $method->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
185 3
                $classGenerator->addMethodFromGenerator($generator);
186 10
            }
187 10
        }
188 10
    }
189
190
}