Test Failed
Push — master ( 271e8d...6ed146 )
by Corrado
02:05
created

SimpleFactory::__call()   B

Complexity

Conditions 8
Paths 9

Size

Total Lines 25
Code Lines 15

Duplication

Lines 0
Ratio 0 %

Importance

Changes 4
Bugs 0 Features 2
Metric Value
cc 8
eloc 15
c 4
b 0
f 2
nc 9
nop 2
dl 0
loc 25
rs 8.4444
1
<?php
2
3
/*
4
 * This file is part of SimpleFactory.
5
 *
6
 * (c) Corrado Ronci <[email protected]>
7
 *
8
 * For the full copyright and license information, please view the LICENSE
9
 * file that was distributed with this source code.
10
 */
11
12
namespace SimpleFactory;
13
14
use ReflectionParameter;
15
16
class SimpleFactory
17
{
18
    /**
19
     * The class to factory
20
     *
21
     * @var string
22
     */
23
    private $class;
24
25
    /**
26
     * The flag to set default paramter to null
27
     *
28
     * @var bool
29
     */
30
    private $configDefaultParameter = false;
31
    
32
    /**
33
     * The class reflaction
34
     *
35
     * @var ReflectionClass
0 ignored issues
show
Bug introduced by
The type SimpleFactory\ReflectionClass was not found. Did you mean ReflectionClass? If so, make sure to prefix the type with \.
Loading history...
36
     */
37
    private $reflactionClass;
38
39
    /**
40
     * The parameters of class
41
     *
42
     * @var array
43
     */
44
    private $parameters = [];
45
46
    public function __construct(string $object, bool $defaultParamter=null)
47
    {
48
        if (!class_exists($object)) {
49
            throw new \InvalidArgumentException(\sprintf('The class %s not exits', $object));
50
        }
51
        
52
        $this->class = $object;
53
        if (!is_null($defaultParamter)) {
54
            $this->configDefaultParameter = $defaultParamter;
55
        }
56
        $this->makeReflection();
57
    }
58
59
    /**
60
     * This method return an instance of property class
61
     *
62
     * @return object
63
     */
64
    public function make()
65
    {
66
        return $this->reflactionClass->newInstanceArgs($this->setInstanceParameters());
67
    }
68
69
    /**
70
     * This method overwrite the parameter with class instantiated
71
     *
72
     * @param object $object
73
     * @return self
74
     */
75
    public function with($object) : self
76
    {
77
        if (!$object instanceof $this->class) {
78
            throw new \InvalidArgumentException(\sprintf('The object class must be instance of %s', $this->class));
79
        }
80
81
        $reflactionWithClass = new \ReflectionClass($object);
82
        
83
        foreach ($reflactionWithClass->getProperties() as $property) {
84
            $property->setAccessible(true);
85
            $this->parameters[$property->getName()] = $property->getValue($object);
86
        }
87
88
        return $this;
89
    }
90
91
    /**
92
     * The magic method will be use to set parameter of class,
93
     * if the parameter has a type this method validate the parameter
94
     *
95
     * @param string $name
96
     * @param array $arguments
97
     * @return self
98
     */
99
    public function __call($name, $arguments) : self
100
    {
101
        if (strpos($name, 'set') > -1) {
102
            $arg   = lcfirst(str_replace('set', '', $name));
103
            $param = $this->findParam($arg);            
104
            if ($param->hasType()) {
105
                if (gettype(current($arguments)) === 'object') {
106
                    $class = $param->getType()->getName();
107
                    if (current($arguments) instanceof $class) {
108
                        $this->parameters[$param->getName()] = current($arguments);
109
                        return $this;
110
                    }
111
                }
112
                $argType = $this->normalizeTypeReflection($param->getType()->getName());
113
                if (gettype(current($arguments)) !== $argType) {
114
                    if (is_null(current($arguments)) && !$param->allowsNull()) {
115
                        throw new \InvalidArgumentException(\sprintf('The parameter %s of %s class must be of the type %s', $param, $this->class, $argType));
116
                    }
117
                }
118
            }
119
            
120
            $this->parameters[$param->getName()] = current($arguments);            
121
        }
122
123
        return $this;
124
    }
125
126
    /**
127
     * This method create self class by static function
128
     *
129
     * @param string $object
130
     * @param bool $defaultParamter
131
     * @return self
132
     */
133
    public static function create(string $object, bool $defaultParamter=null)
134
    {
135
        return new self($object, $defaultParamter);
136
    }
137
138
    /**
139
     * Reflection Class
140
     *
141
     * @return void
142
     */
143
    private function makeReflection()
144
    {
145
        $this->reflactionClass = new \ReflectionClass($this->class);
0 ignored issues
show
Documentation Bug introduced by
It seems like new ReflectionClass($this->class) of type ReflectionClass is incompatible with the declared type SimpleFactory\ReflectionClass of property $reflactionClass.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
146
        $this->reflactionArgsClass = $this->reflactionClass->getConstructor()->getParameters();
0 ignored issues
show
Bug Best Practice introduced by
The property reflactionArgsClass does not exist. Although not strictly required by PHP, it is generally a best practice to declare properties explicitly.
Loading history...
147
    }
148
149
    /**
150
     * This function check param to
151
     * arguments of reflection class
152
     *
153
     * @param string $param
154
     * @return ReflectionParameter
155
     * @throws BadMethodCallException
156
     */
157
    private function findParam(string $param) : ReflectionParameter
158
    {
159
        foreach ($this->reflactionArgsClass as $arg) {
160
            if ($arg->getName() === $param) {
161
                return $arg;
162
            }
163
        }
164
165
        throw new \BadMethodCallException(\sprintf('The parameter %s of %s class doesn\'t exist', $param, $this->class));        
166
    }
167
168
    /**
169
     * This method prepare parameter of reflection class
170
     * for new instance. If parameter
171
     * has class try to set parameter to empty class instance
172
     * If parameter has changed replace parameter with new value.
173
     *
174
     * @return array
175
     */
176
    private function setInstanceParameters() : array
177
    {
178
        $parameters = [];
179
        if (!$this->configDefaultParameter and count($this->reflactionArgsClass) !== count($this->parameters)) {
180
            throw new \ArgumentCountError(
181
                \sprintf('Arguments to class %s exactly expected %s, passed %s', $this->class, count($this->reflactionArgsClass), count($this->parameters))
182
            );            
183
        }
184
        foreach ($this->reflactionArgsClass as $arg) {
185
            if (\array_key_exists($arg->getName(), $this->parameters)) {
186
                $parameters[$arg->getName()] = $this->parameters[$arg->getName()];
187
                continue;
188
            }
189
190
            if (!is_null($arg->getType())) {                
191
                if (class_exists($arg->getType()->getName())) {
192
                    if ($arg->allowsNull() !== true) {
193
                        $parameters[$arg->getName()] = (new self($arg->getType()->getName()))->make();
194
                        continue;
195
                    }                                        
196
                }
197
            }
198
199
            if ($this->configDefaultParameter === true) {
200
                $parameters[$arg->getName()] = null;
201
            }
202
        }
203
    
204
        return $parameters;
205
    }
206
207
    /**
208
     * This function normalize reflection argument type
209
     * with gettype php function
210
     *
211
     * @param string $type
212
     * @return string
213
     */
214
    private function normalizeTypeReflection($type) : string
215
    {
216
        switch ($type) {
217
            case 'int':
218
                return 'integer';
219
            case 'float':
220
                return 'double';
221
            case 'bool':
222
                return 'boolean';    
223
            default:
224
                return $type;
225
        }
226
    }
227
}
228