Improve ParserFactory version targeting

Most users will want to pick createForNewestSupportedVersion()
or getForHostVersion(). The currently default is the former,
which can lead to unwanted surprised due to PHP BC breaks for
users that actually want the latter. Make this choice more
explicit.
This commit is contained in:
Nikita Popov 2022-06-12 18:54:51 +02:00
parent d3d1297c0d
commit 55f29b152c
4 changed files with 118 additions and 10 deletions

View File

@ -22,6 +22,36 @@ However, some aspects of PHP 5 parsing are no longer supported:
* Declarations of the form `global $$var[0]` are not supported in PHP 7 and will cause a parse error. In error recovery mode, it is possible to continue parsing after such declarations.
* The PHP 7 parser will accept many constructs that are not valid in PHP 5. However, this was also true of the dedicated PHP 5 parser.
### Changes to the parser factory
The `ParserFactory::create()` method is deprecated in favor of three new methods that provide more fine-grained control over the PHP version being targeted:
* `createForNewestSupportedVersion()`: Use this if you don't know the PHP version of the code you're parsing. It's better to assume a too new version than a too old one.
* `createForHostVersion()`: Use this if you're parsing code for the PHP version you're running on.
* `createForVersion()`: Use this if you know the PHP version of the code you want to parse.
In all cases, the PHP version is a fairly weak hint that is only used on a best-effort basis. The parser will usually accept code for newer versions if it does not have any backwards-compatibility implications.
For example, if you specify version `"8.0"`, then `class ReadOnly {}` is treated as a valid class declaration, while using `public readonly int $prop` will lead to a parse error. However, `final public const X = Y;` will be accepted in both cases.
```
use PhpParser\ParserFactory;
$factory = new ParserFactory;
# Before
$parser = $factory->create(ParserFactory::PREFER_PHP7);
# After (this is roughly equivalent to PREFER_PHP7 behavior)
$parser = $factory->createForNewestSupportedVersion();
# Or
$parser = $factory->createForHostVersion();
# Before
$parser = $factory->create(ParserFactory::ONLY_PHP5);
# After (supported on a best-effort basis)
$parser = $factory->createForVersion("5.6");
```
### Changes to the default pretty printer
A number of changes to the standard pretty printer have been made, to make it match contemporary coding style conventions (and in particular PSR-12). Options to restore the previous behavior are not provided, but it is possible to override the formatting methods (such as `pStmt_ClassMethod`) with your preferred formatting.

View File

@ -2,6 +2,8 @@
namespace PhpParser;
use PhpParser\Parser\Php7;
class ParserFactory
{
const PREFER_PHP7 = 1;
@ -15,8 +17,10 @@ class ParserFactory
* @param array $parserOptions Parser options. See ParserAbstract::__construct() argument
*
* @return Parser The parser instance
*
* @deprecated Use createForVersion(), createForNewestSupportedVersion() or createForHostVersion() instead.
*/
public function create(int $kind, Lexer $lexer = null, array $parserOptions = []) : Parser {
public function create(int $kind, Lexer $lexer = null, array $parserOptions = []): Parser {
if (null === $lexer) {
$lexer = new Lexer\Emulative();
}
@ -30,4 +34,50 @@ class ParserFactory
);
}
}
/**
* Create a parser targeting the given version on a best-effort basis. The parser will generally
* accept code for the newest supported version, but will try to accommodate code that becomes
* invalid in newer versions or changes in interpretation.
*/
public function createForVersion(string $version, array $lexerOptions = [], array $parserOptions = []): Parser {
if ($version === $this->getHostVersion()) {
$lexer = new Lexer($lexerOptions);
} else {
$lexer = new Lexer\Emulative($lexerOptions + ['phpVersion' => $version]);
}
return new Php7($lexer, $parserOptions + ['phpVersion' => $version]);
}
/**
* Create a parser targeting the newest version supported by this library. Code for older
* versions will be accepted if there have been no relevant backwards-compatibility breaks in
* PHP.
*/
public function createForNewestSupportedVersion(array $lexerOptions = [], array $parserOptions = []): Parser {
return $this->createForVersion($this->getNewestSupportedVersion(), $lexerOptions, $parserOptions);
}
/**
* Create a parser targeting the host PHP version, that is the PHP version we're currently
* running on. This parser will not use any token emulation.
*/
public function createForHostVersion(array $lexerOptions = [], array $parserOptions = []): Parser {
return $this->createForVersion($this->getHostVersion(), $lexerOptions, $parserOptions);
}
/**
* Get the newest PHP version supported by this library. Support for this version may be partial,
* if it is still under development.
*/
public function getNewestSupportedVersion(): string {
return '8.2';
}
/**
* Get the host PHP version, that is the PHP version we're currently running on.
*/
public function getHostVersion(): string {
return \PHP_MAJOR_VERSION . '.' . \PHP_MINOR_VERSION;
}
}

View File

@ -38,15 +38,15 @@ class CodeParsingTest extends CodeTestAbstract
}
public function createParser(?string $version): Parser {
$lexer = new Lexer\Emulative(['usedAttributes' => [
'startLine', 'endLine',
'startFilePos', 'endFilePos',
'startTokenPos', 'endTokenPos',
'comments'
]]);
return new Parser\Php7($lexer, [
'phpVersion' => $version,
]);
$factory = new ParserFactory();
return $factory->createForVersion(
$version ?? $factory->getNewestSupportedVersion(),
['usedAttributes' => [
'startLine', 'endLine',
'startFilePos', 'endFilePos',
'startTokenPos', 'endTokenPos',
'comments'
]]);
}
// Must be public for updateTests.php

View File

@ -0,0 +1,28 @@
Readonly as class name
-----
<?php
class ReadOnly {}
-----
Syntax error, unexpected T_READONLY, expecting T_STRING from 2:7 to 2:14
array(
)
-----
<?php
class ReadOnly {}
-----
!!version=7.4
array(
0: Stmt_Class(
attrGroups: array(
)
flags: 0
name: Identifier(
name: ReadOnly
)
extends: null
implements: array(
)
stmts: array(
)
)
)