Completed
Push — master ( 4b5497...6962e4 )
by Emily
11s
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\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 30
    public static function fromClassName(string $classname)
66
    {
67 30
        return new static
68
        (
69 30
            new PHPNativeReflectionClass($classname),
70 30
            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 30
    public function __construct
82
    (
83
        PHPNativeReflectionClass $reflect,
84
        ReflectionCompositeProviderInterface $provider
85
    )
86
    {
87 30
        parent::__construct($reflect);
88 30
        $this->provider = $provider;
89 30
    }
90
91
    /**
92
     * Builds the ReflectionComposite from the provided parameters
93
     *
94
     * @return ReflectionComposite
95
     */
96 30
    public function build()
97
    {
98 30
        $this->initObject();
99
100 30
        foreach ($this->reflector->getTraits() as $trait)
101
        {
102 5
            $this->addInheritance('traits', $trait);
103
        }
104
105 30
        if ($parent = $this->reflector->getParentClass())
106
        {
107 1
            $this->addInheritance('parent', $parent, 'setRawValue');
108
        }
109
110 30
        foreach ($this->reflector->getInterfaces() as $interface)
111
        {
112 1
            $this->addInheritance('interfaces', $interface);
113
        }
114
115 30
        $this->parseDocComment
116
        ([
117 30
            'generic' => 'addGeneric'
118
        ]);
119
120 30
        $fileName = $this->reflector->getFileName();
121
122 30
        $file = (new ReflectionFileFactory($fileName))->build();
123 30
        $this->accessor->setRawValue('file', $file);
124
125 30
        $this->accessor->setRawValue
126
        (
127 30
            'classname',
128 30
            $this->reflector->name
129
        );
130 30
        $this->accessor->setRawValue
131
        (
132 30
            'namespace',
133 30
            $file->namespaces[$this->reflector->getNamespaceName()]
134
        );
135
136 30
        $this->addItems('properties', false, 'Property');
137 30
        $this->addItems('methods', true, 'Method');
138
139 30
        $this->resizeProperties();
140
141 30
        return $this->object;
142
    }
143
144
    /**
145
     * Initialise the object with fixed lists
146
     */
147 30
    protected function initObject()
148
    {
149 30
        foreach (static::PROPERTIES as $name => $prefixes)
150
        {
151 30
            $size = count($this->reflector->{'get' . $name}());
152 30
            foreach ($prefixes as $prefix)
153
            {
154 30
                $this->accessor->setRawValue
155
                (
156 30
                    $prefix . $name,
157 30
                    new FixedList($size)
158
                );
159
            }
160
        }
161 30
    }
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 30
    protected function resizeProperties()
192
    {
193 30
        foreach (static::PROPERTIES as $name => $prefixes)
194
        {
195 30
            foreach ($prefixes as $prefix)
196
            {
197 30
                $this->object->{$prefix . $name}->resizeToFull();
198
            }
199
        }
200 30
    }
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 30
    protected function addItems
211
    (
212
        string $name,
213
        bool $checkFile,
214
        string $signular
215
    )
216
    {
217 30
        foreach ($this->reflector->{'get' . $name}() as $item)
218
        {
219
            // We only reflect on methods in userspace
220 30
            if ($checkFile && !$item->getFileName())
221
            {
222
                continue;
223
            }
224
            // This belongs to a super class, use that definition
225
            // instead
226 30
            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 30
                    . '\Reflection' . $signular . 'Factory';
237 30
                $item = $this->{'build' . $signular}
238
                (
239 30
                    new $factory($item),
240 30
                    $item
241
                );
242 30
                $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 30
                    'local' . ucfirst($name),
245 30
                    $item
246
                );
247
            }
248
249 30
            $this->accessor->getRawValue($name)[$item->name] = $item;
250
        }
251 30
    }
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 6
    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 6
        if ($reflect->getFileName())
269
        {
270 6
            $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 6
            $this->accessor->$method($group, $item);
272
273 6
            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 6
    }
279
280
    /**
281
     * Uses a ReflectionPropertyFactory to build a ReflectionProperty
282
     *
283
     * @param ReflectionPropertyFactory $factory
284
     * @return ReflectionProperty
285
     */
286 27
    protected function buildProperty
287
    (
288
        ReflectionPropertyFactory $factory,
289
        PHPNativeReflectionProperty $reflect
290
    )
291
    : ReflectionProperty
292
    {
293 27
        return $factory->build
294
        (
295 27
            $this->object,
296 27
            $this->reflector
297 27
                ->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 30
    protected function buildMethod(ReflectionMethodFactory $factory)
308
        : ReflectionMethod
309
    {
310 30
        return $factory->build($this->object);
311
    }
312
}
313
314