Skip to content

Commit f99a96e

Browse files
committed
Introduce ErrorHandler
Add ErrorHandler interface, as well as ErrorHandler\Throwing and ErrorHandler\Collecting. The error handler is passed to Parser::parse(). This supersedes the throwOnError option. NameResolver now accepts an ErrorHandler in the ctor.
1 parent 90834bf commit f99a96e

File tree

15 files changed

+204
-126
lines changed

15 files changed

+204
-126
lines changed

lib/PhpParser/ErrorHandler.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
namespace PhpParser;
4+
5+
interface ErrorHandler
6+
{
7+
/**
8+
* Handle an error generated during lexing, parsing or some other operation.
9+
*
10+
* @param Error $error The error that needs to be handled
11+
*/
12+
public function handleError(Error $error);
13+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<?php
2+
3+
namespace PhpParser\ErrorHandler;
4+
5+
use PhpParser\Error;
6+
use PhpParser\ErrorHandler;
7+
8+
/**
9+
* Error handler that collects all errors into an array.
10+
*
11+
* This allows graceful handling of errors.
12+
*/
13+
class Collecting implements ErrorHandler
14+
{
15+
/** @var Error[] Collected errors */
16+
private $errors = [];
17+
18+
public function handleError(Error $error) {
19+
$this->errors[] = $error;
20+
}
21+
22+
/**
23+
* Get collected errors.
24+
*
25+
* @return Error[]
26+
*/
27+
public function getErrors() {
28+
return $this->errors;
29+
}
30+
31+
/**
32+
* Check whether there are any errors.
33+
*
34+
* @return bool
35+
*/
36+
public function hasErrors() {
37+
return !empty($this->errors);
38+
}
39+
40+
/**
41+
* Reset/clear collected errors.
42+
*/
43+
public function clearErrors() {
44+
$this->errors = [];
45+
}
46+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?php
2+
3+
namespace PhpParser\ErrorHandler;
4+
5+
use PhpParser\Error;
6+
use PhpParser\ErrorHandler;
7+
8+
/**
9+
* Error handler that handles all errors by throwing them.
10+
*
11+
* This is the default strategy used by all components.
12+
*/
13+
class Throwing implements ErrorHandler
14+
{
15+
public function handleError(Error $error) {
16+
throw $error;
17+
}
18+
}

lib/PhpParser/Lexer.php

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ class Lexer
99
{
1010
protected $code;
1111
protected $tokens;
12-
protected $errors;
1312
protected $pos;
1413
protected $line;
1514
protected $filePos;
@@ -54,13 +53,18 @@ public function __construct(array $options = array()) {
5453
* the getErrors() method.
5554
*
5655
* @param string $code The source code to lex
56+
* @param ErrorHandler|null $errorHandler Error handler to use for lexing errors. Defaults to
57+
* ErrorHandler\Throwing
5758
*/
58-
public function startLexing($code) {
59+
public function startLexing($code, ErrorHandler $errorHandler = null) {
60+
if (null === $errorHandler) {
61+
$errorHandler = new ErrorHandler\Throwing();
62+
}
63+
5964
$this->code = $code; // keep the code around for __halt_compiler() handling
6065
$this->pos = -1;
6166
$this->line = 1;
6267
$this->filePos = 0;
63-
$this->errors = [];
6468

6569
// If inline HTML occurs without preceding code, treat it as if it had a leading newline.
6670
// This ensures proper composability, because having a newline is the "safe" assumption.
@@ -70,7 +74,7 @@ public function startLexing($code) {
7074

7175
$this->resetErrors();
7276
$this->tokens = @token_get_all($code);
73-
$this->handleErrors();
77+
$this->handleErrors($errorHandler);
7478

7579
if (false !== $scream) {
7680
ini_set('xdebug.scream', $scream);
@@ -88,7 +92,7 @@ protected function resetErrors() {
8892
}
8993
}
9094

91-
private function handleInvalidCharacterRange($start, $end, $line) {
95+
private function handleInvalidCharacterRange($start, $end, $line, ErrorHandler $errorHandler) {
9296
for ($i = $start; $i < $end; $i++) {
9397
$chr = $this->code[$i];
9498
if ($chr === 'b' || $chr === 'B') {
@@ -105,12 +109,12 @@ private function handleInvalidCharacterRange($start, $end, $line) {
105109
);
106110
}
107111

108-
$this->errors[] = new Error($errorMsg, [
112+
$errorHandler->handleError(new Error($errorMsg, [
109113
'startLine' => $line,
110114
'endLine' => $line,
111115
'startFilePos' => $i,
112116
'endFilePos' => $i,
113-
]);
117+
]));
114118
}
115119
}
116120

@@ -132,7 +136,7 @@ private function errorMayHaveOccurred() {
132136
&& false === strpos($error['message'], 'Undefined variable');
133137
}
134138

135-
protected function handleErrors() {
139+
protected function handleErrors(ErrorHandler $errorHandler) {
136140
if (!$this->errorMayHaveOccurred()) {
137141
return;
138142
}
@@ -151,7 +155,8 @@ protected function handleErrors() {
151155
if (substr($this->code, $filePos, $tokenLen) !== $tokenValue) {
152156
// Something is missing, must be an invalid character
153157
$nextFilePos = strpos($this->code, $tokenValue, $filePos);
154-
$this->handleInvalidCharacterRange($filePos, $nextFilePos, $line);
158+
$this->handleInvalidCharacterRange(
159+
$filePos, $nextFilePos, $line, $errorHandler);
155160
$filePos = $nextFilePos;
156161
}
157162

@@ -163,32 +168,33 @@ protected function handleErrors() {
163168
if (substr($this->code, $filePos, 2) === '/*') {
164169
// Unlike PHP, HHVM will drop unterminated comments entirely
165170
$comment = substr($this->code, $filePos);
166-
$this->errors[] = new Error('Unterminated comment', [
171+
$errorHandler->handleError(new Error('Unterminated comment', [
167172
'startLine' => $line,
168173
'endLine' => $line + substr_count($comment, "\n"),
169174
'startFilePos' => $filePos,
170175
'endFilePos' => $filePos + \strlen($comment),
171-
]);
176+
]));
172177

173178
// Emulate the PHP behavior
174179
$isDocComment = isset($comment[3]) && $comment[3] === '*';
175180
$this->tokens[] = [$isDocComment ? T_DOC_COMMENT : T_COMMENT, $comment, $line];
176181
} else {
177182
// Invalid characters at the end of the input
178-
$this->handleInvalidCharacterRange($filePos, \strlen($this->code), $line);
183+
$this->handleInvalidCharacterRange(
184+
$filePos, \strlen($this->code), $line, $errorHandler);
179185
}
180186
return;
181187
}
182188

183189
// Check for unterminated comment
184190
$lastToken = $this->tokens[count($this->tokens) - 1];
185191
if ($this->isUnterminatedComment($lastToken)) {
186-
$this->errors[] = new Error('Unterminated comment', [
192+
$errorHandler->handleError(new Error('Unterminated comment', [
187193
'startLine' => $line - substr_count($lastToken[1], "\n"),
188194
'endLine' => $line,
189195
'startFilePos' => $filePos - \strlen($lastToken[1]),
190196
'endFilePos' => $filePos,
191-
]);
197+
]));
192198
}
193199
}
194200

@@ -302,15 +308,6 @@ public function getTokens() {
302308
return $this->tokens;
303309
}
304310

305-
/**
306-
* Returns errors that occurred during lexing.
307-
*
308-
* @return Error[] Array of lexer errors
309-
*/
310-
public function getErrors() {
311-
return $this->errors;
312-
}
313-
314311
/**
315312
* Handles __halt_compiler() by returning the text after it.
316313
*

lib/PhpParser/Lexer/Emulative.php

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpParser\Lexer;
44

5+
use PhpParser\ErrorHandler;
56
use PhpParser\Parser\Tokens;
67

78
class Emulative extends \PhpParser\Lexer
@@ -50,10 +51,10 @@ public function __construct(array $options = array()) {
5051
$this->tokenMap[self::T_POW_EQUAL] = Tokens::T_POW_EQUAL;
5152
}
5253

53-
public function startLexing($code) {
54+
public function startLexing($code, ErrorHandler $errorHandler = null) {
5455
$this->inObjectAccess = false;
5556

56-
parent::startLexing($code);
57+
parent::startLexing($code, $errorHandler);
5758
if ($this->requiresEmulation($code)) {
5859
$this->emulateTokens();
5960
}

lib/PhpParser/NodeTraverser.php

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44

55
class NodeTraverser implements NodeTraverserInterface
66
{
7-
/**
8-
* @var NodeVisitor[] Visitors
9-
*/
7+
/** @var NodeVisitor[] Visitors */
108
protected $visitors;
119

1210
/**

lib/PhpParser/NodeVisitor/NameResolver.php

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace PhpParser\NodeVisitor;
44

5+
use PhpParser\ErrorHandler;
56
use PhpParser\NodeVisitorAbstract;
67
use PhpParser\Error;
78
use PhpParser\Node;
@@ -18,6 +19,18 @@ class NameResolver extends NodeVisitorAbstract
1819
/** @var array Map of format [aliasType => [aliasName => originalName]] */
1920
protected $aliases;
2021

22+
/** @var ErrorHandler Error handler */
23+
protected $errorHandler;
24+
25+
/**
26+
* Constructs a name resolution visitor.
27+
*
28+
* @param ErrorHandler|null $errorHandler Error handler
29+
*/
30+
public function __construct(ErrorHandler $errorHandler = null) {
31+
$this->errorHandler = $errorHandler ?: new ErrorHandler\Throwing;
32+
}
33+
2134
public function beforeTraverse(array $nodes) {
2235
$this->resetState();
2336
}
@@ -132,13 +145,14 @@ protected function addAlias(Stmt\UseUse $use, $type, Name $prefix = null) {
132145
Stmt\Use_::TYPE_CONSTANT => 'const ',
133146
);
134147

135-
throw new Error(
148+
$this->errorHandler->handleError(new Error(
136149
sprintf(
137150
'Cannot use %s%s as %s because the name is already in use',
138151
$typeStringMap[$type], $name, $use->alias
139152
),
140-
$use->getLine()
141-
);
153+
$use->getAttributes()
154+
));
155+
return;
142156
}
143157

144158
$this->aliases[$type][$aliasName] = $name;
@@ -160,10 +174,10 @@ protected function resolveClassName(Name $name) {
160174
// don't resolve special class names
161175
if (in_array(strtolower($name->toString()), array('self', 'parent', 'static'))) {
162176
if (!$name->isUnqualified()) {
163-
throw new Error(
177+
$this->errorHandler->handleError(new Error(
164178
sprintf("'\\%s' is an invalid class name", $name->toString()),
165-
$name->getLine()
166-
);
179+
$name->getAttributes()
180+
));
167181
}
168182

169183
return $name;

lib/PhpParser/Parser.php

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,18 +7,11 @@ interface Parser {
77
* Parses PHP code into a node tree.
88
*
99
* @param string $code The source code to parse
10+
* @param ErrorHandler|null $errorHandler Error handler to use for lexer/parser errors, defaults
11+
* to ErrorHandler\Throwing.
1012
*
1113
* @return Node[]|null Array of statements (or null if the 'throwOnError' option is disabled and the parser was
1214
* unable to recover from an error).
1315
*/
14-
public function parse($code);
15-
16-
/**
17-
* Get array of errors that occurred during the last parse.
18-
*
19-
* This method may only return multiple errors if the 'throwOnError' option is disabled.
20-
*
21-
* @return Error[]
22-
*/
23-
public function getErrors();
16+
public function parse($code, ErrorHandler $errorHandler = null);
2417
}

0 commit comments

Comments
 (0)