Test Failed
Push — master ( da2c9c...d0cc4d )
by Kirill
02:03
created

Reflection::typeRedefinition()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 9

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 9
ccs 0
cts 7
cp 0
rs 9.9666
c 0
b 0
f 0
cc 1
nc 1
nop 1
crap 2
1
<?php
2
/**
3
 * This file is part of Railt package.
4
 *
5
 * For the full copyright and license information, please view the LICENSE
6
 * file that was distributed with this source code.
7
 */
8
declare(strict_types=1);
9
10
namespace Railt\Reflection;
11
12
use Railt\Reflection\Common\Serializable;
13
use Railt\Reflection\Contracts\Definition\TypeDefinition;
14
use Railt\Reflection\Contracts\Document as DocumentInterface;
15
use Railt\Reflection\Contracts\Reflection as ReflectionInterface;
16
use Railt\Reflection\Contracts\Type;
0 ignored issues
show
Bug introduced by
This use statement conflicts with another class in this namespace, Railt\Reflection\Type.

Let’s assume that you have a directory layout like this:

.
|-- OtherDir
|   |-- Bar.php
|   `-- Foo.php
`-- SomeDir
    `-- Foo.php

and let’s assume the following content of Bar.php:

// Bar.php
namespace OtherDir;

use SomeDir\Foo; // This now conflicts the class OtherDir\Foo

If both files OtherDir/Foo.php and SomeDir/Foo.php are loaded in the same runtime, you will see a PHP error such as the following:

PHP Fatal error:  Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.php

However, as OtherDir/Foo.php does not necessarily have to be loaded and the error is only triggered if it is loaded before OtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias:

// Bar.php
namespace OtherDir;

use SomeDir\Foo as SomeDirFoo; // There is no conflict anymore.
Loading history...
17
use Railt\Reflection\Exception\TypeConflictException;
18
use Railt\Reflection\Exception\TypeNotFoundException;
19
20
/**
21
 * Class Reflection
22
 */
23
class Reflection implements ReflectionInterface
24
{
25
    use Serializable;
26
27
    /**
28
     * @var array|TypeDefinition[]
29
     */
30
    protected $types = [];
31
32
    /**
33
     * @var array|DocumentInterface[]
34
     */
35
    protected $documents = [];
36
37
    /**
38
     * @return iterable
39
     */
40 4
    public function getDocuments(): iterable
41
    {
42 4
        return \array_values($this->documents);
0 ignored issues
show
Bug Best Practice introduced by
The return type of return \array_values($this->documents); (array) is incompatible with the return type declared by the interface Railt\Reflection\Contrac...eflection::getDocuments of type Railt\Reflection\Contrac...on\Contracts\Document[].

If you return a value from a function or method, it should be a sub-type of the type that is given by the parent type f.e. an interface, or abstract method. This is more formally defined by the Lizkov substitution principle, and guarantees that classes that depend on the parent type can use any instance of a child type interchangably. This principle also belongs to the SOLID principles for object oriented design.

Let’s take a look at an example:

class Author {
    private $name;

    public function __construct($name) {
        $this->name = $name;
    }

    public function getName() {
        return $this->name;
    }
}

abstract class Post {
    public function getAuthor() {
        return 'Johannes';
    }
}

class BlogPost extends Post {
    public function getAuthor() {
        return new Author('Johannes');
    }
}

class ForumPost extends Post { /* ... */ }

function my_function(Post $post) {
    echo strtoupper($post->getAuthor());
}

Our function my_function expects a Post object, and outputs the author of the post. The base class Post returns a simple string and outputting a simple string will work just fine. However, the child class BlogPost which is a sub-type of Post instead decided to return an object, and is therefore violating the SOLID principles. If a BlogPost were passed to my_function, PHP would not complain, but ultimately fail when executing the strtoupper call in its body.

Loading history...
43
    }
44
45
    /**
46
     * @param DocumentInterface $document
47
     */
48 4
    public function addDocument(DocumentInterface $document): void
49
    {
50 4
        $this->documents[$document->getName()] = $document;
51 4
    }
52
53
    /**
54
     * @param Type|null $of
55
     * @return iterable|TypeDefinition[]
56
     */
57 3
    public function all(Type $of = null): iterable
58
    {
59 3
        foreach ($this->types as $definition) {
60 1
            if ($of === null || $definition::typeOf($of)) {
61 1
                yield $definition;
62
            }
63
        }
64 3
    }
65
66
    /**
67
     * @param string $name
68
     * @param TypeDefinition|null $from
69
     * @return TypeDefinition
70
     * @throws TypeNotFoundException
71
     */
72 1
    public function get(string $name, TypeDefinition $from = null): TypeDefinition
73
    {
74 1
        if ($result = $this->find($name)) {
75 1
            return $result;
76
        }
77
78
        throw $this->typeNotFound($name, $from);
79
    }
80
81
    /**
82
     * @param string $name
83
     * @return null|TypeDefinition
84
     */
85 4
    public function find(string $name): ?TypeDefinition
86
    {
87 4
        return $this->types[$name] ?? null;
88
    }
89
90
    /**
91
     * @param string $name
92
     * @param TypeDefinition $from
93
     * @return TypeNotFoundException
94
     */
95
    protected function typeNotFound(string $name, TypeDefinition $from = null): TypeNotFoundException
96
    {
97
        $error = \sprintf('Type %s not found or could not be loaded', $name);
98
99
        $exception = new TypeNotFoundException($error);
100
101
        if ($from !== null) {
102
            $exception->throwsIn($from->getFile(), $from->getLine(), $from->getColumn());
103
        }
104
105
        return $exception;
106
    }
107
108
    /**
109
     * @param TypeDefinition $type
110
     * @return ReflectionInterface
111
     */
112 2
    public function add(TypeDefinition $type): ReflectionInterface
113
    {
114 2
        $this->types[$type->getName()] = $type;
115 2
        $this->addDocument($type->getDocument());
116
117 2
        return $this;
118
    }
119
120
    /**
121
     * @param string $name
122
     * @return bool
123
     */
124 3
    public function has(string $name): bool
125
    {
126 3
        return ($this->types[$name] ?? null) !== null;
127
    }
128
}
129