symplify/astral

This package is abandoned and no longer maintained. The author suggests using the nikic/php-parser package instead.

Toolking for smart daily work with AST

Maintainers

Details

github.com/symplify/astral

Source

Installs: 2 386 221

Dependents: 7

Suggesters: 0

Security: 0

Stars: 25

Watchers: 2

Forks: 1

Type:phpstan-extension

This package is auto-updated.

Last update: 2022-08-29 21:45:23 UTC


README

Downloads total

Install

composer require symplify/astral

Add to Symfony Project

Register package in config/config.php:

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\Astral\ValueObject\AstralConfig::FILE_PATH;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->import(AstralConfig::FILE_PATH);
};

Add to PHPStan Rules

Include in your phpstan.neon:

includes:
    - vendor/symplify/astral/config/services.neon

Usage

1. Resolve Value of Node

$value = 1000;

How can we get the 1000 value?

use PhpParser\Node;
use PhpParser\Node\Expr\Assign;
use PhpParser\Node\Scalar\LNumber;
use PHPStan\Analyser\Scope;
use Symplify\Astral\NodeValue\NodeValueResolver;

final class SomeRule
{
    public function __construct(
        // PHP 8.0 promoted property syntax
        private NodeValueResolver $nodeValueResolver
    ) {
    }

    public function process(Node $node, Scope $scope): void
    {
        if ($node instanceof Assign && $node->expr instanceof LNumber) {
            $resolvedValue = $this->nodeValueResolver->resolve($node->expr, $scope->getFile());
        }
    }
}

Work for static expressions like these:

$value = 'Hey';
// "Hey"

SomeClass::class;
// "SomeClass"

class SomeClass
{
    public const VALUE = 'different';
}

SomeClass::VALUE;
// "different"

__DIR__;
// realpath of the __DIR__ in its place

2. Unique *Builder Classes

Native PhpParser node class and builder class share the same short class name.

use PhpParser\Builder\Class_;
use PhpParser\Node\Stmt\Class_;

$class = new Class_('ClassName');
$class = $class->getNode();

This confuses IDE and lead to wrong classes being used as type hints. To avoid that, this package provides *Builder names:

use Symplify\Astral\ValueObject\NodeBuilder\ClassBuilder;

$classBuilder = new ClassBuilder('some_class');
$class = $classBuilder->getNode();

3. Traverse Nodes with Simple Callback

Working with nodes is based on traversing each one of them. You can use native NodeVisitor and NodeTraverses. But that requires to create at least 2 objects, to connect them and call them.

What if we need just a small traverse right in this method? Service SimpleCallableNodeTraverser to the rescue:

use PhpParser\Node;
use PhpParser\Node\Scalar\String_;
use PhpParser\Node\Stmt\ClassMethod;
use Symplify\Astral\NodeTraverser\SimpleCallableNodeTraverser;

/** @var ClassMethod $classMethod */
$classMethod = '...';

$simpleCallableNodeTraverser = new SimpleCallableNodeTraverser();
$simpleCallableNodeTraverser->traverseNodesWithCallable($classMethod, function (Node $node) {
    if (! $node instanceof String_) {
        return null;
    }

    $node->value = 'changed name';
    return $node;
});

4. Register Config

Register config in your config/config.php:

use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use Symplify\Astral\ValueObject\AstralConfig;

return static function (ContainerConfigurator $containerConfigurator): void {
    $containerConfigurator->import(AstralConfig::FILE_PATH);
};

5. Usage of SimplePhpDocParser

Required services Symplify\Astral\PhpDocParser\SimplePhpDocParser in constructor, where you need it, and use it:

use PHPStan\PhpDocParser\Ast\PhpDoc\ParamTagValueNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\Type\TypeNode;
use Symplify\Astral\PhpDocParser\SimplePhpDocParser;

final class SomeClass
{
    public function __construct(
        private SimplePhpDocParser $simplePhpDocParser
    ) {
    }

    public function some(): void
    {
        $docBlock = '/** @param int $name */';

        /** @var PhpDocNode $phpDocNode */
        $simplePhpDocNode = $this->simplePhpDocParser->parseDocBlock($docBlock);

        // param extras

        /** @var TypeNode $nameParamType */
        $nameParamType = $simplePhpDocNode->getParamType('name');

        /** @var ParamTagValueNode $nameParamTagValueNode */
        $nameParamTagValueNode = $simplePhpDocNode->getParam('name');
    }
}

6. Traverse Nodes with PhpDocNodeTraverser

use PHPStan\PhpDocParser\Ast\Node;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\VarTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;
use Symplify\Astral\PhpDocParser\PhpDocNodeTraverser;
use Symplify\Astral\PhpDocParser\PhpDocNodeVisitor\AbstractPhpDocNodeVisitor;
use Symplify\Astral\PhpDocParser\PhpDocNodeVisitor\CallablePhpDocNodeVisitor;

$phpDocNodeTraverser = new PhpDocNodeTraverser();
$phpDocNode = new PhpDocNode([new PhpDocTagNode('@var', new VarTagValueNode(new IdentifierTypeNode('string')))]);

// A. you can use callable to traverse
$callable = function (Node $node): Node {
    if (! $node instanceof VarTagValueNode) {
        return $node;
    }

    $node->type = new IdentifierTypeNode('int');
    return $node;
};

$callablePhpDocNodeVisitor = new CallablePhpDocNodeVisitor($callable, null);
$phpDocNodeTraverser->addPhpDocNodeVisitor($callablePhpDocNodeVisitor);

// B. or class that extends AbstractPhpDocNodeVisitor
final class IntegerPhpDocNodeVisitor extends AbstractPhpDocNodeVisitor
{
    public function enterNode(Node $node): Node|int|null
    {
        if (! $node instanceof VarTagValueNode) {
            return $node;
        }

        $node->type = new IdentifierTypeNode('int');
        return $node;
    }
}

$integerPhpDocNodeVisitor = new IntegerPhpDocNodeVisitor();
$phpDocNodeTraverser->addPhpDocNodeVisitor($integerPhpDocNodeVisitor);

// then traverse the main node
$phpDocNodeTraverser->traverse($phpDocNode);

Report Issues

In case you are experiencing a bug or want to request a new feature head over to the Symplify monorepo issue tracker


Contribute

The sources of this package are contained in the Symplify monorepo. We welcome contributions for this package on symplify/symplify.