Trong trường hợp lý tưởng, chúng ta nên sử dụng PHP 8.0 (phiên bản mới nhất khi viết bài này) cho tất cả các trang web của mình và cập nhật nó ngay khi phiên bản mới được phát hành. Tuy nhiên, các nhà phát triển thường sẽ cần phải làm việc với các phiên bản PHP trước đó, chẳng hạn như khi tạo một plugin công khai cho WordPress hoặc làm việc với mã kế thừa cản trở việc nâng cấp môi trường của máy chủ web.

Trong những tình huống này, chúng tôi có thể từ bỏ hy vọng sử dụng mã PHP mới nhất. Nhưng có một giải pháp thay thế tốt hơn: chúng ta vẫn có thể viết mã nguồn của mình bằng PHP 8.0 và chuyển nó sang phiên bản PHP trước – ngay cả sang PHP 7.1.

Trong hướng dẫn này, chúng tôi sẽ dạy bạn mọi thứ bạn cần biết về việc chuyển mã PHP.

Vận chuyển là gì?

Phép dịch chuyển đổi mã nguồn từ một ngôn ngữ lập trình thành một mã nguồn tương đương của cùng một ngôn ngữ lập trình hoặc một ngôn ngữ lập trình khác.

Dịch chuyển không phải là một khái niệm mới trong phát triển web: các nhà phát triển phía máy khách có thể sẽ quen thuộc với Babel, một trình chuyển đổi cho mã JavaScript.

Babel chuyển đổi mã JavaScript từ phiên bản ECMAScript 2015+ hiện đại thành phiên bản kế thừa tương thích với các trình duyệt cũ hơn. Ví dụ, với một hàm mũi tên ES2015:

[2, 4, 6].map((n) => n * 2);

… Babel sẽ chuyển nó thành phiên bản ES5:

[2, 4, 6].map(function(n) { return n * 2; });

Dịch chuyển PHP là gì?

Điều mới mẻ tiềm tàng trong phát triển web là khả năng chuyển mã phía máy chủ, cụ thể là PHP.

Chuyển đổi PHP hoạt động giống như chuyển JavaScript: mã nguồn từ phiên bản PHP hiện đại được chuyển đổi thành mã tương đương cho phiên bản PHP cũ hơn.

Theo ví dụ tương tự như trước, một hàm mũi tên từ PHP 7.4:

$nums = array_map(fn($n) => $n * 2, [2, 4, 6]);

… Có thể được chuyển sang phiên bản PHP 7.3 tương đương của nó:

$nums = array_map( function ($n) { return $n * 2; }, [2, 4, 6] );

Các hàm mũi tên có thể được hoán vị vì chúng là đường cú pháp, tức là một cú pháp mới để tạo ra một hành vi hiện có. Đây là quả treo thấp.

Tuy nhiên, cũng có những tính năng mới tạo ra một hành vi mới và như vậy, sẽ không có mã tương đương cho các phiên bản PHP trước. Đó là trường hợp của các kiểu liên minh, được giới thiệu trong PHP 8.0:

function someFunction(float|int $param): string|float|int|null { // ... }

Trong những tình huống này, vẫn có thể thực hiện chuyển đổi miễn là tính năng mới được yêu cầu để phát triển nhưng không phải để sản xuất. Sau đó, chúng ta có thể chỉ cần xóa toàn bộ tính năng khỏi mã đã chuyển mà không gây hậu quả nghiêm trọng.

Một ví dụ như vậy là các loại liên minh. Tính năng này được sử dụng để kiểm tra xem không có sự không khớp nào giữa loại đầu vào và giá trị được cung cấp của nó, giúp ngăn ngừa lỗi. Nếu có xung đột với các loại, thì sẽ có một lỗi đã xảy ra trong quá trình phát triển, và chúng ta nên bắt lỗi và sửa nó trước khi mã được đưa vào sản xuất.

Do đó, chúng tôi có đủ khả năng để xóa tính năng khỏi mã để sản xuất:

function someFunction($param) { // ... }

Nếu lỗi vẫn xảy ra trong quá trình sản xuất, thông báo lỗi được đưa ra sẽ kém chính xác hơn nếu chúng ta có các loại liên hợp. Tuy nhiên, nhược điểm tiềm ẩn này vượt trội hơn do có thể sử dụng các loại liên minh ngay từ đầu.

Ưu điểm của việc dịch mã PHP

Transpiling cho phép một người viết mã một ứng dụng bằng cách sử dụng phiên bản PHP mới nhất và tạo ra một bản phát hành cũng hoạt động trong môi trường chạy các phiên bản PHP cũ hơn.

Điều này có thể đặc biệt hữu ích cho các nhà phát triển tạo sản phẩm cho hệ thống quản lý nội dung cũ (CMS). Ví dụ, WordPress vẫn chính thức hỗ trợ PHP 5.6 (mặc dù nó khuyến nghị PHP 7.4+). Tỷ lệ phần trăm các trang web WordPress chạy phiên bản PHP từ 5.6 đến 7.2 – tất cả đều là End-of-Life (EOL), nghĩa là chúng không nhận được các bản cập nhật bảo mật nữa – ở mức khá lớn là 34,8% và những trang chạy trên bất kỳ phiên bản PHP nào khác 8.0 là con số khổng lồ 99,5%:

WordPress usage by version
Số liệu thống kê sử dụng WordPress theo phiên bản. Nguồn hình ảnh: WordPress

Do đó, các chủ đề và plugin WordPress được nhắm mục tiêu đến đối tượng toàn cầu rất có thể sẽ được mã hóa bằng phiên bản PHP cũ để tăng khả năng tiếp cận của họ. Nhờ chuyển ngữ, chúng có thể được mã hóa bằng PHP 8.0 và vẫn được phát hành cho phiên bản PHP cũ hơn, do đó nhắm mục tiêu đến càng nhiều người dùng càng tốt.

Thật vậy, bất kỳ ứng dụng nào cần hỗ trợ bất kỳ phiên bản PHP nào khác với phiên bản mới nhất (ngay cả trong phạm vi của các phiên bản PHP hiện được hỗ trợ) đều có thể được hưởng lợi.

Đây là trường hợp của Drupal, yêu cầu PHP 7.3. Nhờ chuyển ngữ, các nhà phát triển có thể tạo các mô-đun Drupal có sẵn công khai bằng cách sử dụng PHP 8.0 và phát hành chúng với PHP 7.3.

Một ví dụ khác là khi tạo mã tùy chỉnh cho các máy khách không thể chạy PHP 8.0 trong môi trường của họ do lý do này hay lý do khác. Tuy nhiên, nhờ tính năng chuyển ngữ, các nhà phát triển vẫn có thể viết mã các sản phẩm của họ bằng cách sử dụng PHP 8.0 và chạy chúng trên các môi trường kế thừa đó.

Khi nào chuyển tải PHP

Mã PHP luôn có thể được chuyển đổi trừ khi nó chứa một số tính năng PHP không có tính năng tương đương trong phiên bản PHP trước đó.

Đó có thể là trường hợp với các thuộc tính, được giới thiệu trong PHP 8.0:

#[SomeAttr] function someFunc() {} #[AnotherAttr] class SomeClass {}

Trong ví dụ trước đó bằng cách sử dụng các hàm mũi tên, mã có thể được chuyển đổi bởi vì các hàm mũi tên là đường cú pháp. Ngược lại, các thuộc tính tạo ra hành vi hoàn toàn mới. Hành vi này cũng có thể được tái tạo với PHP 7.4 trở xuống, nhưng chỉ bằng cách mã hóa nó theo cách thủ công, tức là không tự động dựa trên một công cụ hoặc quy trình (AI có thể cung cấp giải pháp, nhưng chúng tôi chưa có).

Các thuộc tính dành cho mục đích phát triển, chẳng hạn như #[Deprecated] , có thể bị xóa giống như cách xóa các loại liên kết. Nhưng các thuộc tính sửa đổi hành vi của ứng dụng trong quá trình sản xuất không thể bị loại bỏ và chúng cũng không thể được chuyển đổi trực tiếp.

Cho đến ngày nay, không có trình chuyển đổi nào có thể lấy mã với các thuộc tính PHP 8.0 và tự động tạo ra mã PHP 7.4 tương đương của nó. Do đó, nếu mã PHP của bạn cần sử dụng các thuộc tính, thì việc chuyển mã nó sẽ khó hoặc không khả thi.

Các tính năng PHP có thể được vận chuyển

Đây là các tính năng từ PHP 7.1 trở lên hiện có thể được chuyển đổi. Nếu mã của bạn chỉ sử dụng các tính năng này, bạn có thể tận hưởng sự chắc chắn rằng ứng dụng đã chuyển của bạn sẽ hoạt động. Nếu không, bạn sẽ cần đánh giá xem mã được chuyển đổi có tạo ra lỗi hay không.

Phiên bản PHP Đặc trưng
7.1 Mọi điều
7.2 – loại object
– mở rộng loại tham số
– Cờ PREG_UNMATCHED_AS_NULL trong preg_match
7.3 – Các phép gán tham chiếu trong list() / hủy cấu trúc mảng ( Ngoại trừ bên trong foreach – # 4376)
– Cú pháp Heredoc và Nowdoc linh hoạt
– Dấu phẩy theo sau trong các lệnh gọi hàm
set(raw)cookie chấp nhận đối số $ option
7.4 – Thuộc tính đã nhập
– Các hàm mũi tên
– Toán tử gán liên kết rỗng
– Mở gói bên trong các mảng
– Dấu phân cách chữ số
strip_tags() với mảng tên thẻ
– kiểu trả về hiệp phương sai và kiểu tham số tương phản
8.0 – Các loại công đoàn
– loại giả mixed
– kiểu trả về static
::class trên các đối tượng
match các biểu thức
– chỉ catch các ngoại lệ theo loại
– Nhà điều hành không an toàn
– Thúc đẩy thuộc tính phương thức khởi tạo lớp
– Dấu phẩy theo sau trong danh sách tham số và danh sách use đóng

Transpilers PHP

Hiện tại, có một công cụ để chuyển mã PHP: Hiệu trưởng.

Hiệu trưởng là một công cụ tái tạo PHP, công cụ này chuyển đổi mã PHP dựa trên các quy tắc có thể lập trình được. Chúng tôi nhập mã nguồn và bộ quy tắc để chạy, và Hiệu trưởng sẽ chuyển đổi mã.

Hiệu trưởng được vận hành thông qua dòng lệnh, được cài đặt trong dự án thông qua Trình soạn thảo. Khi được thực thi, Hiệu trưởng sẽ xuất ra “khác biệt” (bổ sung màu xanh lá cây, xóa màu đỏ) của mã trước và sau khi chuyển đổi:

"diff" output from Rector
Đầu ra “khác biệt” từ Hiệu trưởng

Phiên bản PHP nào để chuyển tải sang

Để chuyển mã qua các phiên bản PHP, các quy tắc tương ứng phải được tạo.

Ngày nay, thư viện Hiệu trưởng bao gồm hầu hết các quy tắc chuyển mã trong phạm vi từ PHP 8.0 đến 7.1. Do đó, chúng tôi có thể chuyển mã PHP của mình xuống phiên bản 7.1 một cách đáng tin cậy.

Ngoài ra còn có các quy tắc để chuyển từ PHP 7.1 sang 7.0 và từ 7.0 sang 5.6, nhưng những quy tắc này không đầy đủ. Công việc đang được tiến hành để hoàn thành chúng, vì vậy cuối cùng chúng tôi có thể chuyển mã PHP xuống phiên bản 5.6.

Vận chuyển và vận chuyển ngược

Backporting tương tự như chuyển đổi, nhưng đơn giản hơn. Mã backporting không nhất thiết phải dựa vào các tính năng mới từ một ngôn ngữ. Thay vào đó, chức năng tương tự có thể được cung cấp cho phiên bản cũ hơn của ngôn ngữ chỉ bằng cách sao chép / dán / điều chỉnh mã tương ứng từ phiên bản mới của ngôn ngữ.

Ví dụ, hàm str_contains đã được giới thiệu trong PHP 8.0. Chức năng tương tự cho PHP 7.4 trở xuống có thể được triển khai dễ dàng như sau:

if (!defined('PHP_VERSION_ID') || (defined('PHP_VERSION_ID') && PHP_VERSION_ID < 80000)) { if (!function_exists('str_contains')) { /** * Checks if a string contains another * * @param string $haystack The string to search in * @param string $needle The string to search * @return boolean Returns TRUE if the needle was found in haystack, FALSE otherwise. */ function str_contains(string $haystack, string $needle): bool { return strpos($haystack, $needle) !== false; } } }

Bởi vì backporting đơn giản hơn so với chuyển tiếp, chúng ta nên chọn giải pháp này bất cứ khi nào backporting thực hiện được công việc.

Liên quan đến phạm vi từ PHP 8.0 đến 7.1, chúng ta có thể sử dụng các thư viện polyfill của Symfony:

  • Polyfill PHP 7.1
  • Polyfill PHP 7.2
  • Polyfill PHP 7.3
  • Polyfill PHP 7.4
  • Polyfill PHP 8.0

Các thư viện này phản hồi các hàm, lớp, hằng số và giao diện sau:

Phiên bản PHP Đặc trưng
7.2 Chức năng:

  • spl_object_id
  • utf8_encode
  • utf8_decode

Hằng số:

  • PHP_FLOAT_*
  • PHP_OS_FAMILY
7.3 Chức năng:

  • array_key_first
  • array_key_last
  • hrtime
  • is_countable

Các trường hợp ngoại lệ:

  • JsonException
7.4 Chức năng:

  • get_mangled_object_vars
  • mb_str_split
  • password_algos
8.0 Giao diện:

  • Stringable

Các lớp học:

  • ValueError
  • UnhandledMatchError

Hằng số:

  • FILTER_VALIDATE_BOOL

Chức năng:

  • fdiv
  • get_debug_type
  • preg_last_error_msg
  • str_contains
  • str_starts_with
  • str_ends_with
  • get_resource_id

Ví dụ về PHP được dịch chuyển

Hãy kiểm tra một vài ví dụ về mã PHP đã được chuyển và một vài gói đang được chuyển hoàn toàn.

Mã PHP

Biểu thức match đã được giới thiệu trong PHP 8.0. Mã nguồn này:

function getFieldValue(string $fieldName): ?string { return match($fieldName) { 'foo' => 'foofoo', 'bar' => 'barbar', 'baz' => 'bazbaz', default => null, }; }

… Sẽ được chuyển sang phiên bản PHP 7.4 tương đương của nó, sử dụng toán tử switch :

function getFieldValue(string $fieldName): ?string { switch ($fieldName) { case 'foo': return 'foofoo'; case 'bar': return 'barbar'; case 'baz': return 'bazbaz'; default: return null; } }

Toán tử nullsafe cũng được giới thiệu trong PHP 8.0:

public function getValue(TypeResolverInterface $typeResolver): ?string { return $this->getResolver($typeResolver)?->getValue(); }

Trước tiên, mã được chuyển đổi cần phải gán giá trị của thao tác cho một biến mới, để tránh thực hiện thao tác hai lần:

public function getValue(TypeResolverInterface $typeResolver): ?string { return ($val = $this->getResolver($typeResolver)) ? $val->getValue() : null; }

Tính năng quảng bá thuộc tính phương thức khởi tạo, cũng được giới thiệu trong PHP 8.0, cho phép các nhà phát triển viết ít mã hơn:

class QueryResolver { function __construct(protected QueryFormatter $queryFormatter) { } }

Khi chuyển nó sang PHP 7.4, đoạn mã đầy đủ sẽ được tạo ra:

 class QueryResolver { protected QueryFormatter $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }

Đoạn mã được chuyển đổi ở trên chứa các thuộc tính đã nhập, được giới thiệu trong PHP 7.4. Việc chuyển mã đó xuống PHP 7.3 sẽ thay thế chúng bằng docblocks:

 class QueryResolver { /** * @var QueryFormatter */ protected $queryFormatter; function __construct(QueryFormatter $queryFormatter) { $this->queryFormatter = $queryFormatter; } }

Gói PHP

Các thư viện sau đang được chuyển đổi để sản xuất:

Thư viện / mô tả Mã / ghi chú
Hiệu trưởng
Công cụ cấu trúc lại PHP giúp cho việc chuyển ngữ có thể thực hiện được
– Mã nguồn
– Mã vận chuyển
– Ghi chú
Tiêu chuẩn mã hóa dễ dàng
Công cụ để có mã PHP tuân thủ một bộ quy tắc
– Mã nguồn
– Mã vận chuyển
– Ghi chú
API GraphQL cho WordPress
Plugin cung cấp máy chủ GraphQL cho WordPress
– Mã nguồn
– Mã vận chuyển
– Ghi chú

Ưu và nhược điểm của Transpiling PHP

Lợi ích của việc chuyển ngữ PHP đã được mô tả: nó cho phép mã nguồn sử dụng PHP 8.0 (tức là phiên bản PHP mới nhất), sẽ được chuyển đổi sang phiên bản thấp hơn cho PHP để sản xuất chạy trong ứng dụng hoặc môi trường kế thừa.

Điều này có hiệu quả cho phép chúng tôi trở thành những nhà phát triển giỏi hơn, tạo ra mã với chất lượng cao hơn. Điều này là do mã nguồn của chúng tôi có thể sử dụng các kiểu liên hợp của PHP 8.0, các thuộc tính được định kiểu của PHP 7.4 và các kiểu và kiểu giả khác nhau được thêm vào mỗi phiên bản PHP mới ( mixed từ PHP 8.0, object từ PHP 7.2), trong số các tính năng hiện đại khác của PHP.

Sử dụng các tính năng này, chúng tôi có thể bắt lỗi tốt hơn trong quá trình phát triển và viết mã dễ đọc hơn.

Bây giờ, chúng ta hãy nhìn vào những hạn chế.

Nó phải được mã hóa và duy trì

Hiệu trưởng có thể chuyển mã tự động, nhưng quá trình này có thể sẽ yêu cầu một số đầu vào thủ công để làm cho nó hoạt động với thiết lập cụ thể của chúng tôi.

Thư viện bên thứ ba cũng phải được vận chuyển

Điều này trở thành một vấn đề bất cứ khi nào việc chuyển đổi chúng tạo ra lỗi vì sau đó chúng tôi phải nghiên cứu kỹ mã nguồn của chúng để tìm ra lý do có thể. Nếu sự cố có thể được khắc phục và dự án là nguồn mở, chúng tôi sẽ cần gửi một yêu cầu kéo. Nếu thư viện không phải là mã nguồn mở, chúng tôi có thể gặp phải rào cản.

Hiệu trưởng không thông báo cho chúng tôi khi mã không thể được vận chuyển

Nếu mã nguồn chứa các thuộc tính PHP 8.0 hoặc bất kỳ tính năng nào khác không thể chuyển đổi, chúng tôi không thể tiếp tục. Tuy nhiên, Hiệu trưởng sẽ không kiểm tra điều kiện này, vì vậy chúng tôi cần phải làm điều đó theo cách thủ công. Đây có thể không phải là vấn đề lớn liên quan đến mã nguồn của chúng ta vì chúng ta đã quen thuộc với nó, nhưng nó có thể trở thành một trở ngại liên quan đến sự phụ thuộc của bên thứ ba.

Thông tin gỡ lỗi sử dụng mã được vận chuyển, không phải mã nguồn

Khi ứng dụng tạo ra thông báo lỗi với một dấu vết ngăn xếp trong quá trình sản xuất, số dòng sẽ trỏ đến mã được chuyển. Chúng ta cần chuyển đổi lại từ mã đã chuyển đổi sang mã gốc để tìm số dòng tương ứng trong mã nguồn.

Đăng kí để nhận thư mới

Mã được vận chuyển cũng phải có tiền tố

Dự án được chuyển đổi của chúng tôi và một số thư viện khác cũng được cài đặt trong môi trường sản xuất có thể sử dụng cùng sự phụ thuộc của bên thứ ba. Phần phụ thuộc của bên thứ ba này sẽ được chuyển đổi cho dự án của chúng tôi và giữ mã nguồn ban đầu của nó cho thư viện khác. Do đó, phiên bản được chuyển đổi phải được đặt tiền tố qua PHP-Scoper, Strauss hoặc một số công cụ khác để tránh xung đột tiềm ẩn.

Vận chuyển phải diễn ra trong quá trình tích hợp liên tục (CI)

Vì mã được chuyển tự nhiên sẽ ghi đè mã nguồn, chúng ta không nên chạy quá trình chuyển mã trên các máy tính đang phát triển của mình, nếu không chúng ta sẽ có nguy cơ tạo ra các tác dụng phụ. Chạy quy trình trong khi chạy CI phù hợp hơn (thêm về điều này bên dưới).

Cách chuyển mã PHP

Trước tiên, chúng ta cần cài đặt Principal trong dự án của mình để phát triển:

composer require rector/rector --dev

Sau đó, chúng tôi tạo một tệp cấu hình rector.php trong thư mục gốc của dự án chứa các bộ quy tắc bắt buộc. Để hạ cấp mã từ PHP 8.0 xuống 7.1, chúng tôi sử dụng cấu hình sau:

use RectorSetValueObjectDowngradeSetList; use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $containerConfigurator->import(DowngradeSetList::PHP_80); $containerConfigurator->import(DowngradeSetList::PHP_74); $containerConfigurator->import(DowngradeSetList::PHP_73); $containerConfigurator->import(DowngradeSetList::PHP_72); };

Để đảm bảo quá trình thực thi như mong đợi, chúng ta có thể chạy lệnh process của Hiệu trưởng ở chế độ khô, chuyển (các) vị trí để xử lý (trong trường hợp này là tất cả các tệp trong thư mục src/ ):

vendor/bin/rector process src --dry-run

Để thực hiện chuyển đổi, chúng tôi chạy lệnh process của Hiệu trưởng, lệnh này sẽ sửa đổi các tệp trong vị trí hiện có của chúng:

vendor/bin/rector process src

Xin lưu ý: nếu chúng tôi chạy rector process biểu diễn trong máy tính phát triển của mình, mã nguồn sẽ được chuyển đổi tại chỗ, dưới src/ . Tuy nhiên, chúng tôi muốn tạo mã đã chuyển đổi ở một vị trí khác để không ghi đè mã nguồn khi hạ cấp mã. Vì lý do này, việc chạy quy trình là phù hợp nhất trong quá trình tích hợp liên tục.

Tối ưu hóa quá trình vận chuyển

Để tạo ra một sản phẩm có thể chuyển đổi được cho quá trình sản xuất, chỉ có mã cho sản xuất phải được chuyển đổi; mã chỉ cần thiết để phát triển có thể được bỏ qua. Điều đó có nghĩa là chúng ta có thể tránh chuyển đổi tất cả các bài kiểm tra (cho cả dự án của chúng tôi và các phụ thuộc của nó) và tất cả các phụ thuộc để phát triển.

Liên quan đến các bài kiểm tra, chúng tôi sẽ biết vị trí của các bài kiểm tra cho dự án của chúng tôi – ví dụ: trong tests/ . Chúng ta cũng phải tìm ra vị trí của những cái phụ thuộc – ví dụ, trong thư mục con của chúng là tests/ , test/Test/ (đối với các thư viện khác nhau). Sau đó, chúng tôi yêu cầu Hiệu trưởng bỏ qua việc xử lý các thư mục này:

return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ // Skip tests '*/tests/*', '*/test/*', '*/Test/*', ]); };

Liên quan đến các phần phụ thuộc, Composer biết cái nào dành cho phát triển (những cái được nhập require-dev trong composer.json ) và cái nào dành cho sản xuất (những cái được nhập require ).

Để truy xuất từ Composer các đường dẫn của tất cả các phần phụ thuộc cho quá trình sản xuất, chúng tôi chạy:

composer info --path --no-dev

Lệnh này sẽ tạo ra một danh sách các phụ thuộc với tên và đường dẫn của chúng, như sau:

brain/cortex /Users/leo/GitHub/leoloso/PoP/vendor/brain/cortex composer/installers /Users/leo/GitHub/leoloso/PoP/vendor/composer/installers composer/semver /Users/leo/GitHub/leoloso/PoP/vendor/composer/semver guzzlehttp/guzzle /Users/leo/GitHub/leoloso/PoP/vendor/guzzlehttp/guzzle league/pipeline /Users/leo/GitHub/leoloso/PoP/vendor/league/pipeline

Chúng tôi có thể trích xuất tất cả các đường dẫn và đưa chúng vào lệnh Hiệu trưởng, sau đó sẽ xử lý thư mục src/ dự án của chúng tôi cộng với các thư mục chứa tất cả các phụ thuộc cho sản xuất:

$ paths="$(composer info --path --no-dev | cut -d' ' -f2- | sed 's/ //g' | tr 'n' ' ')" $ vendor/bin/rector process src $paths

Một cải tiến hơn nữa có thể ngăn Hiệu trưởng xử lý các phần phụ thuộc đó đã sử dụng phiên bản PHP đích. Nếu một thư viện đã được mã hóa bằng PHP 7.1 (hoặc bất kỳ phiên bản nào bên dưới), thì không cần phải chuyển nó sang PHP 7.1.

Để đạt được điều này, chúng tôi có thể lấy danh sách các thư viện yêu cầu PHP 7.2 trở lên và chỉ xử lý những thư viện đó. Chúng tôi sẽ lấy tên của tất cả các thư viện này thông qua lệnh why-not của Composer, như sau:

composer why-not php "7.1.*" | grep -o "S*/S*"

Bởi vì lệnh này không hoạt động với cờ --no-dev , để chỉ bao gồm các phụ thuộc cho sản xuất, trước tiên chúng ta cần xóa các phụ thuộc để phát triển và tạo lại trình tải tự động, thực hiện lệnh, sau đó thêm lại chúng:

$ composer install --no-dev $ packages=$(composer why-not php "7.1.*" | grep -o "S*/S*") $ composer install

info --path truy xuất đường dẫn cho một gói, với định dạng sau:

# Executing this command $ composer info psr/cache --path # Produces this response: psr/cache /Users/leo/GitHub/leoloso/PoP/vendor/psr/cache

Chúng tôi thực hiện lệnh này cho tất cả các mục trong danh sách của chúng tôi để lấy tất cả các đường dẫn đến transpile:

Cần một giải pháp lưu trữ mang lại cho bạn lợi thế cạnh tranh? Kinsta giúp bạn bao phủ bởi tốc độ đáng kinh ngạc, bảo mật hiện đại và tự động mở rộng quy mô. Kiểm tra các kế hoạch của chúng tôi

for package in $packages do path=$(composer info $package --path | cut -d' ' -f2-) paths="$paths $path" done

Cuối cùng, chúng tôi cung cấp danh sách này cho Hiệu trưởng (cộng với thư mục src/ của dự án):

vendor/bin/rector process src $paths

Những cạm bẫy cần tránh khi vận chuyển mã

Chuyển mã có thể được coi là một nghệ thuật, thường yêu cầu các chỉnh sửa cụ thể cho dự án. Hãy xem một số vấn đề mà chúng tôi có thể gặp phải.

Các quy tắc chuỗi không phải lúc nào cũng được xử lý

Quy tắc chuỗi là khi quy tắc cần chuyển đổi mã do quy tắc trước đó tạo ra.

Ví dụ, thư viện symfony/cache chứa đoạn mã này:

final class CacheItem implements ItemInterface { public function tag($tags): ItemInterface { // ... return $this; } }

Khi chuyển từ PHP 7.4 sang 7.3, tag hàm phải trải qua hai sửa đổi:

  • Loại trả về ItemInterface trước tiên phải được chuyển đổi thành self , do quy tắc DowngradeCovariantReturnTypeRector
  • Sau đó, loại trả lại self phải được xóa, do quy tắc DowngradeSelfTypeDeclarationRector

Kết quả cuối cùng sẽ là:

final class CacheItem implements ItemInterface { public function tag($tags) { // ... return $this; } }

Tuy nhiên, Hiệu trưởng chỉ xuất ra giai đoạn trung gian:

final class CacheItem implements ItemInterface { public function tag($tags): self { // ... return $this; } }

Vấn đề là Hiệu trưởng không phải lúc nào cũng có thể kiểm soát thứ tự áp dụng các quy tắc.

Giải pháp là xác định các quy tắc chuỗi nào không được xử lý và thực hiện một lần chạy Hiệu trưởng mới để áp dụng chúng.

Để xác định các quy tắc được xâu chuỗi, chúng tôi chạy Hiệu trưởng hai lần trên mã nguồn, như sau:

$ vendor/bin/rector process src $ vendor/bin/rector process src --dry-run

Lần đầu tiên, chúng tôi chạy Hiệu trưởng như mong đợi, để thực hiện chuyển ngữ. Lần thứ hai, chúng tôi sử dụng cờ --dry-run để khám phá xem có thay đổi nào cần thực hiện hay không. Nếu có, lệnh sẽ thoát với mã lỗi và đầu ra “diff” sẽ cho biết (các) quy tắc nào vẫn có thể được áp dụng. Điều đó có nghĩa là lần chạy đầu tiên không hoàn tất, với một số quy tắc chuỗi không được xử lý.

Running Rector with --dry-run flag
Chạy Hiệu trưởng với cờ –dry-run

Khi chúng tôi đã xác định được quy tắc chuỗi (hoặc quy tắc) chưa được áp dụng, sau đó chúng tôi có thể tạo một tệp cấu hình Hiệu trưởng khác – ví dụ: rector-chained-rule.php sẽ thực thi quy tắc bị thiếu. Thay vì xử lý bộ quy tắc đầy đủ cho tất cả các tệp trong src/ , lần này, chúng tôi có thể chạy quy tắc bị thiếu cụ thể trên tệp cụ thể nơi nó cần được áp dụng:

// rector-chained-rule.php use RectorCoreConfigurationOption; use RectorDowngradePhp74RectorClassMethodDowngradeSelfTypeDeclarationRector; use SymfonyComponentDependencyInjectionLoaderConfiguratorContainerConfigurator; return static function (ContainerConfigurator $containerConfigurator): void { $services = $containerConfigurator->services(); $services->set(DowngradeSelfTypeDeclarationRector::class); $parameters = $containerConfigurator->parameters(); $parameters->set(Option::PATHS, [ __DIR__ . '/vendor/symfony/cache/CacheItem.php', ]); };

Cuối cùng, chúng tôi yêu cầu Hiệu trưởng trong lần vượt qua thứ hai sử dụng tệp cấu hình mới thông qua đầu vào --config :

# First pass with all modifications $ vendor/bin/rector process src # Second pass to fix a specific problem $ vendor/bin/rector process --config=rector-chained-rule.php

Sự phụ thuộc của người soạn nhạc có thể không nhất quán

Các thư viện có thể khai báo một phần phụ thuộc sẽ được dự kiến phát triển (tức là theo require-dev trong composer.json ), tuy nhiên, vẫn tham chiếu một số mã từ chúng để sản xuất (chẳng hạn như trên một số tệp trong src/ , không phải tests/ ).

Thông thường, đây không phải là vấn đề vì mã đó có thể không được tải trên sản xuất, vì vậy sẽ không bao giờ có lỗi trên ứng dụng. Tuy nhiên, khi Hiệu trưởng xử lý mã nguồn và các phụ thuộc của nó, nó xác nhận rằng tất cả các mã được tham chiếu đều có thể được tải. Hiệu trưởng sẽ gặp lỗi nếu bất kỳ tệp nào tham chiếu đến một số đoạn mã từ một thư viện chưa được cài đặt (vì nó được khai báo là chỉ cần thiết để phát triển).

Ví dụ: lớp EarlyExpirationHandler từ thành phần Cache của Symfony triển khai giao diện MessageHandlerInterface từ thành phần Messenger:

class EarlyExpirationHandler implements MessageHandlerInterface { //... }

Tuy nhiên, symfony/cache khai báo symfony/messenger là một phụ thuộc để phát triển. Sau đó, khi chạy hiệu trưởng trên một dự án phụ thuộc vào symfony/cache , nó sẽ xuất hiện một lỗi:

[ERROR] Could not process "vendor/symfony/cache/Messenger/EarlyExpirationHandler.php" file, due to: "Analyze error: "Class SymfonyComponentMessengerHandlerMessageHandlerInterface not found.". Include your files in "$parameters->set(Option::AUTOLOAD_PATHS, [...]);" in "rector.php" config. See https://github.com/rectorphp/rector#configuration".

Có ba giải pháp cho vấn đề này:

  1. Trong cấu hình Hiệu trưởng, bỏ qua việc xử lý tệp tham chiếu đến đoạn mã đó:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::SKIP, [ __DIR__ . '/vendor/symfony/cache/Messenger/EarlyExpirationHandler.php', ]); };
  1. Tải xuống thư viện bị thiếu và thêm đường dẫn của nó để được tự động tải bởi Hiệu trưởng:
return static function (ContainerConfigurator $containerConfigurator): void { // ... $parameters->set(Option::AUTOLOAD_PATHS, [ __DIR__ . '/vendor/symfony/messenger', ]); };
  1. Có dự án của bạn phụ thuộc vào thư viện bị thiếu để sản xuất:
composer require symfony/messenger

Vận chuyển và tích hợp liên tục

Như đã đề cập trước đó, trong các máy tính phát triển của chúng ta, chúng ta phải sử dụng cờ --dry-run khi chạy Principal, hoặc nếu không, mã nguồn sẽ bị ghi đè bằng mã được chuyển. Vì lý do này, sẽ phù hợp hơn để chạy quá trình chuyển vị thực tế trong quá trình tích hợp liên tục (CI), nơi chúng tôi có thể quay vòng các trình chạy tạm thời để thực hiện quy trình.

Thời điểm lý tưởng để thực hiện quá trình chuyển đổi là khi tạo bản phát hành cho dự án của chúng tôi. Ví dụ: mã bên dưới là quy trình làm việc cho GitHub Actions, quy trình này tạo ra bản phát hành plugin WordPress:

name: Generate Installable Plugin and Upload as Release Asset on: release: types: [published] jobs: build: name: Build, Downgrade and Upload Release runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Downgrade code for production (to PHP 7.1) run: | composer install vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php - name: Build project for production run: | composer install --no-dev --optimize-autoloader mkdir build - name: Create artifact uses: montudor/[email protected] with: args: zip -X -r build/graphql-api.zip . -x *.git* node_modules/* .* "*/.*" CODE_OF_CONDUCT.md CONTRIBUTING.md ISSUE_TEMPLATE.md PULL_REQUEST_TEMPLATE.md rector.php *.dist composer.* dev-helpers** build** - name: Upload artifact uses: actions/[email protected] with: name: graphql-api path: build/graphql-api.zip - name: Upload to release uses: JasonEtco/[email protected] with: args: build/graphql-api.zip application/zip env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

Dòng công việc này chứa một quy trình tiêu chuẩn để phát hành một plugin WordPress thông qua GitHub Actions. Việc bổ sung mới, để chuyển mã của plugin từ PHP 7.4 sang 7.1, sẽ xảy ra trong bước này:

 - name: Downgrade code for production (to PHP 7.1) run: | vendor/bin/rector process sed -i 's/Requires PHP: 7.4/Requires PHP: 7.1/' graphql-api.php

Được kết hợp với nhau, quy trình làm việc này hiện thực hiện các bước sau:

  1. Kiểm tra mã nguồn cho một plugin WordPress từ kho lưu trữ của nó, được viết bằng PHP 7.4
  2. Cài đặt các phần phụ thuộc vào Trình soạn nhạc của nó
  3. Chuyển mã của nó từ PHP 7.4 sang 7.1
  4. Sửa đổi mục nhập “Yêu cầu PHP” trong tiêu đề tệp chính của plugin từ "7.4" thành "7.1"
  5. Loại bỏ các phụ thuộc cần thiết để phát triển
  6. Tạo tệp .zip của plugin, loại trừ tất cả các tệp không cần thiết
  7. Tải lên tệp .zip dưới dạng nội dung phát hành (và ngoài ra, dưới dạng phần mềm cho Hành động GitHub)

Kiểm tra mã được vận chuyển

Khi mã đã được chuyển sang PHP 7.1, làm thế nào để chúng ta biết rằng nó hoạt động tốt? Hay nói cách khác, làm sao chúng ta biết nó đã được chuyển đổi hoàn toàn và không có tàn tích nào của các phiên bản cao hơn của mã PHP bị bỏ lại?

Tương tự như chuyển mã, chúng ta có thể triển khai giải pháp trong một quy trình CI. Ý tưởng là thiết lập môi trường của người chạy với PHP 7.1 và chạy một linter trên mã đã được chuyển vị. Nếu bất kỳ đoạn mã nào không tương thích với PHP 7.1 (chẳng hạn như một thuộc tính đã nhập từ PHP 7.4 chưa được chuyển đổi), thì linter sẽ gặp lỗi.

Một linter cho PHP hoạt động tốt là PHP Parallel Lint. Chúng tôi có thể cài đặt thư viện này như một phần phụ thuộc để phát triển trong dự án của chúng tôi hoặc yêu cầu quá trình CI cài đặt nó như một dự án Composer độc lập:

composer create-project php-parallel-lint/php-parallel-lint

Bất cứ khi nào mã chứa PHP 7.2 trở lên, PHP Parallel Lint sẽ gặp lỗi như sau:

Run php-parallel-lint/parallel-lint layers/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php PHP 7.1.33 | 10 parallel jobs ............................................................ 60/2870 (2 %) ............................................................ 120/2870 (4 %) ... ............................................................ 660/2870 (22 %) .............X.............................................. 720/2870 (25 %) ............................................................ 780/2870 (27 %) ... ............................................................ 2820/2870 (98 %) .................................................. 2870/2870 (100 %) Checked 2870 files in 15.4 seconds Syntax error found in 1 file ------------------------------------------------------------ Parse error: layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php:55 53| '0.8.0', 54| __('GraphQL API for WordPress', 'graphql-api'), > 55| ))) { 56| $plugin->setup(); 57| } Unexpected ')' in layers/GraphQLAPIForWP/plugins/graphql-api-for-wp/graphql-api.php on line 55 Error: Process completed with exit code 1.

Hãy thêm linter vào quy trình làm việc của CI của chúng tôi. Các bước để thực thi chuyển mã từ PHP 8.0 sang 7.1 và kiểm tra nó là:

  1. Kiểm tra mã nguồn
  2. Để môi trường chạy PHP 8.0, để Hiệu trưởng có thể diễn giải mã nguồn
  3. Chuyển mã sang PHP 7.1
  4. Cài đặt công cụ PHP linter
  5. Chuyển phiên bản PHP của môi trường sang 7.1
  6. Chạy linter trên mã đã chuyển

Dòng công việc Hành động GitHub này thực hiện công việc:

name: Downgrade PHP tests jobs: main: name: Downgrade code to PHP 7.1 via Rector, and execute tests runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/[email protected] - name: Set-up PHP uses: shivammathur/[email protected] with: php-version: 8.0 coverage: none - name: Local packages - Downgrade PHP code via Rector run: | composer install vendor/bin/rector process # Prepare for testing on PHP 7.1 - name: Install PHP Parallel Lint run: composer create-project php-parallel-lint/php-parallel-lint --ansi - name: Switch to PHP 7.1 uses: shivammathur/[email protected] with: php-version: 7.1 coverage: none # Lint the transpiled code - name: Run PHP Parallel Lint on PHP 7.1 run: php-parallel-lint/parallel-lint src/ vendor/ --exclude vendor/symfony/polyfill-ctype/bootstrap80.php --exclude vendor/symfony/polyfill-intl-grapheme/bootstrap80.php --exclude vendor/symfony/polyfill-intl-idn/bootstrap80.php --exclude vendor/symfony/polyfill-intl-normalizer/bootstrap80.php --exclude vendor/symfony/polyfill-mbstring/bootstrap80.php

Xin lưu ý rằng một số tệp bootstrap80.php từ các thư viện polyfill của Symfony (không cần chuyển dịch) phải được loại trừ khỏi linter. Các tệp này chứa PHP 8.0, vì vậy linter sẽ gặp lỗi khi xử lý chúng. Tuy nhiên, việc loại trừ các tệp này là an toàn vì chúng sẽ chỉ được tải trên phiên bản sản xuất khi chạy PHP 8.0 trở lên:

if (PHP_VERSION_ID >= 80000) { return require __DIR__.'/bootstrap80.php'; }

Bản tóm tắt

Bài viết này đã hướng dẫn chúng tôi cách chuyển mã PHP, cho phép chúng tôi sử dụng PHP 8.0 trong mã nguồn và tạo một bản phát hành hoạt động trên PHP 7.1. Việc vận chuyển được thực hiện thông qua Hiệu trưởng, một công cụ tái tạo PHP.

Việc dịch mã của chúng tôi giúp chúng tôi trở thành những nhà phát triển tốt hơn vì chúng tôi có thể bắt lỗi tốt hơn trong quá trình phát triển và tạo ra mã dễ đọc và dễ hiểu hơn một cách tự nhiên.

Việc dịch mã cũng cho phép chúng tôi tách mã của mình với các yêu cầu PHP cụ thể từ CMS. Bây giờ chúng tôi có thể làm như vậy nếu chúng tôi muốn sử dụng phiên bản PHP mới nhất để tạo một plugin WordPress hoặc mô-đun Drupal có sẵn công khai mà không hạn chế nghiêm trọng cơ sở người dùng của chúng tôi.

Bạn có câu hỏi nào về việc chuyển ngữ PHP không? Cho chúng tôi biết trong phần ý kiến!


Tiết kiệm thời gian, chi phí và tối đa hóa hiệu suất trang web với:

  • Trợ giúp tức thì từ các chuyên gia lưu trữ WordPress, 24/7.
  • Tích hợp Cloudflare Enterprise.
  • Tiếp cận khán giả toàn cầu với 34 trung tâm dữ liệu trên toàn thế giới.
  • Tối ưu hóa với Giám sát Hiệu suất Ứng dụng được tích hợp sẵn của chúng tôi.

Tất cả những điều đó và hơn thế nữa, trong một kế hoạch không có hợp đồng dài hạn, hỗ trợ di chuyển và đảm bảo hoàn tiền trong 30 ngày. Kiểm tra các kế hoạch của chúng tôi hoặc nói chuyện với bộ phận bán hàng để tìm ra kế hoạch phù hợp với bạn.