Completed
Pull Request — master (#8)
by Emily
02:26
created

ReflectionCompositeFactory::addItems()   B

Complexity

Conditions 5
Paths 4

Size

Total Lines 42
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5.0061

Importance

Changes 0
Metric Value
dl 0
loc 42
ccs 15
cts 16
cp 0.9375
rs 8.439
c 0
b 0
f 0
cc 5
eloc 21
nc 4
nop 3
crap 5.0061
1
<?php
2
/**
3
 * This file is part of the Composite Utils package.
4
 *
5
 * (c) Emily Shepherd <[email protected]>
6
 *
7
 * For the full copyright and license information, please view the
8
 * LICENSE.md file that was distributed with this source code.
9
 *
10
 * @package spaark/composite-utils
11
 * @author Emily Shepherd <[email protected]>
12
 * @license MIT
13
 */
14
15
namespace Spaark\CompositeUtils\Factory\Reflection;
16
17
use Spaark\CompositeUtils\Factory\BaseFactory;
18
use Spaark\CompositeUtils\Model\Reflection\ReflectionComposite;
19
use Spaark\CompositeUtils\Model\Reflection\ReflectionProperty;
20
use Spaark\CompositeUtils\Model\Reflection\ReflectionMethod;
21
use Spaark\CompositeUtils\Model\Collection\ListCollection\FixedList;
22
use Spaark\CompositeUtils\Service\ReflectionCompositeProviderInterface;
23
use Spaark\CompositeUtils\Service\ReflectionCompositeProvider;
24
use \ReflectionClass as PHPNativeReflectionClass;
25
use \ReflectionProperty as PHPNativeReflectionProperty;
26
use \ReflectionMethod as PHPNativeReflectionMethod;
27
use \Reflector;
28
29
/**
30
 * Builds a ReflectionComposite for a given class
31
 */
32
class ReflectionCompositeFactory extends ReflectorFactory
33
{
34
    const REFLECTION_OBJECT = ReflectionComposite::class;
35
36
    const PROPERTIES = [
37
        'traits' => [''],
38
        'interfaces' => [''],
39
        'Methods' => ['local'],
40
        'Properties' => ['local', 'required', 'optional', 'built']
41
    ];
42
43
    /**
44
     * @var PHPNativeReflector
45
     */
46
    protected $reflector;
47
48
    /**
49
     * @var ReflectionComposite
50
     */
51
    protected $object;
52
53
    /**
54
     * @var ReflectionCompositeProviderInterface
55
     */
56
    protected $provider;
57
58
    /**
59
     * Creates a new ReflectionCompositeFactory from the given
60
     * classname
61
     *
62
     * @param string $classname The class to build a reflect upon
63
     * @return ReflectionCompositeFactory
64
     */
65 29
    public static function fromClassName(string $classname)
66
    {
67 29
        return new static
68
        (
69 29
            new PHPNativeReflectionClass($classname),
70 29
            ReflectionCompositeProvider::getDefault()
71
        );
72
    }
73
74
    /**
75
     * Constructs the Factory with the given reflector and Composite
76
     * provider
77
     *
78
     * @param PHPNativeReflectionClass $reflect
79
     * @param ReflectionCompositeProviderInterface $provider
80
     */
81 29
    public function __construct
82
    (
83
        PHPNativeReflectionClass $reflect,
84
        ReflectionCompositeProviderInterface $provider
85
    )
86
    {
87 29
        parent::__construct($reflect);
88 29
        $this->provider = $provider;
89 29
    }
90
91
    /**
92
     * Builds the ReflectionComposite from the provided parameters
93
     *
94
     * @return ReflectionComposite
95
     */
96 29
    public function build()
97
    {
98 29
        $this->initObject();
99
100 29
        foreach ($this->reflector->getTraits() as $trait)
101
        {
102 4
            $this->addInheritance('traits', $trait);
103
        }
104
105 29
        if ($parent = $this->reflector->getParentClass())
106
        {
107 1
            $this->addInheritance('parent', $parent, 'setRawValue');
108
        }
109
110 29
        foreach ($this->reflector->getInterfaces() as $interface)
111
        {
112
            $this->addInheritance('interfaces', $interface);
113
        }
114
115 29
        $this->parseDocComment
116
        ([
117 29
            'generic' => 'addGeneric'
118
        ]);
119
120 29
        $fileName = $this->reflector->getFileName();
121
122 29
        $file = (new ReflectionFileFactory($fileName))->build();
123 29
        $this->accessor->setRawValue('file', $file);
124
125 29
        $this->accessor->setRawValue
126
        (
127 29
            'classname',
128 29
            $this->reflector->name
129
        );
130 29
        $this->accessor->setRawValue
131
        (
132 29
            'namespace',
133 29
            $file->namespaces[$this->reflector->getNamespaceName()]
134
        );
135
136 29
        $this->addItems('properties', false, 'Property');
137 29
        $this->addItems('methods', true, 'Method');
138
139 29
        $this->resizeProperties();
140
141 29
        return $this->object;
142
    }
143
144
    /**
145
     * Initialise the object with fixed lists
146
     */
147 29
    protected function initObject()
148
    {
149 29
        foreach (static::PROPERTIES as $name => $prefixes)
150
        {
151 29
            $size = count($this->reflector->{'get' . $name}());
152 29
            foreach ($prefixes as $prefix)
153
            {
154 29
                $this->accessor->setRawValue
155
                (
156 29
                    $prefix . $name,
157 29
                    new FixedList($size)
158
                );
159
            }
160
        }
161 29
    }
162
163
    /**
164
     * Adds a generic value to this class
165
     *
166
     * @var string $name Should be 'generic'. Unused
167
     * @var string $value The annotation value
168
     */
169 27
    public function addGeneric($name, $value)
0 ignored issues
show
Unused Code introduced by
The parameter $name 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...
170
    {
171 27
        $space = strpos($value, ' ');
172
173 27
        if ($space === false)
174
        {
175 27
            $key = $value;
176 27
            $type = '';
177
        }
178
        else
179
        {
180 23
            $key = substr($value, 0, $space);
181 23
            $type = substr($value, $space);
182
        }
183
184 27
        $this->accessor->getRawValue('generics')[$key] =
185 27
            (new TypeParser($this->object))->parse($type);
186 27
    }
187
188
    /**
189
     * Resize the FixedList properties down to their size
190
     */
191 29
    protected function resizeProperties()
192
    {
193 29
        foreach (static::PROPERTIES as $name => $prefixes)
194
        {
195 29
            foreach ($prefixes as $prefix)
196
            {
197 29
                $this->object->{$prefix . $name}->resizeToFull();
198
            }
199
        }
200 29
    }
201
202
    /**
203
     * Loops through the list methods or properties adding them to the
204
     * Composite
205
     *
206
     * @param string $name
207
     * @param bool $checkFile
208
     * @param string $singular
0 ignored issues
show
Bug introduced by
There is no parameter named $singular. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
209
     */
210 29
    protected function addItems
211
    (
212
        string $name,
213
        bool $checkFile,
214
        string $signular
215
    )
216
    {
217 29
        foreach ($this->reflector->{'get' . $name}() as $item)
218
        {
219
            // We only reflect on methods in userspace
220 29
            if ($checkFile && !$item->getFileName())
221
            {
222
                continue;
223
            }
224
            // This belongs to a super class, use that definition
225
            // instead
226 29
            elseif ($item->class !== $this->reflector->getName())
227
            {
228 1
                $item = $this->provider->get($item->class)
229 1
                    ->$name[$item->getName()];
230
            }
231
            // Parse this method
232
            else
233
            {
234
                $factory =
235
                      '\Spaark\CompositeUtils\Factory\Reflection'
236 29
                    . '\Reflection' . $signular . 'Factory';
237 29
                $item = $this->{'build' . $signular}
238
                (
239 29
                    new $factory($item),
240 29
                    $item
241
                );
242 29
                $this->accessor->rawAddToValue
0 ignored issues
show
Deprecated Code introduced by
The method Spaark\CompositeUtils\Se...cessor::rawAddToValue() has been deprecated.

This method has been deprecated.

Loading history...
243
                (
244 29
                    'local' . ucfirst($name),
245 29
                    $item
246
                );
247
            }
248
249 29
            $this->accessor->getRawValue($name)[$item->name] = $item;
250
        }
251 29
    }
252
253
    /**
254
     * Adds a super class / interface / trait to this Composite
255
     *
256
     * @param string $group The type of superclass (parent, etc...)
257
     * @param PHPNativeReflectionClass $reflect
258
     * @param string $method
259
     */
260 5
    protected function addInheritance
261
    (
262
        string $group,
263
        PHPNativeReflectionClass $reflect,
264
        string $method = 'rawAddToValue'
265
    )
266
    {
267
        // We only reflect on classes within userspace
268 5
        if ($reflect->getFileName())
269
        {
270 5
            $item = $this->provider->get($reflect->getName());
0 ignored issues
show
Bug introduced by
Consider using $reflect->name. There is an issue with getName() and APC-enabled PHP versions.
Loading history...
271 5
            $this->accessor->$method($group, $item);
272
273 5
            foreach ($item->generics as $name => $generic)
0 ignored issues
show
Documentation introduced by
The property $generics is declared protected in Spaark\CompositeUtils\Mo...ion\ReflectionComposite. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
274
            {
275 1
                $this->object->generics[$name] = $generic;
0 ignored issues
show
Documentation introduced by
The property $generics is declared protected in Spaark\CompositeUtils\Mo...ion\ReflectionComposite. Since you implemented __get(), maybe consider adding a @property or @property-read annotation. This makes it easier for IDEs to provide auto-completion.

Since your code implements the magic setter _set, this function will be called for any write 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.");
        }
    }

}

Since the property has write access only, you can use the @property-write 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...
276
            }
277
        }
278 5
    }
279
280
    /**
281
     * Uses a ReflectionPropertyFactory to build a ReflectionProperty
282
     *
283
     * @param ReflectionPropertyFactory $factory
284
     * @return ReflectionProperty
285
     */
286 26
    protected function buildProperty
287
    (
288
        ReflectionPropertyFactory $factory,
289
        PHPNativeReflectionProperty $reflect
290
    )
291
    : ReflectionProperty
292
    {
293 26
        return $factory->build
294
        (
295 26
            $this->object,
296 26
            $this->reflector
297 26
                ->getDefaultProperties()[$reflect->getName()]
298
        );
299
    }
300
301
    /**
302
     * Uses a ReflectionMethodFactory to build a ReflectionMethod
303
     *
304
     * @param ReflectionMethodFactory $factory
305
     * @return ReflectionMethod
306
     */
307 29
    protected function buildMethod(ReflectionMethodFactory $factory)
308
        : ReflectionMethod
309
    {
310 29
        return $factory->build($this->object);
311
    }
312
}
313
314