php预处理_如何用预处理让 PHP 更先进
原標題:如何用預處理讓 PHP 更先進
先來點趣事。不久以前, 來添加 Python 的 range 語法。然后, 大蝦 ,并且 建議為 PHP 添加 C# 風格的 getter 和 setter。
我意識到對于一個局外人來說,建議和實現新的語言特性是件緩慢的事情,所以我打開了自己的編輯器……
這篇教程的代碼可以在 上找到。它在 PHP^7.1 版本測試,生成的代碼可以運行在 PHP^5.6|^7.0。
宏是如何運行的?
從我上次談及宏,已經有一段時間了(也許你從來沒有聽說過他們)。為了更新存儲空間,他們會采用類似這樣的代碼:
macro { →(···expression)} >> { ··stringify(···expression)}macro { T_VARIABLE·A[ ···range ]} >> { eval( '$list = '. →(T_VARIABLE·A) . ';'. '$lower = '. explode( '..', →(···range))[ 0] . ';'. '$upper = '. explode( '..', →(···range))[ 1] . ';'. 'return array_slice($list, $lower, $upper - $lower);')}
…并將自定義的 PHP 語法,如下所示:
$few = many[ 1.. 3];
…轉化為合法的 PHP 語法,如下所示:
$few= eval( '$list = '. '$many'. ';'. '$lower = '. explode( '..', '1..3')[0] . ';'. '$upper = '. explode( '..', '1..3')[1] . ';'. 'return array_slice($list, $lower, $upper - $lower);');
如果你想了解這是如何運行的,可以查看我之前發布的 。
秘訣是理解解析器的如何分割代碼字符串,構建一個宏模式,然后將該模式遞歸地應用于新的語法之上的。
但是 沒有很好的文檔。我們很難知道模式究竟是什么樣子的,或者最終生成什么樣的有效語法。每個新的應用程序都要求編寫一個類似這樣的教程,其他人才能真正理解發生了什么。
創建基準代碼
所以,讓我們來看看手邊的應用程序。我們模仿 C# 的語法向 PHP 添加 getter 和 setter 語法。在我們可以做到這一點之前,我們需要有一個好的基準代碼,用于后續開發。 也許是某種形式的trait,我們可以將其添加到需要這個新功能的類中。
我們需要實現代碼來檢查類定義,并為每個特殊屬性或注釋動態創建 getter 和 setter 方法。
也許我們可以從定義一個特殊方法名稱的格式開始,并且使用 __get 和 __set 方法:
namespaceApp; traitAccessorTrait{ /** * @inheritdoc* * @paramstring $property * @parammixed $value */publicfunction__get($property){ if(method_exists( $this, "__get_{$property}")) { return$this->{ "__get_{$property}"}(); } } /** * @inheritdoc* * @paramstring $property * @parammixed $value */publicfunction__set($property, $value){ if(method_exists( $this, "__set_{$property}")) { return$this->{ "__set_{$property}"}($value); } }}
每個以 __get_ 和 __set_ 命名開始的方法都需要與一個尚未定義的屬性相關聯。我們可以參考類似下面的語法:
namespace App; classSprocket{ private$ type{ get { return$ this-> type; } set { $ this-> type= strtoupper($value); } };}
…被轉化為和下面非常類似的格式:
namespaceApp; classSprocket{ useAccessorTrait; private$type; privatefunction__get_type(){ return$this->type; } privatefunction__set_type($value){ $this->type = strtoupper($value); }}
定義所需的宏是這些工作中最難的部分。鑒于文檔缺乏(和未廣泛使用),并且只有少數有用的異常消息,這里面大多是反復驗證和試錯的結果。
我花了幾個小時整理出以下幾種模式:
macro ·unsafe { ·ns()· class{ ···body }} >> {· class{ useAccessorTrait; ···body }}macro ·unsafe { privateT_VARIABLE· var{ get { ···getter } set { ···setter } };} >> { privateT_VARIABLE· var; privatefunction··concat(__get_ ··unvar(T_VARIABLE·var))(){ ···getter } privatefunction··concat(__set_ ··unvar(T_VARIABLE·var))($value){ ···setter }}
好吧,讓我們看看這兩個宏是做什么的:
我們從匹配 class MyClass {...} 開始,并插入我們之前構建的 AccessorTrait。 這里提供了 _get 和 _set 的實現,其中將 _get_bar 鏈接到 print $class->bar 中。
我們匹配 accessor 塊的語法,并將其替換為通用的屬性定義,后面是幾個獨立的方法定義。 我們可以在這些函數中封裝 get{...} 和 set{...} 塊的實現部分。
起初,當你運行這個代碼時,你會遇到一個錯誤。這是因為 ··unvar 函數不是宏處理器的標準組件。這是我不得不添加的部分,從 $type 到 type 的轉換:
namespaceYayDSLExpanders; useYayToken; useYayTokenStream; functionunvar(TokenStream $ts): TokenStream{ $str = str_replace( '$', '', (string) $ts); returnTokenStream::fromSequence( newToken( T_CONSTANT_ENCAPSED_STRING, $str ) ) ;}
我本可以拷貝(幾乎全部)的 stringify 擴展器的代碼,它是包含在宏解析器代碼之中。為了弄清楚 Yay 如何實現的,你不需要了解很多關于 Yay 內部結構。將 TokenStream 轉換為 string(在此上下文中)意味著你正在獲取當前token所標記的字符串的值 - 在本例中為 ··unvar(T_VARIABLE·var) - 并對其執行字符串操作。
(string) $ts 變成“$type”,而不是“T_VARIABLE·var”。
通常,當這些宏被放置在要處理的腳本中,會自動完成這些。換句話說,我們可以創建一個類似于下面的腳本:
<?phpmacro ·unsafe { ...} >> { ...}macro ·unsafe { ...} >> { ...}namespace App; traitAccessorTrait{ ...} classSprocket{ private$ type{ get { return$ this-> type; } set { $ this-> type= strtoupper($value); } };}
…然后我們可以用下面命令運行它:
vendor/bin/yay src/Sprocket.pre >>src/Sprocket.php
最后,我們就可以使用這些代碼了(需要 Composer PSR-4 autoloading):
require__DIR__. "/vendor/autoload.php";$sprocket = newAppSprocket();$sprocket->type = "acme sprocket"; print$sprocket->type; // Acme Sprocket自動轉換
手動過程就是這樣子。在每次更改 src/Sprocket.pre 時誰會想去運行這個 bash 命令呢? 幸運的是,我們可以將其自動化!
第一步是定義自定義的自動加載器:
spl_autoload_register( function($class){ $definitions = require__DIR__. "/vendor/composer/autoload_psr4.php"; foreach($definitions as$prefix => $paths) { $prefixLength = strlen($prefix); if(strncmp($prefix, $class, $prefixLength) !== 0) { continue; } $relativeClass = substr($class, $prefixLength); foreach($paths as$path) { $php = $path . "/". str_replace( "", "/", $relativeClass) . ".php"; $pre = $path . "/". str_replace( "", "/", $relativeClass) . ".pre"; $relative = ltrim(str_replace( __DIR__, "", $pre), DIRECTORY_SEPARATOR); $macros = __DIR__. "/macros.pre"; if(file_exists($pre)) { // ... convert and load file} } }}, false, true);
如 中所述,你可以將此文件保存為 autoload.php,并使用 files 自動加載功能,通過 Composer 的自動加載器包含它。
該定義的第一部分直接來自于 的示例實現。我們獲得 Composer 的 PSR-4 定義文件,對于每個前綴,我們檢查它是否與當前正在加載的類匹配。
如果匹配,我們檢查每個可能的路徑,直到我們找到一個 file.pre,其中定義了我們的自定義語法。 之后,我們獲得 macros.pre 文件的內容(在項目基目錄中),并創建一個臨時文件 - 使用 macros.pre 內容+匹配的文件的內容命名。這意味著宏在傳遞給 Yay 的文件中可用。 待 Yay 編譯完 file.pre.interim→file.php 之后,我們就刪除 file.pre.interim。
這個處理過程的代碼如下:
if(file_exists($php)) { unlink($php);}file_put_contents( "{$pre}.interim", str_replace( "<?php ", file_get_contents($macros), file_get_contents($pre) )); exec( "vendor/bin/yay {$pre}.interim >> {$php}");$comment = " # This file is generated, changes you make will be lost. # Make your changes in {$relative} instead.";file_put_contents( $php, str_replace( "<?php ", "<?phpn{$comment}", file_get_contents($php) )); unlink( "{$pre}.interim");require_once $php;
注意,在調用 spl_autoload_register 結束時的那兩個布爾值。第一個是標示這個自動加載器是否應該拋出異常加載錯誤。 第二個是標示這個自動加載器是否應該預先加載到堆棧中。 這把它放在 Composer 自動加載器之前,這意味著我們可以在 Composer 嘗試加載 file.php 之前轉換 file.pre!
創建一個插件框架
這種自動化實現很棒,但如果在每個項目中都重新操作是非常浪費的。 如果我們可以僅添加一個 composer require 依賴(為獲得一個新的語言功能)就可以正常工作,這怎么樣呢?讓我們試試看......
首先,我們需要創建一個新的 repo,包含以下文件:
composer.json→ 自動加載下列文件
functions.php→ 創建宏路徑函數(在其他庫中可以動態添加自己的宏文件)
expanders.php→ 創建擴展器函數,比如 ··unvar
autoload.php→ augment Composer 的自動加載器,將每個其他庫的宏文件加載到每個編譯的 .prefile 中{
"name":
"pre/plugin",
"require": {
"php":
"^7.0",
"yay/yay":
"dev-master"},
"autoload": {
"files": [
"functions.php",
"expanders.php",
"autoload.php"] },
"minimum-stability":
"dev",
"prefer-stable":
true}
上面代碼來自 composer.json
上面代碼來自 functions.php
你可能正在想著使用 $GLOBALS 作為存儲宏文件路徑。這并不重要,因為我們可以使用諸多其他方式來存儲這些路徑。 這里僅僅是演示模式實現的最簡單的方法。
這部分來自 expanders.php
<?phpnamespacePre ;if(file_exists(__DIR__. "/../../autoload.php")) { define("BASE_DIR", realpath(__DIR__. "/../../../"));}spl_autoload_register(function($class){ $definitions = requireBASE_DIR . "/vendor/composer/autoload_psr4.php"; foreach($definitions as$prefix => $paths) { // ...check $prefixLengthforeach($paths as$path) { // ...create $php and $pre$relative = ltrim(str_replace(BASE_DIR, "", $pre), DIRECTORY_SEPARATOR); $macros = BASE_DIR . "/macros.pre"; if(file_exists($pre)) { // ...remove existing PHP fileforeach(getMacroPaths() as$macroPath) { file_put_contents( "{$pre}.interim", str_replace( "<?php ", file_get_contents($macroPath), file_get_contents($pre) ) ); } // ...write and include the PHP file} } }}, false, true);
這部分來自 autoload.php
現在,附加的宏插件可以使用這些函數將自己的代碼掛接到系統中了...
創建新的語言功能
通過構建插件代碼,我們可以將我們的類訪問器重構為獨立的、可自動應用的功能。 我們需要創建幾個文件來實現這一點:
composer.json→ 用于查找基本插件庫并自動加載以下文件
macros.pre→ 當前插件的宏代碼
functions.php→ 將 accessor 宏掛接到基本插件系統中
src/AccessorsTrait.php→ 大致上保持不變{
"name":
"pre/class-accessors",
"require": {
"php":
"^7.0",
"pre/plugin":
"dev-master"},
"autoload": {
"files": [
"functions.php"],
"psr-4": {
"Pre":
"src"} },
"minimum-stability":
"dev",
"prefer-stable":
true}
這是來自 composer.json
namespacePre;addMacroPath( __DIR__. "/macros.pre");
這是來自 functions.php
macro · unsafe{ ·ns()· class{ ···body }} >> { · class{ use PreAccessorsTrait; ···body }}macro · unsafe{ privateT_VARIABLE·variable { get{ ···getter } set{ ···setter } };} >> { // ...}macro · unsafe{ privateT_VARIABLE·variable { set{ ···setter } get{ ···getter } };} >> { // ...}macro · unsafe{ privateT_VARIABLE·variable { set{ ···setter } };} >> { // ...}macro · unsafe{ privateT_VARIABLE·variable { get{ ···getter } };} >> { // ...}
這是來自 macros.pre
這個宏文件比以前的版本更冗長。可能有一個更優雅的方式來處理所有的關于 accessors 重定義的排列,但我目前還沒有找到。
整合在一起
現在,一切都很好地打包了,你可以直接使用語言功能。 看看這個快速演示!
你可以在 Github 上找到這些插件庫:
結語
和所有的東西一樣,這可能被濫用。 宏也不例外。雖然它在概念上很酷, 但這個代碼絕對不是產品級代碼。
本文來自:https://www.oschina.net/translate/how-to-make-modern-php-more-modern-with-preprocessing
關注微信公眾號:PHP技術大全
PHPer升級為大神并不難!返回搜狐,查看更多
責任編輯:
總結
以上是生活随笔為你收集整理的php预处理_如何用预处理让 PHP 更先进的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 3.MongoDB数据查询
- 下一篇: 开发人员:月薪过万与年薪百万之间的差距