PHP là gì thảo luận về việc thực thi tập lệnh PHP

Bài viết này đã được bình duyệt bởi Younes Rafie. Cảm ơn tất cả những người đánh giá ngang hàng của SitePoint đã làm cho nội dung SitePoint trở nên tốt nhất có thể


Lấy cảm hứng từ một bài viết gần đây về cách mã Ruby thực thi, bài viết này đề cập đến quy trình thực thi mã PHP

PHP là gì thảo luận về việc thực thi tập lệnh PHP

Giới thiệu

Có rất nhiều thứ đang diễn ra khi chúng ta thực thi một đoạn mã PHP. Nói chung, trình thông dịch PHP trải qua bốn giai đoạn khi thực thi mã

  1. lexing
  2. phân tích cú pháp
  3. biên soạn
  4. Diễn dịch

Bài viết này sẽ lướt qua các giai đoạn này và chỉ ra cách chúng ta có thể xem đầu ra từ mỗi giai đoạn để thực sự biết điều gì đang diễn ra. Lưu ý rằng mặc dù một số tiện ích mở rộng được sử dụng phải là một phần trong quá trình cài đặt PHP của bạn (chẳng hạn như mã thông báo và OPcache), những tiện ích mở rộng khác sẽ cần được cài đặt và kích hoạt thủ công (chẳng hạn như php-ast và VLD)

Giai đoạn 1 – Lexing

Lexing (hoặc tokenizing) là quá trình biến một chuỗi (trong trường hợp này là mã nguồn PHP) thành một chuỗi các token. Mã thông báo chỉ đơn giản là một mã định danh được đặt tên cho giá trị mà nó đã khớp. PHP sử dụng re2c để tạo từ vựng của nó từ zend_language_scanner. tôi tập tin định nghĩa

Chúng ta có thể thấy đầu ra của giai đoạn lexing thông qua tiện ích mở rộng mã thông báo

$code = <<<'code'

$a = 1;
code;

$tokens = token_get_all($code);

foreach ($tokens as $token) {
    if (is_array($token)) {
        echo "Line {$token[2]}: ", token_name($token[0]), " ('{$token[1]}')", PHP_EOL;
    } else {
        var_dump($token);
    }
}

đầu ra

Line 1: T_OPEN_TAG ('

Có một vài điểm đáng chú ý từ đầu ra trên. Điểm đầu tiên là không phải tất cả các phần của mã nguồn đều được đặt tên là mã thông báo. Thay vào đó, bản thân họ (chẳng hạn như =,

Line 1: T_OPEN_TAG ('
0,
Line 1: T_OPEN_TAG ('
1,
Line 1: T_OPEN_TAG ('
2, v.v.). Điểm thứ hai là lexer thực sự làm được nhiều việc hơn là chỉ xuất ra một luồng mã thông báo. Trong hầu hết các trường hợp, nó cũng lưu trữ từ vựng (giá trị khớp với mã thông báo) và số dòng của mã thông báo khớp (được sử dụng cho những thứ như dấu vết ngăn xếp)

Giai đoạn 2 – Phân tích cú pháp

Trình phân tích cú pháp cũng được tạo, lần này là với Bison thông qua tệp ngữ pháp BNF. PHP sử dụng ngữ pháp phi ngữ cảnh LALR(1) (nhìn về phía trước, từ trái sang phải). Phần nhìn về phía trước chỉ đơn giản có nghĩa là trình phân tích cú pháp có thể xem trước

Line 1: T_OPEN_TAG ('
3 mã thông báo (1, trong trường hợp này) để giải quyết những điểm mơ hồ mà nó có thể gặp phải trong khi phân tích cú pháp. Phần từ trái sang phải có nghĩa là nó phân tích luồng mã thông báo từ trái sang phải

Giai đoạn trình phân tích cú pháp được tạo lấy luồng mã thông báo từ lexer làm đầu vào và có hai công việc. Đầu tiên, nó xác minh tính hợp lệ của thứ tự mã thông báo bằng cách cố gắng khớp chúng với bất kỳ một trong các quy tắc ngữ pháp được xác định trong tệp ngữ pháp BNF của nó. Điều này đảm bảo rằng các cấu trúc ngôn ngữ hợp lệ đang được hình thành bởi các mã thông báo trong luồng mã thông báo. Công việc thứ hai của trình phân tích cú pháp là tạo cây cú pháp trừu tượng (AST) – dạng xem dạng cây của mã nguồn sẽ được sử dụng trong giai đoạn tiếp theo (biên dịch)

Chúng ta có thể xem một dạng AST do trình phân tích cú pháp tạo ra bằng tiện ích mở rộng php-ast. AST nội bộ không được hiển thị trực tiếp vì nó không đặc biệt "sạch" để làm việc với (về tính nhất quán và khả năng sử dụng chung), và do đó, tiện ích mở rộng php-ast thực hiện một số chuyển đổi trên nó để làm cho nó hoạt động tốt hơn

Chúng ta hãy xem AST để biết một đoạn mã thô sơ

$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));

đầu ra

ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => a
                    )
                )
                [expr] => 1
            )
        )
    )
)

Các nút cây (thường thuộc loại

Line 1: T_OPEN_TAG ('
4) có một số thuộc tính

  • Line 1: T_OPEN_TAG ('
    5 – Một giá trị số nguyên để mô tả loại nút; . g.
    Line 1: T_OPEN_TAG ('
    6 => 132,
    Line 1: T_OPEN_TAG ('
    7 => 517,
    Line 1: T_OPEN_TAG ('
    8 => 256)
  • Line 1: T_OPEN_TAG ('
    9 – Một số nguyên xác định hành vi quá tải (e. g. một nút
    $code = <<<'code'
    
    $a = 1;
    code;
    
    print_r(ast\parse_code($code, 30));
    
    0 sẽ có các cờ để phân biệt hoạt động nhị phân nào đang xảy ra)
  • $code = <<<'code'
    
    $a = 1;
    code;
    
    print_r(ast\parse_code($code, 30));
    
    1 – Số dòng, như đã thấy từ thông tin mã thông báo trước đó
  • $code = <<<'code'
    
    $a = 1;
    code;
    
    print_r(ast\parse_code($code, 30));
    
    2 – các nút con, điển hình là các phần của nút được chia nhỏ hơn nữa (e. g. một nút chức năng sẽ có con. tham số, kiểu trả về, nội dung, v.v.)

Đầu ra AST của giai đoạn này rất thuận tiện để sử dụng cho các công cụ như máy phân tích mã tĩnh (e. g. Phan)

Giai đoạn 3 – Tổng hợp

Giai đoạn biên dịch sử dụng AST, nơi nó phát ra mã lệnh bằng cách duyệt qua cây một cách đệ quy. Giai đoạn này cũng thực hiện một vài tối ưu hóa. Chúng bao gồm các đối số theo nghĩa đen (chẳng hạn như

$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
3 đến
$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
4) và gấp các biểu thức toán học hằng số (chẳng hạn như
$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
5 thành
$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
6)

Chúng ta có thể kiểm tra đầu ra opcode ở giai đoạn này theo một số cách, bao gồm với OPcache, và PHPDBG. Tôi sẽ sử dụng VLD cho việc này, vì tôi cảm thấy đầu ra thân thiện hơn khi nhìn vào

Hãy xem đầu ra của tệp sau là gì. tập lệnh php

if (PHP_VERSION === '7.1.0-dev') {
    echo 'Yay', PHP_EOL;
}

Thực hiện lệnh sau

php -dopcache.enable_cli=1 -dopcache.optimization_level=0 -dvld.active=1 -dvld.execute=0 file.php

đầu ra của chúng tôi là

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   3     0  E > > JMPZ                                                     , ->3
   4     1    >   ECHO                                                     'Yay'
         2        ECHO                                                     '%0A'
   7     3    > > RETURN                                                   1

Các opcode giống với mã nguồn ban đầu, đủ để thực hiện theo các thao tác cơ bản. (Tôi sẽ không đi sâu vào chi tiết về opcodes trong bài viết này, vì điều đó sẽ chiếm toàn bộ một số bài viết. ) Không có tối ưu hóa nào được áp dụng ở cấp opcode trong tập lệnh trên – nhưng như chúng ta có thể thấy, giai đoạn biên dịch đã thực hiện một số bằng cách giải quyết điều kiện không đổi (

$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
7) thành
$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
8

OPcache không chỉ đơn giản là lưu trữ mã opcode (do đó bỏ qua các giai đoạn từ vựng, phân tích cú pháp và biên dịch). Nó cũng đi kèm với nhiều cấp độ tối ưu hóa khác nhau. Hãy tăng mức độ tối ưu hóa lên bốn lượt để xem điều gì sẽ xảy ra

Chỉ huy

php -dopcache.enable_cli=1 -dopcache.optimization_level=1111 -dvld.active=-1 -dvld.execute=0 file.php

đầu ra

line     #* E I O op                           fetch          ext  return  operands
-------------------------------------------------------------------------------------
   4     0  E >   ECHO                                                     'Yay%0A'
   7     1      > RETURN                                                   1

Chúng ta có thể thấy rằng điều kiện không đổi đã bị loại bỏ và hai hướng dẫn

$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
9 đã được nén thành một hướng dẫn duy nhất. Đây chỉ là một phần của nhiều tối ưu hóa mà OPcache áp dụng khi thực hiện chuyển qua các opcode của tập lệnh. Tuy nhiên, tôi sẽ không đi qua các cấp độ tối ưu hóa khác nhau trong bài viết này, vì đó cũng là một bài viết.

Giai đoạn 4 – Phiên dịch

Giai đoạn cuối cùng là giải thích các opcodes. Đây là nơi mã lệnh được chạy trên máy ảo Zend Engine (ZE). Thực sự có rất ít điều để nói về giai đoạn này (ít nhất là từ góc độ cấp cao). Đầu ra hầu như là bất cứ thứ gì tập lệnh PHP của bạn xuất ra thông qua các lệnh như

ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => a
                    )
                )
                [expr] => 1
            )
        )
    )
)
0,
ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => a
                    )
                )
                [expr] => 1
            )
        )
    )
)
1,
ast\Node Object (
    [kind] => 132
    [flags] => 0
    [lineno] => 1
    [children] => Array (
        [0] => ast\Node Object (
            [kind] => 517
            [flags] => 0
            [lineno] => 2
            [children] => Array (
                [var] => ast\Node Object (
                    [kind] => 256
                    [flags] => 0
                    [lineno] => 2
                    [children] => Array (
                        [name] => a
                    )
                )
                [expr] => 1
            )
        )
    )
)
2, v.v.

Vì vậy, thay vì đào sâu vào bất cứ điều gì phức tạp ở giai đoạn này, đây là một sự thật thú vị. PHP yêu cầu chính nó như một phần phụ thuộc khi tạo VM của riêng nó. Điều này là do VM được tạo bởi một tập lệnh PHP, do nó đơn giản hơn để viết và dễ bảo trì hơn

Phần kết luận

Chúng ta đã xem qua bốn giai đoạn mà trình thông dịch PHP trải qua khi chạy mã PHP. Điều này liên quan đến việc sử dụng các tiện ích mở rộng khác nhau (bao gồm mã thông báo, php-ast, OPcache và VLD) để thao tác và xem đầu ra của từng giai đoạn

Tôi hy vọng bài viết này đã giúp cung cấp cho bạn sự hiểu biết toàn diện hơn về trình thông dịch của PHP, cũng như cho thấy tầm quan trọng của tiện ích mở rộng OPcache (đối với cả khả năng lưu trữ và tối ưu hóa của nó)

Chia sẻ bài viết này

Thomas Punt

Thomas là một sinh viên Công nghệ Web mới tốt nghiệp từ Vương quốc Anh. Anh ấy rất quan tâm đến lập trình, đặc biệt tập trung vào các công nghệ phát triển web phía máy chủ (cụ thể là PHP và Elixir). Anh ấy đóng góp cho PHP và các dự án mã nguồn mở khác trong thời gian rảnh rỗi, cũng như viết về các chủ đề mà anh ấy thấy thú vị

Thực thi PHP là gì?

Toán tử thực thi ¶ . dấu ngược (``). Lưu ý rằng đây không phải là dấu nháy đơn. PHP sẽ cố gắng thực thi nội dung của backticks dưới dạng lệnh shell ; . e. , nó sẽ không đơn giản được kết xuất thành đầu ra; .

PHP là gì giải thích bạn sẽ viết và thực thi tập lệnh PHP như thế nào?

Cách tạo tập lệnh PHP .
Dòng 1 – Thẻ này cho máy chủ biết bạn đang viết mã PHP
Dòng 2 – Bạn có thể sử dụng chức năng echo để in ra một chuỗi văn bản, chuỗi này sẽ được hiển thị lại khi chạy tập lệnh
Dòng 3 – Thẻ này cho máy chủ biết rằng bạn đã ngừng viết mã PHP

PHP là gì và giải thích?

PHP là từ viết tắt của "PHP. Bộ tiền xử lý siêu văn bản" PHP là ngôn ngữ kịch bản mã nguồn mở được sử dụng rộng rãi. Các tập lệnh PHP được thực thi trên máy chủ. PHP được tải xuống và sử dụng miễn phí.

PHP thực thi và xử lý tập lệnh ở đâu?

Đó là máy chủ web —chứ không phải trình duyệt web—có thể tương tác với trình thông dịch PHP. Trình duyệt của bạn có thể tự xử lý HTML, nhưng nó phải yêu cầu máy chủ web xử lý các tập lệnh PHP. Máy chủ đó có thể lấy các tập lệnh PHP của bạn và chạy chúng, sau đó nhận phản hồi và gửi lại cho trình duyệt của bạn.

Làm cách nào để thực thi một tệp PHP?

php” được đặt trong thư mục “htdocs”. Nếu bạn muốn chạy nó, hãy mở bất kỳ trình duyệt web nào và nhập “localhost/demo. php” và nhấn enter . Chương trình của bạn sẽ chạy.

Mã PHP được thực thi như thế nào?

Trình thông dịch là tập lệnh nhị phân chỉ được thực thi khi được gọi. Vì vậy, nếu tập lệnh PHP cần được thực thi, trình thông dịch sẽ được người dùng gọi trực tiếp qua bảng điều khiển của máy chủ hoặc qua một số dịch vụ khác đã chạy trên Máy chủ lưu trữ web ( . .