Skip to content

Commit

Permalink
Fixed the problem with using named parameters and antonymous classes …
Browse files Browse the repository at this point in the history
…in class located by a tokenizer. (#895)
  • Loading branch information
butschster committed Mar 16, 2023
1 parent e0e4c3c commit 2e00249
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 3 deletions.
7 changes: 7 additions & 0 deletions CHANGELOG.md
@@ -1,5 +1,12 @@
# CHANGELOG

## Unreleased
- **Medium Impact Changes**
- [spiral/queue] Added the ability to use mixed types as job payload.
- **Bug Fixes**
- [spiral/console] Fixed the problem with commands description with signature definition.
- [spiral/tokenizer] Fixed the problem with using named parameters in class located by a tokenizer.

## 3.6.1 - 2023-02-20
- **Bug Fixes**
- [spiral/scaffolder] Fixed the problem with namespace option in some scaffolder commands.
Expand Down
35 changes: 32 additions & 3 deletions src/Tokenizer/src/Reflection/ReflectionFile.php
Expand Up @@ -246,7 +246,17 @@ protected function locateDeclarations()
case T_TRAIT:
case T_INTERFACE:
if ($this->isClassNameConst($tokenID)) {
//PHP5.5 ClassName::class constant
// PHP5.5 ClassName::class constant
continue 2;
}

if ($this->isAnonymousClass($tokenID)) {
// PHP7.0 Anonymous classes new class ('foo', 'bar')
continue 2;
}

if (!$this->isCorrectDeclaration($tokenID)) {
// PHP8.0 Named parameters ->foo(class: 'bar')
continue 2;
}

Expand Down Expand Up @@ -395,8 +405,6 @@ private function registerDeclaration(int $tokenID, int $tokenType): void

/**
* Check if token ID represents `ClassName::class` constant statement.
*
*
*/
private function isClassNameConst(int $tokenID): bool
{
Expand All @@ -405,6 +413,27 @@ private function isClassNameConst(int $tokenID): bool
&& $this->tokens[$tokenID - 1][self::TOKEN_TYPE] === T_PAAMAYIM_NEKUDOTAYIM;
}

/**
* Check if token ID represents anonymous class creation, e.g. `new class ('foo', 'bar')`.
*/
private function isAnonymousClass(int|string $tokenID): bool
{
return $this->tokens[$tokenID][self::TOKEN_TYPE] === T_CLASS
&& isset($this->tokens[$tokenID - 2])
&& $this->tokens[$tokenID - 2][self::TOKEN_TYPE] === T_NEW;
}

/**
* Check if token ID represents named parameter with name `class`, e.g. `foo(class: SomeClass::name)`.
*/
private function isCorrectDeclaration(int|string $tokenID): bool
{
return \in_array($this->tokens[$tokenID][self::TOKEN_TYPE], [T_CLASS, T_TRAIT, T_INTERFACE], true)
&& isset($this->tokens[$tokenID + 2])
&& $this->tokens[$tokenID + 1][self::TOKEN_TYPE] === T_WHITESPACE
&& $this->tokens[$tokenID + 2][self::TOKEN_TYPE] === T_STRING;
}

/**
* Locate every function or static method call (including $this calls).
*
Expand Down
17 changes: 17 additions & 0 deletions src/Tokenizer/tests/Classes/ClassWithAnonymousClass.php
@@ -0,0 +1,17 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Classes;

class ClassWithAnonymousClass
{
public function __construct()
{
$class = new class ('foo', 'bar') {
private function someFunc(): void
{
}
};
}
}
31 changes: 31 additions & 0 deletions src/Tokenizer/tests/Classes/ClassWithHeredoc.php
@@ -0,0 +1,31 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Classes;

define('FOO', 'bar');

class ClassWithHeredoc
{
public function __construct()
{
<<<class
FooBar
class;
<<<class
class FooBar
{
}
class;FOO;
<<<'class'
class FooBar
{
}
class ;FOO;
}
}
19 changes: 19 additions & 0 deletions src/Tokenizer/tests/Classes/ClassWithNamedParameter.php
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace Spiral\Tests\Tokenizer\Classes;

use Spiral\Tests\Tokenizer\Classes\Inner\ClassD;

class ClassWithNamedParameter
{
public function __construct()
{
$this->foo(class: ClassD::class);
}

private function foo(string $class): void
{
}
}
28 changes: 28 additions & 0 deletions src/Tokenizer/tests/ReflectionFileTest.php
Expand Up @@ -51,6 +51,33 @@ public function testReflection()
$this->assertSame('123', $functionB->getArgument(1)->getValue());
}

public function testReflectionFileWithNamedParameters(): void
{
$reflection = new ReflectionFile(__DIR__ . '/Classes/ClassWithNamedParameter.php');

$this->assertSame([
'Spiral\Tests\Tokenizer\Classes\ClassWithNamedParameter',
], $reflection->getClasses());
}

public function testReflectionFileAnonymousClass(): void
{
$reflection = new ReflectionFile(__DIR__ . '/Classes/ClassWithAnonymousClass.php');

$this->assertSame([
'Spiral\Tests\Tokenizer\Classes\ClassWithAnonymousClass',
], $reflection->getClasses());
}

public function testReflectionFileWithHeredoc(): void
{
$reflection = new ReflectionFile(__DIR__ . '/Classes/ClassWithHeredoc.php');

$this->assertSame([
'Spiral\Tests\Tokenizer\Classes\ClassWithHeredoc',
], $reflection->getClasses());
}

private function deadend()
{
$a = $b = null;
Expand All @@ -62,6 +89,7 @@ private function deadend()
function hello()
{
}

// phpcs:disable
trait TestTrait
{
Expand Down

0 comments on commit 2e00249

Please sign in to comment.