Hướng dẫn where does php code get executed? - mã php được thực thi ở đâu?

PHP là ngôn ngữ được biên dịch kể từ PHP 4.0

Ý tưởng về một trình biên dịch dường như là một chủ đề gây ra sự nhầm lẫn lớn. Một số người cho rằng trình biên dịch là một chương trình chuyển đổi mã nguồn bằng một ngôn ngữ thành một chương trình thực thi. Định nghĩa về một trình biên dịch thực sự rộng hơn thế.

Trình biên dịch là một chương trình chuyển đổi mã nguồn thành một đại diện khác của mã. Biểu diễn mục tiêu thường là mã máy, nhưng nó cũng có thể là mã nguồn bằng ngôn ngữ khác hoặc thậm chí trong cùng một ngôn ngữ.

PHP trở thành ngôn ngữ được biên soạn vào năm 2000, khi Php 4 được phát hành lần đầu tiên. Cho đến phiên bản 3, mã nguồn PHP đã được phân tích cú pháp và thực hiện ngay lập tức bởi trình thông dịch PHP.

Php 4 giới thiệu động cơ Zend. Động cơ này chia việc xử lý mã PHP thành nhiều giai đoạn. Pha đầu tiên phân tích mã nguồn PHP và tạo biểu diễn nhị phân của mã PHP được gọi là mã hóa Zend. Opcodes là bộ hướng dẫn tương tự như các byte Java. Các opcode này được lưu trữ trong bộ nhớ. Giai đoạn thứ hai của xử lý động cơ Zend bao gồm thực hiện các mã hóa được tạo.

Hình thành thêm thông tin Truy cập http://www.phpclasses.org/blog/post/117-php-compiler-performance.html

Bài viết này đã được xem xét bởi Younes Rafie. Cảm ơn tất cả các nhà đá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 Ruby Code thực hiện, bài viết này bao gồm quá trình thực thi cho mã PHP.

Hướng dẫn where does php code get executed? - mã php được thực thi ở đâu?

Giới thiệu

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

  1. Từ xa
  2. Phân tích cú pháp
  3. Tổng hợp
  4. Diễn dịch

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

Giai đoạn 1 - Lexing

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

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

$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);
    }
}

Outputs:

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 đó, một số biểu tượng được coi là mã thông báo trong và của chính chúng (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ự thực hiện nhiều hơn một chút chỉ đơn giản là xuất một luồng mã thông báo. Nó cũng, trong hầu hết các trường hợp, lưu trữ từ vựng (giá trị phù hợp với mã thông báo) và số dòng của mã thông báo được 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 với Bison thông qua tệp ngữ pháp BNF. PHP sử dụng ngữ pháp không có ngữ pháp LALR (1) (nhìn về phía trước, từ trái sang phải). Phần nhìn về phía trước đơn giản có nghĩa là trình phân tích cú pháp có thể nhìn

Line 1: T_OPEN_TAG ('
3 mã thông báo phía trước (1, trong trường hợp này) để giải quyết sự 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 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. Trước 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 với 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) - chế độ xem 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 được tạo bởi trình phân tích cú pháp bằng cách sử dụng tiện ích mở rộng PHP-est. AST bên trong không được phơi bày trực tiếp vì nó không đặc biệt là sạch sẽ để làm việc (về tính nhất quán và khả năng sử dụng chung), và do đó, tiện ích mở rộng PHP-est thực hiện một vài phép biến đổi để làm cho nó đẹp hơn để làm việc.

Hãy để một cái nhìn về AST cho một đoạn mã thô sơ:

$code = <<<'code'

$a = 1;
code;

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

Output:

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 là loại

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

  • Line 1: T_OPEN_TAG ('
    5 - Giá trị số nguyên để mô tả loại nút; Mỗi có một hằng số tương ứng (ví dụ:
    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 chỉ định hành vi quá tải (ví dụ: nút ____20 sẽ 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ư được nhìn 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 phụ, thường là các phần của nút bị chia nhỏ hơn nữa (ví dụ: một nút chức năng sẽ có trẻ em: tham số, loại trả về, cơ thể, v.v.)

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

Giai đoạn 3 - biên dịch

Giai đoạn biên dịch tiêu thụ AST, nơi nó phát ra mã hóa bằng cách đi qua cây đệ 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 giải quyết một số cuộc gọi chức năng với 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 không đổi (như
$code = <<<'code'

$a = 1;
code;

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

$a = 1;
code;

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

Chúng tôi có thể kiểm tra đầu ra opcode ở giai đoạn này theo một số cách, bao gồm cả Opcache, VLD 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 là gì cho tập lệnh tệp sau.php:file.php script:

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 loại opcodes giống với mã nguồn ban đầu, đủ để theo dõi cùng với các hoạt động cơ bản. . đã 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ã hóa (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 đóng gói với nhiều cấp độ tối ưu hóa khác nhau. Hãy để Lôi tăng mức tối ưu hóa lên bốn đường chuyền để xem những gì xuất hiện:

Command:

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

Output:

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 đã được gỡ bỏ và hai hướng dẫn

$code = <<<'code'

$a = 1;
code;

print_r(ast\parse_code($code, 30));
9 đã được nén vào một hướng dẫn duy nhất. Đây chỉ là một hương vị của nhiều tối ưu hóa Opcache áp dụng khi thực hiện các lần truyền qua các mã hóa của một tập lệnh. Mặc dù vậy, tôi đã giành chiến thắng trong các cấp độ tối ưu hóa khác nhau trong bài viết này, vì đó cũng sẽ là một bài viết.

Giai đoạn 4 - Giải thích

Giai đoạn cuối cùng là sự giải thích của các opcodes. Đây là nơi các mã hóa được chạy trên VM Zend Engine (ZE). Có rất ít để nói về giai đoạn này (ít nhất là từ góc độ cấp cao). Đầu ra là khá nhiều bất kể đầu ra tập lệnh PHP của bạn 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, một sự thật thú vị: PHP yêu cầu bản thân như một sự phụ thuộc khi tạo VM của riêng mình. Điều này là do VM được tạo bởi tập lệnh PHP, do nó đơn giản hơn để viết và dễ bảo trì hơn.

Sự kết luận

Chúng tôi đã 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 có liên quan đến việc sử dụng các tiện ích mở rộng khác nhau (bao gồm tokenizer, 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 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 bộ nhớ đệm và tối ưu hóa của nó).

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

Có ba cách khác nhau để cung cấp cho CLI sapi mã PHP được thực thi:..
Nói với PHP để thực hiện một tệp nhất định.$ php my_script.php $ PHP -f my_script.php.....
Vượt qua mã PHP để thực thi trực tiếp trên dòng lệnh.....
Cung cấp mã PHP để thực thi thông qua đầu vào tiêu chuẩn (stdin) ..

PHP có được thực thi trên máy chủ hoặc máy khách không?

PHP là ngôn ngữ kịch bản phía máy chủ phổ biến trong phát triển web.PHP được thực thi trên máy chủ, sau đó được dịch sang phía máy khách bằng mã HTML.PHP is executed on the server, which then gets translated into the client-side using HTML code.