Phpstan/phpdoc-trình phân tích cú pháp

Hôm nay chúng ta xem xét cách sửa đổi docblocks một cách dễ dàng. Nhưng trước tiên, chúng ta phải tìm hiểu cách thức hoạt động của

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
0

Hãy bắt đầu với một docblock đơn giản như thế này

/**
 * @return int
 */

Nếu chúng ta phân tích cú pháp bằng

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
1. bạn biết gì?

composer require symplify/astral


use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);

Nếu chúng ta kết xuất

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
2, chúng ta sẽ có được cây nút đại khái như sau

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"

Đây là PHP cổ điển - đối tượng trong một đối tượng trong một đối tượng

Làm thế nào chúng ta có thể Thay thế composer require symplify/astral9 bằng use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory; $values = <<<'PHPDOC' /** * @return int */ PHPDOC; $simplePhpDocParser = SimplePhpDocParserStaticFactory::create(); $phpDocNode = $simplePhpDocParser->parse($values);4?

Đầu tiên, chúng ta cần đến

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
5. Sau đó, chúng tôi kiểm tra giá trị của nó là
use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
6. Nếu vậy, chúng tôi đổi nó thành
use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
7. Có vẻ khá đơn giản, phải không?

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}

Nếu chúng tôi chạy mã ở trên, chúng tôi quản lý để thấy sự thay đổi này

 /**
- * @return int
+ * @return string
  */

Cấp độ tiếp theo. 2 nút

Trong cuộc sống thực, loại này hiếm khi được sử dụng ở một vị trí. Hãy thêm

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
8

/**
 * @param int $age
 * @return int
 */

Sau khi phân tích cú pháp, chúng tôi nhận được cây nút này

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"
     |- PhpDocTagNode
        |- name: "@param"
        |- value: ParamTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"

Làm thế nào chúng ta nên mở rộng logic ở trên để bao gồm cả thẻ

use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
8?

 if (
     ! $phpDocChildNode->value instanceof ReturnTagValueNode
+    && ! $phpDocChildNode->value instanceof ParamTagValueNode
 ) {
      continue;
 }

Dễ dàng chọn, phải không?


Phpstan/phpdoc-trình phân tích cú pháp

Cấp độ tiếp theo. 2 nút với các loại lồng nhau

Các loại cũng có thể là hợp chất, đây là

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"
0 với 2 nút
use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
5 trong đó

/**
 * @param int|null $age
 * @return int
 */

Bây giờ chúng ta đang bước vào vòng lặp của niềm vui. Tôi đoán bạn có thể tưởng tượng làm thế nào điều này có thể trở thành mã hóa địa ngục

Nhưng tại sao chúng ta lại muốn thay đổi

composer require symplify/astral
9 thành
use Symplify\Astral\PhpDocParser\StaticFactory\SimplePhpDocParserStaticFactory;

$values = <<<'PHPDOC'
/**
 * @return int
 */
PHPDOC;

$simplePhpDocParser = SimplePhpDocParserStaticFactory::create();
$phpDocNode = $simplePhpDocParser->parse($values);
4?

Trong thực tế, chúng tôi muốn đổi tên lớp cũ thành một lớp mới

composer require symplify/astral
0

Quá nhiều sự phức tạp

Có lẽ bạn đang thắc mắc tại sao việc thay đổi một nút trong docblock lại khó đến vậy?

Trên thực tế, có ~40 lớp kế thừa từ

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"
4. Chúng ta sẽ phải thêm một dấu kiểm cho từng cái một, nếu nó có một kiểu, được lồng vào một thuộc tính nào đó, v.v.

Còn Node Traverser thì sao?

Thay vào đó, chúng ta có thể sử dụng nguyên tắc tương tự như trình phân tích cú pháp php - trình duyệt nút với khách truy cập nút

Bạn có nghe về nó lần đầu tiên không?

  • PhpDocNode:
      |- children:
         |- PhpDocTagNode
            |- name: "@return"
            |- value: ReturnTagValueNode
                |- type: IdentifierTypeNode
                    |- name: "int"
    5 ~=
    PhpDocNode:
      |- children:
         |- PhpDocTagNode
            |- name: "@return"
            |- value: ReturnTagValueNode
                |- type: IdentifierTypeNode
                    |- name: "int"
    6
  • PhpDocNode:
      |- children:
         |- PhpDocTagNode
            |- name: "@return"
            |- value: ReturnTagValueNode
                |- type: IdentifierTypeNode
                    |- name: "int"
    7 ~=
    PhpDocNode:
      |- children:
         |- PhpDocTagNode
            |- name: "@return"
            |- value: ReturnTagValueNode
                |- type: IdentifierTypeNode
                    |- name: "int"
    8


  • 1 NodeTraverser có nhiều NodeVisitor
  • 1 EventDispatcher có nhiều EventSubscribers


  • EventSubscribers đang chờ một sự kiện cụ thể xảy ra
  • NodeVisitor đang đợi một nút cụ thể xuất hiện

Mã thực sự

Trong thực tế, chúng ta nên chạy ngang trên

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"
9 để có được kết quả

composer require symplify/astral
1

Ồ, chúng tôi quên thêm

PhpDocNode:
  |- children:
     |- PhpDocTagNode
        |- name: "@return"
        |- value: ReturnTagValueNode
            |- type: IdentifierTypeNode
                |- name: "int"
7

composer require symplify/astral
2

Bây giờ chúng tôi chỉ thêm

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}
1 vào
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}
2 ↓

composer require symplify/astral
3

Đó là nó. Giờ đây, mọi "int" đơn lẻ đều được chuyển thành "chuỗi", ngay cả trong các doblock phức tạp như thế này

composer require symplify/astral
4

Làm thế nào nó hoạt động?

Logic rất đơn giản - một lần nữa, công lao thuộc về Nikita Popov, người đã tạo NodeTraverser trong trình phân tích cú pháp php

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}
2 đi qua mọi thuộc tính công khai của nút đó (xem trên Github)

Điều đó có nghĩa là nếu

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}
4 đi vào, nó sẽ đi qua đó

composer require symplify/astral
5

Nếu

use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\PhpDocTagNode;
use PHPStan\PhpDocParser\Ast\PhpDoc\ReturnTagValueNode;
use PHPStan\PhpDocParser\Ast\Type\IdentifierTypeNode;

/** @var PhpDocNode $phpDocNode */
foreach ($phpDocNode->children as $phpDocChildNode) {
    if (! $phpDocChildNode instanceof PhpDocTagNode) {
        continue;
    }

    // is this node a @return?
    if (! $phpDocChildNode->value instanceof ReturnTagValueNode) {
        continue;
    }

    // does @return have simple type? here can be any TypeNode
    // e.g. UnionTypeNode, IntersectionTypeNode, CallableTypeNode etc.
    $returnTagValueNode = $phpDocChildNode->value;
    if (! $returnTagValueNode->type instanceof IdentifierTypeNode) {
        continue;
    }

    $identifierName = $returnTagValueNode->type->name;
    if (! $identifierName === 'int') {
        continue;
    }

    // phew.. here we can finally change value
    $returnTagValueNode->type = new IdentifierTypeNode('string');
}
5 là nút, nó sẽ đi qua tất cả các thuộc tính công khai của nó, v.v.


Công khai các thuộc tính là một quy ước trong cả trình phân tích cú pháp phpdoc và trình phân tích cú pháp php, vì vậy chúng tôi có thể tin tưởng 100% vào nó