Completed
Push — master ( b8da3f...f66325 )
by Emily
10s
created

ReflectionCompositeFactory::fromClassName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 1

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
c 0
b 0
f 0
cc 1
eloc 4
nc 1
nop 1
crap 1
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\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 21
    public static function fromClassName(string $classname)
66
    {
67 21
        return new static
68
        (
69 21
            new PHPNativeReflectionClass($classname),
70 21
            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 21
    public function __construct
82
    (
83
        PHPNativeReflectionClass $reflect,
84
        ReflectionCompositeProviderInterface $provider
85
    )
86
    {
87 21
        parent::__construct($reflect);
88 21
        $this->provider = $provider;
89 21
    }
90
91
    /**
92
     * Builds the ReflectionComposite from the provided parameters
93
     *
94
     * @return ReflectionComposite
95
     */
96 21
    public function build()
97
    {
98 21
        $this->initObject();
99
100 21
        foreach ($this->reflector->getTraits() as $trait)
101
        {
102 2
            $this->addInheritance('traits', $trait);
103
        }
104
105 21
        if ($parent = $this->reflector->getParentClass())
106
        {
107 1
            $this->addInheritance('parent', $parent, 'setRawValue');
108
        }
109
110 21
        foreach ($this->reflector->getInterfaces() as $interface)
111
        {
112
            $this->addInheritance('interfaces', $interface);
113
        }
114
115 21
        $this->parseDocComment
116
        ([
117
            'generic' => 'addGeneric'
118 21
        ]);
119
120 21
        $fileName = $this->reflector->getFileName();
121
122 21
        $file = (new ReflectionFileFactory($fileName))->build();
123 21
        $this->accessor->setRawValue('file', $file);
124
125 21
        $this->accessor->setRawValue
126
        (
127 21
            'classname',
128 21
            $this->reflector->name
129
        );
130 21
        $this->accessor->setRawValue
131
        (
132 21
            'namespace',
133 21
            $file->namespaces[$this->reflector->getNamespaceName()]
134
        );
135
136 21
        $this->addItems('properties', false, 'Property');
137 21
        $this->addItems('methods', true, 'Method');
138
139 21
        $this->resizeProperties();
140
141 21
        return $this->object;
142
    }
143
144
    /**
145
     * Initialise the object with fixed lists
146
     */
147 21
    protected function initObject()
148
    {
149 21
        foreach (static::PROPERTIES as $name => $prefixes)
150
        {
151 21
            $size = count($this->reflector->{'get' . $name}());
152 21
            foreach ($prefixes as $prefix)
153
            {
154 21
                $this->accessor->setRawValue
155
                (
156 21
                    $prefix . $name,
157 21
                    new FixedList($size)
158
                );
159
            }
160
        }
161 21
    }
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 19
    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 19
        $space = strpos($value, ' ');
172
173 19
        if ($space === false)
174
        {
175 19
            $key = $value;
176 19
            $type = '';
177
        }
178
        else
179
        {
180 18
            $key = substr($value, 0, $space);
181 18
            $type = substr($value, $space);
182
        }
183
184 19
        $this->accessor->getRawValue('generics')[$key] =
185 19
            (new TypeParser($this->object))->parse($type);
186 19
    }
187
188
    /**
189
     * Resize the FixedList properties down to their size
190
     */
191 21
    protected function resizeProperties()
192
    {
193 21
        foreach (static::PROPERTIES as $name => $prefixes)
194
        {
195 21
            foreach ($prefixes as $prefix)
196
            {
197 21
                $this->object->{$prefix . $name}->resizeToFull();
198
            }
199
        }
200 21
    }
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 21
    protected function addItems
211
    (
212
        string $name,
213
        bool $checkFile,
214
        string $signular
215
    )
216
    {
217 21
        foreach ($this->reflector->{'get' . $name}() as $item)
218
        {
219
            // We only reflect on methods in userspace
220 21
            if ($checkFile && !$item->getFileName())
221
            {
222
                continue;
223
            }
224
            // This belongs to a super class, use that definition
225
            // instead
226 21
            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 21
                    . '\Reflection' . $signular . 'Factory';
237 21
                $item = $this->{'build' . $signular}
238
                (
239 21
                    new $factory($item),
240
                    $item
241
                );
242 21
                $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 21
                    'local' . ucfirst($name),
245
                    $item
246
                );
247
            }
248
249 21
            $this->accessor->getRawValue($name)[$item->name] = $item;
250
        }
251 21
    }
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 3
    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 3
        if ($reflect->getFileName())
269
        {
270 3
            $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 3
            $this->accessor->$method($group, $item);
272
273 3
            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 3
    }
279
280
    /**
281
     * Uses a ReflectionPropertyFactory to build a ReflectionProperty
282
     *
283
     * @param ReflectionPropertyFactory $factory
284
     * @return ReflectionProperty
285
     */
286 20
    protected function buildProperty
287
    (
288
        ReflectionPropertyFactory $factory,
289
        PHPNativeReflectionProperty $reflect
290
    )
291
    : ReflectionProperty
292
    {
293 20
        return $factory->build
294
        (
295 20
            $this->object,
296 20
            $this->reflector
297 20
                ->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 21
    protected function buildMethod(ReflectionMethodFactory $factory)
308
        : ReflectionMethod
309
    {
310 21
        return $factory->build($this->object);
311
    }
312
}
313
314