-
-
Notifications
You must be signed in to change notification settings - Fork 6
Modernize for PHP 8.x: attributes support, parser/CI/tooling updates, README fixes #69
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4015887
2dcf493
4ec532d
53fd46f
9151385
91941db
21749ea
2736156
36d4690
f13eb62
c40ef74
6edff4c
f303d03
9bbb352
1b22dd3
ea59fa3
edef06f
edbe712
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -17,19 +17,19 @@ jobs: | |
| fail-fast: false | ||
| matrix: | ||
| php: [ | ||
| 7.4, | ||
| 8.0, | ||
| 8.1, | ||
| 8.2 | ||
| 8.2, | ||
| 8.3, | ||
| 8.4 | ||
| ] | ||
| composer: [basic] | ||
| timeout-minutes: 10 | ||
| steps: | ||
| - name: Checkout code | ||
| uses: actions/checkout@3df4ab11eba7bda6032a0b82a6bb43b11571feac # v4 | ||
| uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 | ||
|
|
||
| - name: Setup PHP | ||
| uses: shivammathur/setup-php@2.25.5 | ||
| uses: shivammathur/setup-php@accd6127cb78bee3e8082180cb391013d204ef9f # v2.37.0 | ||
| with: | ||
| php-version: ${{ matrix.php }} | ||
| coverage: xdebug | ||
|
|
@@ -38,21 +38,17 @@ jobs: | |
|
|
||
| - name: Determine composer cache directory | ||
| id: composer-cache | ||
| run: echo "::set-output name=directory::$(composer config cache-dir)" | ||
| run: echo "directory=$(composer config cache-dir)" >> "$GITHUB_OUTPUT" | ||
|
|
||
| - name: Cache composer dependencies | ||
| uses: actions/cache@v3.3.1 | ||
| uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4.3.0 | ||
| with: | ||
| path: ${{ steps.composer-cache.outputs.directory }} | ||
| key: ${{ matrix.php }}-composer-${{ hashFiles('**/composer.lock') }} | ||
| restore-keys: ${{ matrix.php }}-composer- | ||
|
|
||
| - name: Install dependencies | ||
| run: | | ||
| if [[ "${{ matrix.php }}" == "8.0" ]]; then | ||
| composer require phpstan/phpstan --no-update | ||
| fi; | ||
|
|
||
| if [[ "${{ matrix.composer }}" == "lowest" ]]; then | ||
| composer update --prefer-dist --no-interaction --prefer-lowest --prefer-stable | ||
| fi; | ||
|
|
@@ -70,7 +66,7 @@ jobs: | |
|
|
||
| - name: Run phpstan | ||
| continue-on-error: true | ||
| if: ${{ matrix.php == '7.4' }} | ||
| if: ${{ matrix.php == '8.3' }} | ||
| run: | | ||
| php vendor/bin/phpstan analyse | ||
|
Comment on lines
67
to
71
|
||
|
|
||
|
|
@@ -82,13 +78,13 @@ jobs: | |
| php-coveralls --coverage_clover=build/logs/clover.xml -v | ||
|
|
||
| - name: Upload coverage results to Codecov | ||
| uses: codecov/codecov-action@v3 | ||
| uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0 | ||
| with: | ||
| files: build/logs/clover.xml | ||
|
|
||
| - name: Archive logs artifacts | ||
| if: ${{ failure() }} | ||
| uses: actions/upload-artifact@v3 | ||
| uses: actions/upload-artifact@b4b15b8c7c6ac21ea08fcf65892d2ee8f75cf882 # v4.4.3 | ||
| with: | ||
| name: logs_composer-${{ matrix.composer }}_php-${{ matrix.php }} | ||
| path: | | ||
|
|
||
This file was deleted.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace voku\SimplePhpParser\Model; | ||
|
|
||
| /** | ||
| * Represents a single PHP 8.0+ attribute instance. | ||
| */ | ||
| class PHPAttribute | ||
| { | ||
| /** | ||
| * Fully qualified attribute class name. | ||
| */ | ||
| public string $name; | ||
|
|
||
| /** | ||
| * Attribute constructor arguments. | ||
| * | ||
| * @var array<int|string, mixed> | ||
| */ | ||
| public array $arguments = []; | ||
|
|
||
| public function __construct(string $name, array $arguments = []) | ||
| { | ||
| $this->name = $name; | ||
| $this->arguments = $arguments; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -50,13 +50,26 @@ | |
|
|
||
| $this->is_anonymous = $node->isAnonymous(); | ||
|
|
||
| // Extract PHP 8.0+ attributes | ||
| if (!empty($node->attrGroups)) { | ||
| $this->attributes = Utils::extractAttributesFromAstNode($node->attrGroups); | ||
| } | ||
|
|
||
| // PHP < 8.2 raises an uncatchable E_COMPILE_ERROR for certain PHP 8.2+ syntax | ||
| // (standalone true/false/null types, DNF types, readonly class). Similarly, | ||
| // PHP < 8.3 raises an error for PHP 8.3+ syntax (typed class constants). | ||
| // Skip autoloading in those cases; AST data is still read from the node below. | ||
| $canAutoload = (\PHP_VERSION_ID >= 80200 || !self::nodeUsesPHP82PlusSyntax($node)) | ||
| && (\PHP_VERSION_ID >= 80300 || !self::nodeUsesPHP83PlusSyntax($node)); | ||
|
Comment on lines
+62
to
+63
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic to skip autoloading for PHP 8.2+ or 8.3+ syntax on older PHP versions is correct to avoid uncatchable $classExists = \class_exists($this->name, false);
if (!$classExists && $canAutoload) {
try {
if (\class_exists($this->name, true)) {
$classExists = true;
}
} catch (\Throwable $e) {
// nothing
}
} |
||
| $classExists = false; | ||
| try { | ||
| if (\class_exists($this->name, true)) { | ||
| $classExists = true; | ||
| if ($canAutoload) { | ||
| try { | ||
| if (\class_exists($this->name, true)) { | ||
| $classExists = true; | ||
| } | ||
| } catch (\Throwable $e) { | ||
| // nothing | ||
| } | ||
| } catch (\Exception $e) { | ||
| // nothing | ||
| } | ||
| if ($classExists) { | ||
| $reflectionClass = Utils::createClassReflectionInstance($this->name); | ||
|
|
@@ -156,6 +169,9 @@ | |
|
|
||
| $this->is_iterable = $clazz->isIterable(); | ||
|
|
||
| // Extract PHP 8.0+ attributes | ||
| $this->attributes = Utils::extractAttributesFromReflection($clazz); | ||
|
|
||
| $parent = $clazz->getParentClass(); | ||
| if ($parent) { | ||
| $this->parentClass = $parent->getName(); | ||
|
|
@@ -169,7 +185,7 @@ | |
| ) { | ||
| $classExists = true; | ||
| } | ||
| } catch (\Exception $e) { | ||
| } catch (\Throwable $e) { | ||
| // nothing | ||
| } | ||
| if ($classExists) { | ||
|
|
@@ -442,4 +458,102 @@ | |
| $this->parseError[\md5($tmpErrorMessage)] = $tmpErrorMessage; | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if the class node uses syntax that requires PHP 8.2+ and would | ||
| * cause an uncatchable E_COMPILE_ERROR when autoloaded on PHP < 8.2. | ||
| * | ||
| * @param Class_ $node | ||
| * | ||
| * @return bool | ||
| */ | ||
| private static function nodeUsesPHP82PlusSyntax(Class_ $node): bool | ||
|
Check warning on line 470 in src/voku/SimplePhpParser/Model/PHPClass.php
|
||
| { | ||
| // readonly class is PHP 8.2+ | ||
| if (\method_exists($node, 'isReadOnly') && $node->isReadOnly()) { | ||
| return true; | ||
| } | ||
|
|
||
| foreach ($node->stmts as $stmt) { | ||
| if ($stmt instanceof \PhpParser\Node\Stmt\ClassMethod) { | ||
| if (self::containsPHP82PlusType($stmt->returnType)) { | ||
| return true; | ||
| } | ||
| foreach ($stmt->params as $param) { | ||
| if (self::containsPHP82PlusType($param->type)) { | ||
| return true; | ||
| } | ||
| } | ||
| } elseif ($stmt instanceof \PhpParser\Node\Stmt\Property) { | ||
| if (self::containsPHP82PlusType($stmt->type)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if the class node uses syntax that requires PHP 8.3+ and would | ||
| * cause an uncatchable E_COMPILE_ERROR when autoloaded on PHP < 8.3. | ||
| * | ||
| * Covers: typed class constants (Stmt\ClassConst with a non-null type). | ||
| * | ||
| * @param Class_ $node | ||
| * | ||
| * @return bool | ||
| */ | ||
| private static function nodeUsesPHP83PlusSyntax(Class_ $node): bool | ||
| { | ||
| foreach ($node->stmts as $stmt) { | ||
| // Typed class constants are PHP 8.3+ | ||
| if ($stmt instanceof \PhpParser\Node\Stmt\ClassConst && $stmt->type !== null) { | ||
| return true; | ||
| } | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
|
|
||
| /** | ||
| * Returns true if the given type node is a PHP 8.2+ type that causes an | ||
| * uncatchable E_COMPILE_ERROR when loaded on PHP < 8.2. | ||
| * | ||
| * Covers: standalone true/false/null types and DNF types (union of intersections). | ||
| * | ||
| * @param \PhpParser\Node|null $typeNode | ||
| * | ||
| * @return bool | ||
| */ | ||
| private static function containsPHP82PlusType($typeNode): bool | ||
|
Check warning on line 529 in src/voku/SimplePhpParser/Model/PHPClass.php
|
||
| { | ||
| if ($typeNode === null) { | ||
| return false; | ||
| } | ||
|
|
||
| // Standalone true, false, null as the *sole* type (not in a nullable like ?string) | ||
| // are PHP 8.2+ only. PHP-Parser represents these as Identifier nodes (not Name). | ||
| // Nullable null (?null) is syntactically invalid; NullableType wraps the inner type. | ||
| if ($typeNode instanceof \PhpParser\Node\Identifier) { | ||
| $name = \strtolower($typeNode->name); | ||
| return $name === 'true' || $name === 'false' || $name === 'null'; | ||
| } | ||
|
|
||
| // DNF types: union type containing an intersection type (PHP 8.2+) | ||
| if ($typeNode instanceof \PhpParser\Node\UnionType) { | ||
| foreach ($typeNode->types as $t) { | ||
| if ($t instanceof \PhpParser\Node\IntersectionType || self::containsPHP82PlusType($t)) { | ||
| return true; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Recurse into nullable type | ||
| if ($typeNode instanceof \PhpParser\Node\NullableType) { | ||
| return self::containsPHP82PlusType($typeNode->type); | ||
| } | ||
|
|
||
| return false; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The workflow switched from pinning
actions/checkout/setup-phpto a specific commit SHA to using floating tags (@v4,@v2). This weakens supply-chain security because the referenced code can change without review. Prefer pinning third-party actions to an immutable commit SHA (and optionally keeping a comment with the corresponding version).