Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
0.00% covered (danger)
0.00%
0 / 1
25.00% covered (danger)
25.00%
1 / 4
CRAP
89.06% covered (warning)
89.06%
57 / 64
PhpDocTypeHelper
0.00% covered (danger)
0.00%
0 / 1
25.00% covered (danger)
25.00%
1 / 4
29.03
89.06% covered (warning)
89.06%
57 / 64
 getTypes
0.00% covered (danger)
0.00%
0 / 1
10
96.30% covered (success)
96.30%
26 / 27
 createType
0.00% covered (danger)
0.00%
0 / 1
9.06
90.91% covered (success)
90.91%
20 / 22
 normalizeType
0.00% covered (danger)
0.00%
0 / 1
8.81
66.67% covered (warning)
66.67%
8 / 12
 getPhpTypeAndClass
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
1<?php
2
3/*
4 * This file is part of the Symfony package.
5 *
6 * (c) Fabien Potencier <fabien@symfony.com>
7 *
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
10 */
11
12namespace Symfony\Component\PropertyInfo\Util;
13
14use phpDocumentor\Reflection\Type as DocType;
15use phpDocumentor\Reflection\Types\Collection;
16use phpDocumentor\Reflection\Types\Compound;
17use phpDocumentor\Reflection\Types\Null_;
18use phpDocumentor\Reflection\Types\Nullable;
19use Symfony\Component\PropertyInfo\Type;
20
21/**
22 * Transforms a php doc type to a {@link Type} instance.
23 *
24 * @author K√©vin Dunglas <dunglas@gmail.com>
25 * @author Guilhem N. <egetick@gmail.com>
26 */
27final class PhpDocTypeHelper
28{
29    /**
30     * Creates a {@see Type} from a PHPDoc type.
31     *
32     * @return Type[]
33     */
34    public function getTypes(DocType $varType): array
35    {
36        $types = [];
37        $nullable = false;
38
39        if ($varType instanceof Nullable) {
40            $nullable = true;
41            $varType = $varType->getActualType();
42        }
43
44        if (!$varType instanceof Compound) {
45            if ($varType instanceof Null_) {
46                $nullable = true;
47            }
48
49            $type = $this->createType($varType, $nullable);
50            if (null !== $type) {
51                $types[] = $type;
52            }
53
54            return $types;
55        }
56
57        $varTypes = [];
58        for ($typeIndex = 0; $varType->has($typeIndex); ++$typeIndex) {
59            $type = $varType->get($typeIndex);
60
61            // If null is present, all types are nullable
62            if ($type instanceof Null_) {
63                $nullable = true;
64                continue;
65            }
66
67            if ($type instanceof Nullable) {
68                $nullable = true;
69                $type = $type->getActualType();
70            }
71
72            $varTypes[] = $type;
73        }
74
75        foreach ($varTypes as $varType) {
76            $type = $this->createType($varType, $nullable);
77            if (null !== $type) {
78                $types[] = $type;
79            }
80        }
81
82        return $types;
83    }
84
85    /**
86     * Creates a {@see Type} from a PHPDoc type.
87     */
88    private function createType(DocType $type, bool $nullable, string $docType = null): ?Type
89    {
90        $docType = $docType ?? (string) $type;
91
92        if ($type instanceof Collection) {
93            list($phpType, $class) = $this->getPhpTypeAndClass((string) $type->getFqsen());
94
95            $key = $this->getTypes($type->getKeyType());
96            $value = $this->getTypes($type->getValueType());
97
98            // More than 1 type returned means it is a Compound type, which is
99            // not handled by Type, so better use a null value.
100            $key = 1 === \count($key) ? $key[0] : null;
101            $value = 1 === \count($value) ? $value[0] : null;
102
103            return new Type($phpType, $nullable, $class, true, $key, $value);
104        }
105
106        // Cannot guess
107        if (!$docType || 'mixed' === $docType) {
108            return null;
109        }
110
111        if ('[]' === substr($docType, -2)) {
112            if ('mixed[]' === $docType) {
113                $collectionKeyType = null;
114                $collectionValueType = null;
115            } else {
116                $collectionKeyType = new Type(Type::BUILTIN_TYPE_INT);
117                $collectionValueType = $this->createType($type, false, substr($docType, 0, -2));
118            }
119
120            return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, $collectionKeyType, $collectionValueType);
121        }
122
123        $docType = $this->normalizeType($docType);
124        list($phpType, $class) = $this->getPhpTypeAndClass($docType);
125
126        if ('array' === $docType) {
127            return new Type(Type::BUILTIN_TYPE_ARRAY, $nullable, null, true, null, null);
128        }
129
130        return new Type($phpType, $nullable, $class);
131    }
132
133    private function normalizeType(string $docType): string
134    {
135        switch ($docType) {
136            case 'integer':
137                return 'int';
138
139            case 'boolean':
140                return 'bool';
141
142            // real is not part of the PHPDoc standard, so we ignore it
143            case 'double':
144                return 'float';
145
146            case 'callback':
147                return 'callable';
148
149            case 'void':
150                return 'null';
151
152            default:
153                return $docType;
154        }
155    }
156
157    private function getPhpTypeAndClass(string $docType): array
158    {
159        if (\in_array($docType, Type::$builtinTypes)) {
160            return [$docType, null];
161        }
162
163        return ['object', substr($docType, 1)];
164    }
165}