Completed
Push — master ( 4b8596...171883 )
by Karsten
02:14
created

DefaultPropertyMapper::__construct()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 3
CRAP Score 1

Importance

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