Skip to content

Commit e7ead46

Browse files
laurentjjaapio
authored andcommitted
Rework the parsing of types, with an iterator
It will allow to parse PSR-5 types, as the syntax is more complex and it may contains sub-groups of types
1 parent 9c97770 commit e7ead46

File tree

1 file changed

+82
-62
lines changed

1 file changed

+82
-62
lines changed

src/TypeResolver.php

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,12 @@ final class TypeResolver
2727
/** @var string Definition of the NAMESPACE operator in PHP */
2828
const OPERATOR_NAMESPACE = '\\';
2929

30+
/** @var integer the iterator parser is inside a compound context */
31+
const PARSER_IN_COMPOUND = 0;
32+
33+
/** @var integer the iterator parser is inside a nullable expression context */
34+
const PARSER_IN_NULLABLE = 1;
35+
3036
/** @var string[] List of recognized keywords and unto which Value Object they map */
3137
private $keywords = array(
3238
'string' => Types\String_::class,
@@ -84,7 +90,7 @@ public function __construct(FqsenResolver $fqsenResolver = null)
8490
* @uses Context::getNamespaceAliases() to check whether the first part of the relative type name should not be
8591
* replaced with another namespace.
8692
*
87-
* @return Type|null
93+
* @return Type
8894
*/
8995
public function resolve($type, Context $context = null)
9096
{
@@ -103,13 +109,83 @@ public function resolve($type, Context $context = null)
103109
$context = new Context('');
104110
}
105111

112+
// split the type string into tokens `|`, `?` and type names
113+
$tokens = preg_split('/(\||\?)/', $type, -1, PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
114+
$tokenIterator = new \ArrayIterator($tokens);
115+
return $this->parseTypes($tokenIterator, $context, self::PARSER_IN_COMPOUND);
116+
}
117+
118+
/**
119+
* Analyse each tokens and creates types
120+
*
121+
* @param \ArrayIterator $tokens the iterator on tokens
122+
* @param Context $context
123+
* @param integer $parserContext on of self::PARSER_* constants, indicating
124+
* the context where we are in the parsing
125+
*
126+
* @return Type
127+
*/
128+
private function parseTypes(\ArrayIterator $tokens, Context $context, $parserContext)
129+
{
130+
$types = array();
131+
$token = '';
132+
while ($tokens->valid()) {
133+
$token = $tokens->current();
134+
if ($parserContext === self::PARSER_IN_COMPOUND && $token == '|') {
135+
if (count($types) == 0) {
136+
throw new \RuntimeException(
137+
'A type is missing before a type separator'
138+
);
139+
}
140+
$tokens->next();
141+
} else if ($parserContext === self::PARSER_IN_COMPOUND
142+
&& $token == '?'
143+
) {
144+
$tokens->next();
145+
$type = $this->parseTypes($tokens, $context, self::PARSER_IN_NULLABLE);
146+
$types[] = new Nullable($type);
147+
} else {
148+
$type = $this->resolveSingleType($token, $context);
149+
$tokens->next();
150+
if ($parserContext === self::PARSER_IN_NULLABLE) {
151+
return $type;
152+
}
153+
$types[] = $type;
154+
}
155+
}
156+
157+
if ($token == '|') {
158+
throw new \RuntimeException(
159+
'A type is missing after a type separator'
160+
);
161+
}
162+
if (count($types) == 0) {
163+
if ($parserContext == self::PARSER_IN_NULLABLE) {
164+
throw new \RuntimeException(
165+
'A type is missing after a nullable character'
166+
);
167+
}
168+
throw new \RuntimeException(
169+
'No types in a compound list'
170+
);
171+
} else if (count($types) == 1) {
172+
return $types[0];
173+
}
174+
return new Compound($types);
175+
}
176+
177+
/**
178+
* resolve the given type into a type object
179+
*
180+
* @param string $type the type string, representing a single type
181+
* @param Context $context
182+
* @return Type|Array_|Object_
183+
*/
184+
private function resolveSingleType($type, Context $context)
185+
{
106186
switch (true) {
107-
case $this->isNullableType($type):
108-
return $this->resolveNullableType($type, $context);
109187
case $this->isKeyword($type):
110188
return $this->resolveKeyword($type);
111-
case ($this->isCompoundType($type)):
112-
return $this->resolveCompoundType($type, $context);
113189
case $this->isTypedArray($type):
114190
return $this->resolveTypedArray($type, $context);
115191
case $this->isFqsen($type):
@@ -200,30 +276,6 @@ private function isFqsen($type)
200276
return strpos($type, self::OPERATOR_NAMESPACE) === 0;
201277
}
202278

203-
/**
204-
* Tests whether the given type is a compound type (i.e. `string|int`).
205-
*
206-
* @param string $type
207-
*
208-
* @return bool
209-
*/
210-
private function isCompoundType($type)
211-
{
212-
return strpos($type, '|') !== false;
213-
}
214-
215-
/**
216-
* Test whether the given type is a nullable type (i.e. `?string`)
217-
*
218-
* @param string $type
219-
*
220-
* @return bool
221-
*/
222-
private function isNullableType($type)
223-
{
224-
return $type[0] === '?';
225-
}
226-
227279
/**
228280
* Resolves the given typed array string (i.e. `string[]`) into an Array object with the right types set.
229281
*
@@ -234,7 +286,7 @@ private function isNullableType($type)
234286
*/
235287
private function resolveTypedArray($type, Context $context)
236288
{
237-
return new Array_($this->resolve(substr($type, 0, -2), $context));
289+
return new Array_($this->resolveSingleType(substr($type, 0, -2), $context));
238290
}
239291

240292
/**
@@ -263,36 +315,4 @@ private function resolveTypedObject($type, Context $context = null)
263315
{
264316
return new Object_($this->fqsenResolver->resolve($type, $context));
265317
}
266-
267-
/**
268-
* Resolves a compound type (i.e. `string|int`) into the appropriate Type objects or FQSEN.
269-
*
270-
* @param string $type
271-
* @param Context $context
272-
*
273-
* @return Compound
274-
*/
275-
private function resolveCompoundType($type, Context $context)
276-
{
277-
$types = [];
278-
279-
foreach (explode('|', $type) as $part) {
280-
$types[] = $this->resolve($part, $context);
281-
}
282-
283-
return new Compound($types);
284-
}
285-
286-
/**
287-
* Resolve nullable types (i.e. `?string`) into a Nullable type wrapper
288-
*
289-
* @param string $type
290-
* @param Context $context
291-
*
292-
* @return Nullable
293-
*/
294-
private function resolveNullableType($type, Context $context)
295-
{
296-
return new Nullable($this->resolve(ltrim($type, '?'), $context));
297-
}
298318
}

0 commit comments

Comments
 (0)