php预处理_如何用预处理让 PHP 更先进
原標(biāo)題:如何用預(yù)處理讓 PHP 更先進(jìn)
先來(lái)點(diǎn)趣事。不久以前, 來(lái)添加 Python 的 range 語(yǔ)法。然后, 大蝦 ,并且 建議為 PHP 添加 C# 風(fēng)格的 getter 和 setter。
我意識(shí)到對(duì)于一個(gè)局外人來(lái)說(shuō),建議和實(shí)現(xiàn)新的語(yǔ)言特性是件緩慢的事情,所以我打開(kāi)了自己的編輯器……
這篇教程的代碼可以在 上找到。它在 PHP^7.1 版本測(cè)試,生成的代碼可以運(yùn)行在 PHP^5.6|^7.0。
宏是如何運(yùn)行的?
從我上次談及宏,已經(jīng)有一段時(shí)間了(也許你從來(lái)沒(méi)有聽(tīng)說(shuō)過(guò)他們)。為了更新存儲(chǔ)空間,他們會(huì)采用類(lèi)似這樣的代碼:
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 語(yǔ)法,如下所示:
$few = many[ 1.. 3];
…轉(zhuǎn)化為合法的 PHP 語(yǔ)法,如下所示:
$few= eval( '$list = '. '$many'. ';'. '$lower = '. explode( '..', '1..3')[0] . ';'. '$upper = '. explode( '..', '1..3')[1] . ';'. 'return array_slice($list, $lower, $upper - $lower);');
如果你想了解這是如何運(yùn)行的,可以查看我之前發(fā)布的 。
秘訣是理解解析器的如何分割代碼字符串,構(gòu)建一個(gè)宏模式,然后將該模式遞歸地應(yīng)用于新的語(yǔ)法之上的。
但是 沒(méi)有很好的文檔。我們很難知道模式究竟是什么樣子的,或者最終生成什么樣的有效語(yǔ)法。每個(gè)新的應(yīng)用程序都要求編寫(xiě)一個(gè)類(lèi)似這樣的教程,其他人才能真正理解發(fā)生了什么。
創(chuàng)建基準(zhǔn)代碼
所以,讓我們來(lái)看看手邊的應(yīng)用程序。我們模仿 C# 的語(yǔ)法向 PHP 添加 getter 和 setter 語(yǔ)法。在我們可以做到這一點(diǎn)之前,我們需要有一個(gè)好的基準(zhǔn)代碼,用于后續(xù)開(kāi)發(fā)。 也許是某種形式的trait,我們可以將其添加到需要這個(gè)新功能的類(lèi)中。
我們需要實(shí)現(xiàn)代碼來(lái)檢查類(lèi)定義,并為每個(gè)特殊屬性或注釋動(dòng)態(tài)創(chuàng)建 getter 和 setter 方法。
也許我們可以從定義一個(gè)特殊方法名稱(chēng)的格式開(kāi)始,并且使用 __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); } }}
每個(gè)以 __get_ 和 __set_ 命名開(kāi)始的方法都需要與一個(gè)尚未定義的屬性相關(guān)聯(lián)。我們可以參考類(lèi)似下面的語(yǔ)法:
namespace App; classSprocket{ private$ type{ get { return$ this-> type; } set { $ this-> type= strtoupper($value); } };}
…被轉(zhuǎn)化為和下面非常類(lèi)似的格式:
namespaceApp; classSprocket{ useAccessorTrait; private$type; privatefunction__get_type(){ return$this->type; } privatefunction__set_type($value){ $this->type = strtoupper($value); }}
定義所需的宏是這些工作中最難的部分。鑒于文檔缺乏(和未廣泛使用),并且只有少數(shù)有用的異常消息,這里面大多是反復(fù)驗(yàn)證和試錯(cuò)的結(jié)果。
我花了幾個(gè)小時(shí)整理出以下幾種模式:
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 }}
好吧,讓我們看看這兩個(gè)宏是做什么的:
我們從匹配 class MyClass {...} 開(kāi)始,并插入我們之前構(gòu)建的 AccessorTrait。 這里提供了 _get 和 _set 的實(shí)現(xiàn),其中將 _get_bar 鏈接到 print $class->bar 中。
我們匹配 accessor 塊的語(yǔ)法,并將其替換為通用的屬性定義,后面是幾個(gè)獨(dú)立的方法定義。 我們可以在這些函數(shù)中封裝 get{...} 和 set{...} 塊的實(shí)現(xiàn)部分。
起初,當(dāng)你運(yùn)行這個(gè)代碼時(shí),你會(huì)遇到一個(gè)錯(cuò)誤。這是因?yàn)?··unvar 函數(shù)不是宏處理器的標(biāo)準(zhǔn)組件。這是我不得不添加的部分,從 $type 到 type 的轉(zhuǎn)換:
namespaceYayDSLExpanders; useYayToken; useYayTokenStream; functionunvar(TokenStream $ts): TokenStream{ $str = str_replace( '$', '', (string) $ts); returnTokenStream::fromSequence( newToken( T_CONSTANT_ENCAPSED_STRING, $str ) ) ;}
我本可以拷貝(幾乎全部)的 stringify 擴(kuò)展器的代碼,它是包含在宏解析器代碼之中。為了弄清楚 Yay 如何實(shí)現(xiàn)的,你不需要了解很多關(guān)于 Yay 內(nèi)部結(jié)構(gòu)。將 TokenStream 轉(zhuǎn)換為 string(在此上下文中)意味著你正在獲取當(dāng)前token所標(biāo)記的字符串的值 - 在本例中為 ··unvar(T_VARIABLE·var) - 并對(duì)其執(zhí)行字符串操作。
(string) $ts 變成“$type”,而不是“T_VARIABLE·var”。
通常,當(dāng)這些宏被放置在要處理的腳本中,會(huì)自動(dòng)完成這些。換句話說(shuō),我們可以創(chuàng)建一個(gè)類(lèi)似于下面的腳本:
<?phpmacro ·unsafe { ...} >> { ...}macro ·unsafe { ...} >> { ...}namespace App; traitAccessorTrait{ ...} classSprocket{ private$ type{ get { return$ this-> type; } set { $ this-> type= strtoupper($value); } };}
…然后我們可以用下面命令運(yùn)行它:
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自動(dòng)轉(zhuǎn)換
手動(dòng)過(guò)程就是這樣子。在每次更改 src/Sprocket.pre 時(shí)誰(shuí)會(huì)想去運(yùn)行這個(gè) bash 命令呢? 幸運(yùn)的是,我們可以將其自動(dòng)化!
第一步是定義自定義的自動(dòng)加載器:
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 自動(dòng)加載功能,通過(guò) Composer 的自動(dòng)加載器包含它。
該定義的第一部分直接來(lái)自于 的示例實(shí)現(xiàn)。我們獲得 Composer 的 PSR-4 定義文件,對(duì)于每個(gè)前綴,我們檢查它是否與當(dāng)前正在加載的類(lèi)匹配。
如果匹配,我們檢查每個(gè)可能的路徑,直到我們找到一個(gè) file.pre,其中定義了我們的自定義語(yǔ)法。 之后,我們獲得 macros.pre 文件的內(nèi)容(在項(xiàng)目基目錄中),并創(chuàng)建一個(gè)臨時(shí)文件 - 使用 macros.pre 內(nèi)容+匹配的文件的內(nèi)容命名。這意味著宏在傳遞給 Yay 的文件中可用。 待 Yay 編譯完 file.pre.interim→file.php 之后,我們就刪除 file.pre.interim。
這個(gè)處理過(guò)程的代碼如下:
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;
注意,在調(diào)用 spl_autoload_register 結(jié)束時(shí)的那兩個(gè)布爾值。第一個(gè)是標(biāo)示這個(gè)自動(dòng)加載器是否應(yīng)該拋出異常加載錯(cuò)誤。 第二個(gè)是標(biāo)示這個(gè)自動(dòng)加載器是否應(yīng)該預(yù)先加載到堆棧中。 這把它放在 Composer 自動(dòng)加載器之前,這意味著我們可以在 Composer 嘗試加載 file.php 之前轉(zhuǎn)換 file.pre!
創(chuàng)建一個(gè)插件框架
這種自動(dòng)化實(shí)現(xiàn)很棒,但如果在每個(gè)項(xiàng)目中都重新操作是非常浪費(fèi)的。 如果我們可以?xún)H添加一個(gè) composer require 依賴(lài)(為獲得一個(gè)新的語(yǔ)言功能)就可以正常工作,這怎么樣呢?讓我們?cè)囋嚳?.....
首先,我們需要?jiǎng)?chuàng)建一個(gè)新的 repo,包含以下文件:
composer.json→ 自動(dòng)加載下列文件
functions.php→ 創(chuàng)建宏路徑函數(shù)(在其他庫(kù)中可以動(dòng)態(tài)添加自己的宏文件)
expanders.php→ 創(chuàng)建擴(kuò)展器函數(shù),比如 ··unvar
autoload.php→ augment Composer 的自動(dòng)加載器,將每個(gè)其他庫(kù)的宏文件加載到每個(gè)編譯的 .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}
上面代碼來(lái)自 composer.json
上面代碼來(lái)自 functions.php
你可能正在想著使用 $GLOBALS 作為存儲(chǔ)宏文件路徑。這并不重要,因?yàn)槲覀兛梢允褂弥T多其他方式來(lái)存儲(chǔ)這些路徑。 這里僅僅是演示模式實(shí)現(xiàn)的最簡(jiǎn)單的方法。
這部分來(lái)自 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);
這部分來(lái)自 autoload.php
現(xiàn)在,附加的宏插件可以使用這些函數(shù)將自己的代碼掛接到系統(tǒng)中了...
創(chuàng)建新的語(yǔ)言功能
通過(guò)構(gòu)建插件代碼,我們可以將我們的類(lèi)訪問(wèn)器重構(gòu)為獨(dú)立的、可自動(dòng)應(yīng)用的功能。 我們需要?jiǎng)?chuàng)建幾個(gè)文件來(lái)實(shí)現(xiàn)這一點(diǎn):
composer.json→ 用于查找基本插件庫(kù)并自動(dòng)加載以下文件
macros.pre→ 當(dāng)前插件的宏代碼
functions.php→ 將 accessor 宏掛接到基本插件系統(tǒng)中
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}
這是來(lái)自 composer.json
namespacePre;addMacroPath( __DIR__. "/macros.pre");
這是來(lái)自 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 } };} >> { // ...}
這是來(lái)自 macros.pre
這個(gè)宏文件比以前的版本更冗長(zhǎng)。可能有一個(gè)更優(yōu)雅的方式來(lái)處理所有的關(guān)于 accessors 重定義的排列,但我目前還沒(méi)有找到。
整合在一起
現(xiàn)在,一切都很好地打包了,你可以直接使用語(yǔ)言功能。 看看這個(gè)快速演示!
你可以在 Github 上找到這些插件庫(kù):
結(jié)語(yǔ)
和所有的東西一樣,這可能被濫用。 宏也不例外。雖然它在概念上很酷, 但這個(gè)代碼絕對(duì)不是產(chǎn)品級(jí)代碼。
本文來(lái)自:https://www.oschina.net/translate/how-to-make-modern-php-more-modern-with-preprocessing
關(guān)注微信公眾號(hào):PHP技術(shù)大全
PHPer升級(jí)為大神并不難!返回搜狐,查看更多
責(zé)任編輯:
總結(jié)
以上是生活随笔為你收集整理的php预处理_如何用预处理让 PHP 更先进的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 3.MongoDB数据查询
- 下一篇: 开发人员:月薪过万与年薪百万之间的差距