Chris@0: Upgrading from PHP-Parser 2.x to 3.0 Chris@0: ==================================== Chris@0: Chris@0: The backwards-incompatible changes in this release may be summarized as follows: Chris@0: Chris@0: * The specific details of the node representation have changed in some cases, primarily to Chris@13: accommodate new PHP 7.1 features. Chris@0: * There have been significant changes to the error recovery implementation. This may affect you, Chris@0: if you used the error recovery mode or have a custom lexer implementation. Chris@0: * A number of deprecated methods were removed. Chris@0: Chris@0: ### PHP version requirements Chris@0: Chris@0: PHP-Parser now requires PHP 5.5 or newer to run. It is however still possible to *parse* PHP 5.2, Chris@0: 5.3 and 5.4 source code, while running on a newer version. Chris@0: Chris@0: ### Changes to the node structure Chris@0: Chris@0: The following changes are likely to require code changes if the respective nodes are used: Chris@0: Chris@0: * The `List` subnode `vars` has been renamed to `items` and now contains `ArrayItem`s instead of Chris@0: plain variables. Chris@0: * The `Catch` subnode `type` has been renamed to `types` and is now an array of `Name`s. Chris@0: * The `TryCatch` subnode `finallyStmts` has been replaced with a `finally` subnode that holds an Chris@0: explicit `Finally` node. Chris@0: * The `type` subnode on `Class`, `ClassMethod` and `Property` has been renamed to `flags`. The Chris@0: `type` subnode has retained for backwards compatibility and is populated to the same value as Chris@0: `flags`. However, writes to `type` will not update `flags` and use of `type` is discouraged. Chris@0: Chris@0: The following changes are unlikely to require code changes: Chris@0: Chris@0: * The `ClassConst` constructor changed to accept an additional `flags` subnode. Chris@0: * The `Trait` constructor now has the same form as the `Class` and `Interface` constructors: It Chris@0: takes an array of subnodes. Unlike classes/interfaces, traits can only have a `stmts` subnode. Chris@0: * The `Array` subnode `items` may now contain `null` elements (due to destructuring). Chris@0: * `void` and `iterable` types are now stored as strings if the PHP 7 parser is used. Previously Chris@0: these would have been represented as `Name` instances. Chris@0: Chris@0: ### Changes to error recovery mode Chris@0: Chris@0: Previously, error recovery mode was enabled by setting the `throwOnError` option to `false` when Chris@0: creating the parser, while collected errors were retrieved using the `getErrors()` method: Chris@0: Chris@0: ```php Chris@0: $lexer = ...; Chris@0: $parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer, [ Chris@0: 'throwOnError' => true, Chris@0: ]); Chris@0: Chris@0: $stmts = $parser->parse($code); Chris@0: $errors = $parser->getErrors(); Chris@0: if ($errors) { Chris@0: handleErrors($errors); Chris@0: } Chris@0: processAst($stmts); Chris@0: ``` Chris@0: Chris@0: Both the `throwOnError` option and the `getErrors()` method have been removed in PHP-Parser 3.0. Chris@0: Instead an instance of `ErrorHandler\Collecting` should be passed to the `parse()` method: Chris@0: Chris@0: ```php Chris@0: $lexer = ...; Chris@0: $parser = (new ParserFactory)->create(ParserFactor::ONLY_PHP7, $lexer); Chris@0: Chris@0: $errorHandler = new ErrorHandler\Collecting; Chris@0: $stmts = $parser->parse($code, $errorHandler); Chris@0: if ($errorHandler->hasErrors()) { Chris@0: handleErrors($errorHandler->getErrors()); Chris@0: } Chris@0: processAst($stmts); Chris@0: ``` Chris@0: Chris@0: #### Multiple parser fallback in error recovery mode Chris@0: Chris@0: As a result of this change, if a `Multiple` parser is used (e.g. through the `ParserFactory` using Chris@0: `PREFER_PHP7` or `PREFER_PHP5`), it will now return the result of the first *non-throwing* parse. As Chris@0: parsing never throws in error recovery mode, the result from the first parser will always be Chris@0: returned. Chris@0: Chris@0: The PHP 7 parser is a superset of the PHP 5 parser, with the exceptions that `=& new` and Chris@0: `global $$foo->bar` are not supported (other differences are in representation only). The PHP 7 Chris@0: parser will be able to recover from the error in both cases. For this reason, this change will Chris@0: likely pass unnoticed if you do not specifically test for this syntax. Chris@0: Chris@0: It is possible to restore the precise previous behavior with the following code: Chris@0: Chris@0: ```php Chris@0: $lexer = ...; Chris@0: $parser7 = new Parser\Php7($lexer); Chris@0: $parser5 = new Parser\Php5($lexer); Chris@0: Chris@0: $errors7 = new ErrorHandler\Collecting(); Chris@0: $stmts7 = $parser7->parse($code, $errors7); Chris@0: if ($errors7->hasErrors()) { Chris@0: $errors5 = new ErrorHandler\Collecting(); Chris@0: $stmts5 = $parser5->parse($code, $errors5); Chris@0: if (!$errors5->hasErrors()) { Chris@0: // If PHP 7 parse has errors but PHP 5 parse has no errors, use PHP 5 result Chris@0: return [$stmts5, $errors5]; Chris@0: } Chris@0: } Chris@0: // If PHP 7 succeeds or both fail use PHP 7 result Chris@0: return [$stmts7, $errors7]; Chris@0: ``` Chris@0: Chris@0: #### Error handling in the lexer Chris@0: Chris@0: In order to support recovery from lexer errors, the signature of the `startLexing()` method changed Chris@0: to optionally accept an `ErrorHandler`: Chris@0: Chris@0: ```php Chris@0: // OLD Chris@0: public function startLexing($code); Chris@0: // NEW Chris@0: public function startLexing($code, ErrorHandler $errorHandler = null); Chris@0: ``` Chris@0: Chris@13: If you use a custom lexer with overridden `startLexing()` method, it needs to be changed to accept Chris@0: the extra parameter. The value should be passed on to the parent method. Chris@0: Chris@0: #### Error checks in node constructors Chris@0: Chris@0: The constructors of certain nodes used to contain additional checks for semantic errors, such as Chris@0: creating a try block without either catch or finally. These checks have been moved from the node Chris@0: constructors into the parser. This allows recovery from such errors, as well as representing the Chris@0: resulting (invalid) AST. Chris@0: Chris@0: This means that certain error conditions are no longer checked for manually constructed nodes. Chris@0: Chris@0: ### Removed methods, arguments, options Chris@0: Chris@0: The following methods, arguments or options have been removed: Chris@0: Chris@0: * `Comment::setLine()`, `Comment::setText()`: Create new `Comment` instances instead. Chris@0: * `Name::set()`, `Name::setFirst()`, `Name::setLast()`, `Name::append()`, `Name::prepend()`: Chris@0: Use `Name::concat()` in combination with `Name::slice()` instead. Chris@0: * `Error::getRawLine()`, `Error::setRawLine()`. Use `Error::getStartLine()` and Chris@0: `Error::setStartLine()` instead. Chris@0: * `Parser::getErrors()`. Use `ErrorHandler\Collecting` instead. Chris@0: * `$separator` argument of `Name::toString()`. Use `strtr()` instead, if you really need it. Chris@0: * `$cloneNodes` argument of `NodeTraverser::__construct()`. Explicitly clone nodes in the visitor Chris@0: instead. Chris@0: * `throwOnError` parser option. Use `ErrorHandler\Collecting` instead. Chris@0: Chris@0: ### Miscellaneous Chris@0: Chris@0: * The `NameResolver` will now resolve unqualified function and constant names in the global Chris@0: namespace into fully qualified names. For example `foo()` in the global namespace resolves to Chris@0: `\foo()`. For names where no static resolution is possible, a `namespacedName` attribute is Chris@0: added now, containing the namespaced variant of the name. Chris@13: * All methods on `PrettyPrinter\Standard` are now protected. Previously most of them were public. Chris@0: The pretty printer should only be invoked using the `prettyPrint()`, `prettyPrintFile()` and Chris@0: `prettyPrintExpr()` methods. Chris@0: * The node dumper now prints numeric values that act as enums/flags in a string representation. Chris@0: If node dumper results are used in tests, updates may be needed to account for this. Chris@0: * The constants on `NameTraverserInterface` have been moved into the `NameTraverser` class. Chris@0: * The emulative lexer now directly postprocesses tokens, instead of using `~__EMU__~` sequences. Chris@0: This changes the protected API of the emulative lexer. Chris@0: * The `Name::slice()` method now returns `null` for empty slices, previously `new Name([])` was Chris@0: used. `Name::concat()` now also supports concatenation with `null`.