Completed
Push — master ( ed8706...d6377e )
by Karsten
02:14
created

DefaultPropertyMapper::readDocBlock()   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 1
Bugs 1 Features 0
Metric Value
c 1
b 1
f 0
dl 0
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 1
eloc 4
nc 1
nop 1
crap 1
1
<?php
2
/**
3
 * Created by gerk on 01.10.16 17:24
4
 */
5
6
namespace PeekAndPoke\Component\MetaCore;
7
8
use PeekAndPoke\Component\MetaCore\DomainModel\Property;
9
use PeekAndPoke\Component\MetaCore\DomainModel\Type;
10
use PeekAndPoke\Component\MetaCore\DomainModel\TypeRef;
11
use PeekAndPoke\Component\MetaCore\DomainModel\Visibility;
12
use PeekAndPoke\Component\MetaCore\Exception\MetaCoreRuntimeException;
13
use PeekAndPoke\Component\Psi\Functions\Unary\Matcher\IsInstanceOf;
14
use PeekAndPoke\Component\Psi\Functions\Unary\Matcher\IsNotInstanceOf;
15
use PeekAndPoke\Component\Psi\Psi;
16
use phpDocumentor\Reflection\DocBlock;
17
use phpDocumentor\Reflection\DocBlockFactory;
18
use phpDocumentor\Reflection\Types;
19
20
/**
21
 * DefaultPropertyMapper
22
 *
23
 * @author Karsten J. Gerber <[email protected]>
24
 */
25
class DefaultPropertyMapper implements PropertyMapper
26
{
27
    /** @var DocBlockFactory */
28
    private $docBlockFactory;
29
30 43
    public function __construct()
31
    {
32 43
        $this->docBlockFactory = DocBlockFactory::createInstance();
33 43
    }
34
35
    /**
36
     * @param Builder             $builder
37
     * @param \ReflectionProperty $property
38
     *
39
     * @return Property
40
     */
41 10
    public function mapProperty(Builder $builder, \ReflectionProperty $property)
42
    {
43 10
        $docBlock = $this->readDocBlock($property);
44 10
        $varTag   = $this->getVarTag($docBlock);
45
46 9
        return new Property(
47 9
            $property->getName(),
48 9
            $this->mapType($builder, $varTag->getType()),
0 ignored issues
show
Bug introduced by
It seems like $varTag->getType() can be null; however, mapType() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
49 7
            Visibility::fromReflection($property),
50 8
            $this->isNullable($varTag->getType()),
0 ignored issues
show
Bug introduced by
It seems like $varTag->getType() can be null; however, isNullable() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
51 8
            new DomainModel\Docs\Doc(
52 8
                (string) $docBlock->getSummary(),
53 7
                (string) $docBlock->getDescription()
54 7
            )
55 8
        );
56
    }
57
58
    /**
59
     * @param \ReflectionProperty $property
60
     *
61
     * @return DocBlock
62
     */
63 10
    protected function readDocBlock(\ReflectionProperty $property)
64
    {
65
        // get the Class OR Trait that really defines the property -> this is needed for the DocBlock reader
66 10
        $declaringClass  = Builder::getRealDeclaringClass($property);
67 10
        $docBlockContext = (new Types\ContextFactory())->createFromReflector($declaringClass);
68
69 10
        return $this->docBlockFactory->create($property->getDocComment(), $docBlockContext);
70
    }
71
72
    /**
73
     * @param DocBlock $docBlock
74
     *
75
     * @return DocBlock\Tags\Var_
76
     */
77 10
    protected function getVarTag(DocBlock $docBlock)
78
    {
79 10
        $varTags = $docBlock->getTagsByName('var');
80
81 10
        if (count($varTags) === 0) {
82 1
            throw MetaCoreRuntimeException::noVarTagFound();
83
        }
84
85 9
        return $varTags[0];
86
    }
87
88
89
    /**
90
     * @param \phpDocumentor\Reflection\Type $type
91
     *
92
     * @return bool
93
     */
94 7
    protected function isNullable(\phpDocumentor\Reflection\Type $type)
95
    {
96 7
        if ($type instanceof Types\Null_) {
97 1
            return true;
98
        }
99
100 7
        if ($type instanceof Types\Compound) {
101
102 1
            return Psi::it($this->getCompoundChildren($type))
103 1
                       ->filter(new IsInstanceOf(Types\Null_::class))
104 1
                       ->count() > 0;
105
        }
106
107 7
        return false;
108
    }
109
110
    /**
111
     * @param Types\Compound $compound
112
     *
113
     * @return \phpDocumentor\Reflection\Type[]
114
     */
115 2
    protected function getCompoundChildren(Types\Compound $compound)
116
    {
117 2
        $reflect = new \ReflectionClass(Types\Compound::class);
118 2
        $prop    = $reflect->getProperty('types');
119 2
        $prop->setAccessible(true);
120
121 2
        return $prop->getValue($compound);
122
    }
123
124
    /**
125
     * @param Builder                        $builder
126
     * @param \phpDocumentor\Reflection\Type $type
127
     *
128
     * @return TypeRef
129
     */
130 9
    private function mapType(Builder $builder, \phpDocumentor\Reflection\Type $type)
131
    {
132
        ////  MULTIPLE TYPE-HINTS  (COMPOUND)  //////////////////////////////////////////////////////////////////////
133
134
        // If there are multiple type hints we try to find the first one that is not NULL_
135 9
        if ($type instanceof Types\Compound) {
136 2
            $type = Psi::it($this->getCompoundChildren($type))
137 2
                ->filter(new IsNotInstanceOf(Types\Null_::class))
138 2
                ->filter(new IsInstanceOf(\phpDocumentor\Reflection\Type::class))
139 2
                ->getFirst();
140
141 2
            if ($type === null) {
142 1
                throw MetaCoreRuntimeException::compoundTypeWithNullsOnly();
143
            }
144
145 1
            return $this->mapType($builder, $type);
146
        }
147
148
        ////  BASIC TYPES  //////////////////////////////////////////////////////////////////////////////////////////
149
150 8
        if ($type instanceof Types\Boolean) {
151 1
            return Type::boolean()->ref();
152
        }
153 8
        if ($type instanceof Types\Float_) {
154 1
            return Type::float()->ref();
155
        }
156 8
        if ($type instanceof Types\Mixed) {
157 1
            return Type::any()->ref();
158
        }
159 8
        if ($type instanceof Types\Integer) {
160 5
            return Type::int()->ref();
161
        }
162 4
        if ($type instanceof Types\String_) {
163 3
            return Type::string()->ref();
164
        }
165 2
        if ($type instanceof Types\Scalar) {
166 1
            return Type::string()->ref();
167
        }
168
169
        ////  ARRAYS   ////////////////////////////////////////////////////////////////////////////////////////////////
170
171 2
        if ($type instanceof Types\Array_) {
172 1
            return Type::map(
173 1
                Type::string()->ref(),
174 1
                $this->mapType($builder, $type->getValueType())
175 1
            )->ref();
176
        }
177
178
        ////  OBJECTS  ////////////////////////////////////////////////////////////////////////////////////////////////
179
180 2
        if ($type instanceof Types\Object_) {
181
182
            // In this case we saw something like
183
            // @var object
184 2
            if ($type->getFqsen() === null) {
185 1
                return Type::any()->ref();
186
            }
187
188
            try {
189 2
                $class = new \ReflectionClass((string) $type);
190 2
            } catch (\ReflectionException $e) {
191 1
                throw MetaCoreRuntimeException::unknownType($e);
192
            }
193
194
            // do the RECURSION for another real type
195 1
            return $builder->buildForClass($class)->ref();
196
        }
197
198
        ////  SPECIALS  ////////////////////////////////////////////////////////////////////////////////////////////////
199
200 1
        if ($type instanceof Types\Null_) {
201 1
            return Type::any()->ref();
202
        }
203
204
        // we default to this one:
205
206 1
        return Type::any()->ref();
207
    }
208
}
209