mirror of
https://github.com/rectorphp/rector.git
synced 2024-06-07 11:50:51 +00:00
Downgrade arrow functions (#4137)
* Extracted rule into abstract class for reuse * Implemented rule * Added rule to downgrade set * Fixed rector-ci errors * Fixed cs * Preserve return type * Direct assignment of parameters * Use interface instead of abstract methods * Moved abstract class under Php72 * Added test for params with type
This commit is contained in:
parent
c6203e96d2
commit
a0f99c2c7f
|
@ -2,6 +2,7 @@
|
|||
|
||||
declare(strict_types=1);
|
||||
|
||||
use Rector\Downgrade\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector;
|
||||
use Rector\Downgrade\Rector\Coalesce\DowngradeNullCoalescingOperatorRector;
|
||||
use Rector\Downgrade\Rector\FunctionLike\DowngradeParamObjectTypeDeclarationRector;
|
||||
use Rector\Downgrade\Rector\FunctionLike\DowngradeReturnObjectTypeDeclarationRector;
|
||||
|
@ -17,5 +18,7 @@ return static function (ContainerConfigurator $containerConfigurator): void {
|
|||
|
||||
$services->set(DowngradeTypedPropertyRector::class);
|
||||
|
||||
$services->set(ArrowFunctionToAnonymousFunctionRector::class);
|
||||
|
||||
$services->set(DowngradeNullCoalescingOperatorRector::class);
|
||||
};
|
||||
|
|
|
@ -0,0 +1,99 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Downgrade\Rector\ArrowFunction;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\ArrowFunction;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt\Return_;
|
||||
use PhpParser\Node\UnionType;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\Php72\Rector\FuncCall\AbstractConvertToAnonymousFunctionRector;
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/manual/en/functions.arrow.php
|
||||
*
|
||||
* @see \Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\ArrowFunctionToAnonymousFunctionRectorTest
|
||||
*/
|
||||
final class ArrowFunctionToAnonymousFunctionRector extends AbstractConvertToAnonymousFunctionRector
|
||||
{
|
||||
public function getDefinition(): RectorDefinition
|
||||
{
|
||||
return new RectorDefinition('Replace arrow functions with anonymous functions', [
|
||||
new CodeSample(
|
||||
<<<'PHP'
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = fn($matches) => $delimiter . strtolower($matches[1]);
|
||||
}
|
||||
}
|
||||
PHP
|
||||
,
|
||||
<<<'PHP'
|
||||
class SomeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = function ($matches) use ($delimiter) {
|
||||
return $delimiter . strtolower($matches[1]);
|
||||
};
|
||||
}
|
||||
}
|
||||
PHP
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[]
|
||||
*/
|
||||
public function getNodeTypes(): array
|
||||
{
|
||||
return [ArrowFunction::class];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrowFunction $node
|
||||
*/
|
||||
public function shouldSkip(Node $node): bool
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrowFunction $node
|
||||
* @return Param[]
|
||||
*/
|
||||
public function getParameters(Node $node): array
|
||||
{
|
||||
return $node->params;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrowFunction $node
|
||||
* @return Identifier|Name|NullableType|UnionType|null
|
||||
*/
|
||||
public function getReturnType(Node $node): ?Node
|
||||
{
|
||||
return $node->returnType;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrowFunction $node
|
||||
* @return Return_[]
|
||||
*/
|
||||
public function getBody(Node $node): array
|
||||
{
|
||||
return [new Return_($node->expr)];
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector;
|
||||
|
||||
use Iterator;
|
||||
use Rector\Core\Testing\PHPUnit\AbstractRectorTestCase;
|
||||
use Rector\Core\ValueObject\PhpVersionFeature;
|
||||
use Rector\Downgrade\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector;
|
||||
use Symplify\SmartFileSystem\SmartFileInfo;
|
||||
|
||||
final class ArrowFunctionToAnonymousFunctionRectorTest extends AbstractRectorTestCase
|
||||
{
|
||||
/**
|
||||
* @requires PHP >= 7.4
|
||||
* @dataProvider provideData()
|
||||
*/
|
||||
public function test(SmartFileInfo $fileInfo): void
|
||||
{
|
||||
$this->doTestFileInfo($fileInfo);
|
||||
}
|
||||
|
||||
public function provideData(): Iterator
|
||||
{
|
||||
return $this->yieldFilesFromDirectory(__DIR__ . '/Fixture');
|
||||
}
|
||||
|
||||
protected function getRectorClass(): string
|
||||
{
|
||||
return ArrowFunctionToAnonymousFunctionRector::class;
|
||||
}
|
||||
|
||||
protected function getPhpVersion(): string
|
||||
{
|
||||
return PhpVersionFeature::BEFORE_ARROW_FUNCTION;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class FixtureClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = fn($matches) => $delimiter . strtolower($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class FixtureClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = function ($matches) use ($delimiter) {
|
||||
return $delimiter . strtolower($matches[1]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoParamsClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = " ";
|
||||
$callable = fn() => 'Hello' . $delimiter . 'world';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoParamsClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = " ";
|
||||
$callable = function () use ($delimiter) {
|
||||
return 'Hello' . $delimiter . 'world';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoParamsOrUseClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = fn() => 'Hello world';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoParamsOrUseClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = function () {
|
||||
return 'Hello world';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoUseClass
|
||||
{
|
||||
public function duplicate()
|
||||
{
|
||||
$numbers = [3, 5, 7];
|
||||
return array_map(fn(int $number) => $number * 2, $numbers);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNoUseClass
|
||||
{
|
||||
public function duplicate()
|
||||
{
|
||||
$numbers = [3, 5, 7];
|
||||
return array_map(function (int $number) {
|
||||
return $number * 2;
|
||||
}, $numbers);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ParamsWithTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = fn(array $matches) => $delimiter . strtolower($matches[1]);
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class ParamsWithTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$delimiter = ",";
|
||||
$callable = function (array $matches) use ($delimiter) {
|
||||
return $delimiter . strtolower($matches[1]);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNullableReturnTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = fn(): ?string => 'Hello world';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithNullableReturnTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = function () : ?string {
|
||||
return 'Hello world';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,29 @@
|
|||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithReturnTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = fn(): string => 'Hello world';
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
-----
|
||||
<?php
|
||||
|
||||
namespace Rector\Downgrade\Tests\Rector\ArrowFunction\ArrowFunctionToAnonymousFunctionRector\Fixture;
|
||||
|
||||
class WithReturnTypeClass
|
||||
{
|
||||
public function run()
|
||||
{
|
||||
$callable = function () : string {
|
||||
return 'Hello world';
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
|
@ -0,0 +1,34 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php72\Contract;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\UnionType;
|
||||
|
||||
interface ConvertToAnonymousFunctionRectorInterface
|
||||
{
|
||||
public function shouldSkip(Node $node): bool;
|
||||
|
||||
/**
|
||||
* @return Param[]
|
||||
*/
|
||||
public function getParameters(Node $node): array;
|
||||
|
||||
/**
|
||||
* @return Identifier|Name|NullableType|UnionType|null
|
||||
*/
|
||||
public function getReturnType(Node $node): ?Node;
|
||||
|
||||
/**
|
||||
* @return Expression[]|Stmt[]
|
||||
*/
|
||||
public function getBody(Node $node): array;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Rector\Php72\Rector\FuncCall;
|
||||
|
||||
use PhpParser\Node;
|
||||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Param;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
use Rector\Php72\Contract\ConvertToAnonymousFunctionRectorInterface;
|
||||
|
||||
/**
|
||||
* @see https://www.php.net/functions.anonymous
|
||||
*/
|
||||
abstract class AbstractConvertToAnonymousFunctionRector extends AbstractRector implements ConvertToAnonymousFunctionRectorInterface
|
||||
{
|
||||
public function refactor(Node $node): ?Node
|
||||
{
|
||||
if ($this->shouldSkip($node)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = $this->getBody($node);
|
||||
$parameters = $this->getParameters($node);
|
||||
$useVariables = $this->resolveUseVariables($body, $parameters);
|
||||
|
||||
$anonymousFunctionNode = new Closure();
|
||||
$anonymousFunctionNode->params = $parameters;
|
||||
|
||||
foreach ($useVariables as $useVariable) {
|
||||
$anonymousFunctionNode->uses[] = new ClosureUse($useVariable);
|
||||
}
|
||||
|
||||
$anonymousFunctionNode->returnType = $this->getReturnType($node);
|
||||
|
||||
if ($body !== []) {
|
||||
$anonymousFunctionNode->stmts = $body;
|
||||
}
|
||||
|
||||
return $anonymousFunctionNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
* @param Param[] $paramNodes
|
||||
* @return Variable[]
|
||||
*/
|
||||
private function resolveUseVariables(array $nodes, array $paramNodes): array
|
||||
{
|
||||
$paramNames = [];
|
||||
foreach ($paramNodes as $paramNode) {
|
||||
$paramNames[] = $this->getName($paramNode);
|
||||
}
|
||||
|
||||
$variableNodes = $this->betterNodeFinder->findInstanceOf($nodes, Variable::class);
|
||||
|
||||
/** @var Variable[] $filteredVariables */
|
||||
$filteredVariables = [];
|
||||
$alreadyAssignedVariables = [];
|
||||
foreach ($variableNodes as $variableNode) {
|
||||
// "$this" is allowed
|
||||
if ($this->isName($variableNode, 'this')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variableName = $this->getName($variableNode);
|
||||
if ($variableName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($variableName, $paramNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentNode = $variableNode->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof Assign) {
|
||||
$alreadyAssignedVariables[] = $variableName;
|
||||
}
|
||||
|
||||
if ($this->isNames($variableNode, $alreadyAssignedVariables)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredVariables[$variableName] = $variableNode;
|
||||
}
|
||||
|
||||
return $filteredVariables;
|
||||
}
|
||||
}
|
|
@ -10,21 +10,20 @@ use PhpParser\Node\Expr;
|
|||
use PhpParser\Node\Expr\Assign;
|
||||
use PhpParser\Node\Expr\BinaryOp\Concat;
|
||||
use PhpParser\Node\Expr\Closure;
|
||||
use PhpParser\Node\Expr\ClosureUse;
|
||||
use PhpParser\Node\Expr\FuncCall;
|
||||
use PhpParser\Node\Expr\Variable;
|
||||
use PhpParser\Node\Identifier;
|
||||
use PhpParser\Node\Name;
|
||||
use PhpParser\Node\NullableType;
|
||||
use PhpParser\Node\Param;
|
||||
use PhpParser\Node\Scalar\Encapsed;
|
||||
use PhpParser\Node\Scalar\String_;
|
||||
use PhpParser\Node\Stmt;
|
||||
use PhpParser\Node\Stmt\Expression;
|
||||
use PhpParser\Node\UnionType;
|
||||
use Rector\Core\Exception\ShouldNotHappenException;
|
||||
use Rector\Core\PhpParser\Parser\InlineCodeParser;
|
||||
use Rector\Core\Rector\AbstractRector;
|
||||
use Rector\Core\RectorDefinition\CodeSample;
|
||||
use Rector\Core\RectorDefinition\RectorDefinition;
|
||||
use Rector\NodeTypeResolver\Node\AttributeKey;
|
||||
|
||||
/**
|
||||
* @see https://stackoverflow.com/q/48161526/1348344
|
||||
|
@ -32,7 +31,7 @@ use Rector\NodeTypeResolver\Node\AttributeKey;
|
|||
*
|
||||
* @see \Rector\Php72\Tests\Rector\FuncCall\CreateFunctionToAnonymousFunctionRector\CreateFunctionToAnonymousFunctionRectorTest
|
||||
*/
|
||||
final class CreateFunctionToAnonymousFunctionRector extends AbstractRector
|
||||
final class CreateFunctionToAnonymousFunctionRector extends AbstractConvertToAnonymousFunctionRector
|
||||
{
|
||||
/**
|
||||
* @var InlineCodeParser
|
||||
|
@ -84,33 +83,35 @@ PHP
|
|||
/**
|
||||
* @param FuncCall $node
|
||||
*/
|
||||
public function refactor(Node $node): ?Node
|
||||
public function shouldSkip(Node $node): bool
|
||||
{
|
||||
if (! $this->isName($node, 'create_function')) {
|
||||
return null;
|
||||
}
|
||||
return ! $this->isName($node, 'create_function');
|
||||
}
|
||||
|
||||
/** @var Variable[] $parameters */
|
||||
$parameters = $this->parseStringToParameters($node->args[0]->value);
|
||||
$body = $this->parseStringToBody($node->args[1]->value);
|
||||
$useVariables = $this->resolveUseVariables($body, $parameters);
|
||||
/**
|
||||
* @param FuncCall $node
|
||||
* @return Param[]
|
||||
*/
|
||||
public function getParameters(Node $node): array
|
||||
{
|
||||
return $this->parseStringToParameters($node->args[0]->value);
|
||||
}
|
||||
|
||||
$anonymousFunctionNode = new Closure();
|
||||
/**
|
||||
* @return Identifier|Name|NullableType|UnionType|null
|
||||
*/
|
||||
public function getReturnType(Node $node): ?Node
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
foreach ($parameters as $parameter) {
|
||||
/** @var Variable $parameter */
|
||||
$anonymousFunctionNode->params[] = new Param($parameter);
|
||||
}
|
||||
|
||||
if ($body !== []) {
|
||||
$anonymousFunctionNode->stmts = $body;
|
||||
}
|
||||
|
||||
foreach ($useVariables as $useVariable) {
|
||||
$anonymousFunctionNode->uses[] = new ClosureUse($useVariable);
|
||||
}
|
||||
|
||||
return $anonymousFunctionNode;
|
||||
/**
|
||||
* @param FuncCall $node
|
||||
* @return Stmt[]
|
||||
*/
|
||||
public function getBody(Node $node): array
|
||||
{
|
||||
return $this->parseStringToBody($node->args[1]->value);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -153,53 +154,6 @@ PHP
|
|||
return $this->inlineCodeParser->parse($node);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param Node[] $nodes
|
||||
* @param Variable[] $paramNodes
|
||||
* @return Variable[]
|
||||
*/
|
||||
private function resolveUseVariables(array $nodes, array $paramNodes): array
|
||||
{
|
||||
$paramNames = [];
|
||||
foreach ($paramNodes as $paramNode) {
|
||||
$paramNames[] = $this->getName($paramNode);
|
||||
}
|
||||
|
||||
$variableNodes = $this->betterNodeFinder->findInstanceOf($nodes, Variable::class);
|
||||
|
||||
/** @var Variable[] $filteredVariables */
|
||||
$filteredVariables = [];
|
||||
$alreadyAssignedVariables = [];
|
||||
foreach ($variableNodes as $variableNode) {
|
||||
// "$this" is allowed
|
||||
if ($this->isName($variableNode, 'this')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$variableName = $this->getName($variableNode);
|
||||
if ($variableName === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (in_array($variableName, $paramNames, true)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parentNode = $variableNode->getAttribute(AttributeKey::PARENT_NODE);
|
||||
if ($parentNode instanceof Assign) {
|
||||
$alreadyAssignedVariables[] = $variableName;
|
||||
}
|
||||
|
||||
if ($this->isNames($variableNode, $alreadyAssignedVariables)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$filteredVariables[$variableName] = $variableNode;
|
||||
}
|
||||
|
||||
return $filteredVariables;
|
||||
}
|
||||
|
||||
private function createEval(Expr $expr): Expression
|
||||
{
|
||||
$evalFuncCall = new FuncCall(new Name('eval'), [new Arg($expr)]);
|
||||
|
|
|
@ -146,6 +146,11 @@ final class PhpVersionFeature
|
|||
*/
|
||||
public const BEFORE_TYPED_PROPERTIES = '7.3';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public const BEFORE_ARROW_FUNCTION = '7.3';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue
Block a user