php预处理_如何用预处理让 PHP 更先进

原标题:如何用预处理让 PHP 更先进

先来点趣事。不久以前, 来添加 Python 的 range 语法。然后, 大虾 ,并且 建议为 PHP 添加 C# 风格的 getter 和 setter。

我意识到对于一个局外人来说,建议和实现新的语言特性是件缓慢的事情,所以我打开了自己的编辑器……

这篇教程的代码可以在 上找到。它在 PHP^7.1 版本测试,生成的代码可以运行在 PHP^5.6|^7.0。

63f4c10eee4edfb85e077e84ff871868.png

宏是如何运行的?

从我上次谈及宏,已经有一段时间了(也许你从来没有听说过他们)。为了更新存储空间,他们会采用类似这样的代码:

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升级为大神并不难!返回搜狐,查看更多

责任编辑:

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/472641.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天池 在线编程 两句话中的不常见单词(哈希计数)

文章目录1. 题目2. 解题1. 题目 给定两个句子 A 和 B 。 &#xff08;句子是一串由空格分隔的单词。每个单词仅由小写字母组成。&#xff09; 如果一个单词在其中一个句子中只出现一次&#xff0c;在另一个句子中却没有出现&#xff0c;那么这个单词就是不常见的。 返回所有…

iphone屏幕上的圆圈怎么设置_iPhone手机屏幕突然变暗或者黑屏怎么办?看这里你就知道该怎么办...

阅读本文前&#xff0c;请您先点击上面的蓝色字体&#xff0c;再点击“关注”&#xff0c;这样您就可以继续免费收到各种有关娱乐的文章了。每天都有分享&#xff0c;完全是免费订阅&#xff0c;请放心关注。 …

C语言库函数大全及应用实例六

C语言库函数大全及应用实例六 原文:C语言库函数大全及应用实例六[编程资料]C语言库函数大全及应用实例六函数名: getlinesettings 功 能: 取当前线型、模式和宽度 用 法: void far getlinesettings(struct linesettingstype far *lininfo): 程序例: <?xml:namespace prefix…

天池 在线编程 最小的行程(动态规划)

文章目录1. 题目2. 解题1. 题目 给定一个二维矩阵&#xff0c;找到从上到下的最小路径。只能向左下&#xff0c;下&#xff0c;右下移动 所有的元素都是正整数 矩阵大小 < 200x200 样例 1: 输入: 1 2 3 4 5 6 7 8 9 输出: 12 解释: 最短的路径为:1->4->7, 返回12.样…

fluent瞬态计算终止条件在哪里设置_Fluent案例7【圆柱绕流】

一个瞬态的圆柱绕流案例知识点&#xff1a;瞬态圆柱绕流的模拟一个后处理的方法&#xff1a;将瞬态模型中一个点的速度变化绘成图表并将数值导出excel文件模型如下图所示&#xff0c;左边界为速度边界进口速度0.5m/s&#xff0c;试模拟出计算域中的速度变化打开workbench&#…

qq登录界面句柄_别小看QQ邮箱测试,80%的测试新手都不能写出完整的测试用例~...

对于很多刚进入测试行业的新手来说&#xff0c;由于自身的工作经验不足&#xff0c;虽有测试基础知识傍身&#xff0c;但仍然很难将测试用例写的尽善尽美。因此&#xff0c;学习别人的测试经验&#xff0c;将是你成为测试达人的必经之路。今天&#xff0c;我们就以QQ邮箱为例&a…

LeetCode 878. 第 N 个神奇数字(二分查找)

文章目录1. 题目2. 解题1. 题目 如果正整数可以被 A 或 B 整除&#xff0c;那么它是神奇的。 返回第 N 个神奇数字。由于答案可能非常大&#xff0c;返回它模 10^9 7 的结果。 示例 1&#xff1a; 输入&#xff1a;N 1, A 2, B 3 输出&#xff1a;2示例 2&#xff1a; 输…

为什么百度统计里面的广告那么多_里面东西一模一样的桶装方便面为什么比袋装贵那么多?...

今天特地买了一桶桶装的和一袋袋装的&#xff0c;同品牌同系列同口味&#xff0c;里面面饼&#xff0c;配料包也一模一样&#xff0c;桶装的只是多了一个小勺子&#xff0c;为什么一袋二元五&#xff0c;一桶就要卖4元&#xff1f;并且大家好像都不觉得有什么不妥……(好吧我就…

LeetCode 1790. 仅执行一次字符串交换能否使两个字符串相等

文章目录1. 题目2. 解题1. 题目 给你长度相等的两个字符串 s1 和 s2 。 一次 字符串交换 操作的步骤如下&#xff1a;选出某个字符串中的两个下标&#xff08;不必不同&#xff09;&#xff0c;并交换这两个下标所对应的字符。 如果对 其中一个字符串 执行 最多一次字符串交换…

gradle 上传jar包_Gradle学习记录014 关于依赖的声明

详细学习Gradle构建的依赖声明。该学习记录基于Gradle官方网站资料。本篇参考链接如下&#xff1a;https://docs.gradle.org/current/userguide/declaring_dependencies.html声明一个模块作为依赖通常声明一个模块作为依赖&#xff0c;需要指定这个模块的版本。Gradle提供了一套…

win10版本查看_想知道电脑中安装的win10版本号,用这3招就对了,一键查看

自从微软在Windows 10中更改了发布模型后&#xff0c;很多用户就对找出他们在电脑上安装的Windows 10版本感兴趣。大家可能都已经知道&#xff0c;微软不会再发布Windows的主要版本&#xff0c;而是会不断发布更新&#xff0c;这让人想起许多Linux发行版的滚动发行模型。如果你…

LeetCode 1791. 找出星型图的中心节点(图出入度)

文章目录1. 题目2. 解题1. 题目 有一个无向的 星型 图&#xff0c;由 n 个编号从 1 到 n 的节点组成。 星型图有一个 中心 节点&#xff0c;并且恰有 n - 1 条边将中心节点与其他每个节点连接起来。 给你一个二维整数数组 edges &#xff0c;其中 edges[i] [ui, vi] 表示在节…

ole db 错误 通讯链接失败_西门子PLC1200的S7通讯(同一项目下)--GET接收指令

西门子PLC1200的S7通讯&#xff08;同一项目下&#xff09;--GET接收指令1.0 首先在同一项目下&#xff0c;组态两个PL&#xff0c;如下图&#xff0c;组态了2个1200PLC 1214C的PLC2.0 点击链接里面&#xff0c;在窗口的右上角选择S7连接&#xff0c;这个窗口可以看到本地ID&am…

电磁波

可见光谱只占有宽广的电磁波谱的一小部分。电磁波&#xff0c;又称电磁辐射&#xff0c;是由同相振荡且互相垂直的电场与磁场在空间中以波的形式传递能量和动量&#xff0c;其传播方向垂直于电场与磁场构成的平面。电磁辐射的载体为光子&#xff0c;不需要依靠介质传播&#xf…

LeetCode 1792. 最大平均通过率(优先队列)

文章目录1. 题目2. 解题1. 题目 一所学校里有一些班级&#xff0c;每个班级里有一些学生&#xff0c;现在每个班都会进行一场期末考试。 给你一个二维数组 classes &#xff0c;其中 classes[i] [passi, totali] &#xff0c;表示你提前知道了第 i 个班级总共有 totali 个学生…

分段线性插值c语言程序_【短道速滑】OpenCV中cvResize函数使用双线性插值缩小图像长宽大小一半时速度飞快(比最近邻还快)之异象解析和自我实现。...

点击上方↑↑↑“OpenCV学堂”关注我作者网名&#xff1a;laviewpbt是图像处理&#xff0c;算法实现与加速优化方面的大神&#xff01;其开发的imageshop软件大小只有1MB&#xff0c;却实现了非常丰富与复杂的各种图像处理功能&#xff0c;邮箱地址为&#xff1a;Email: laview…

端口可以随便设置吗_驱动可以随便更新吗?

答案是&#xff0c;真的不可以&#xff0c;我真的吐了&#xff0c;当你用驱动感觉合适&#xff0c;显卡驱动没有卡屏或者黑屏什么的&#xff0c;网卡用着正常的话就不要盲目更新了&#xff0c;我今天就是更新了网卡&#xff0c;结果就断网了&#xff0c;删除了恢复旧版没用&…

IOS学习:常用第三方库(GDataXMLNode:xml解析库)

IOS学习&#xff1a;常用第三方库&#xff08;GDataXMLNode&#xff1a;xml解析库&#xff09; 解析 XML 通常有两种方式&#xff0c;DOM 和 SAX&#xff1a; DOM解析XML时&#xff0c;读入整个XML文档并构建一个驻留内存的树结构&#xff08;节点树&#xff09;&#xff0c;通…

php必须汉字,php怎么只保留汉字

php只保留汉字的实现方法&#xff1a;首先创建一个PHP示例文件&#xff1b;然后通过mb_convert_encoding进行转码&#xff1b;最后通过preg_match_all实现过滤掉非汉字字符只保留中文字符即可。本文操作环境&#xff1a;windows7系统、PHP7.1版&#xff0c;DELL G3电脑PHP实现过…

LeetCode 1793. 好子数组的最大分数(单调栈)

文章目录1. 题目2. 解题1. 题目 给你一个整数数组 nums &#xff08;下标从 0 开始&#xff09;和一个整数 k 。 一个子数组 (i, j) 的 分数 定义为 min(nums[i], nums[i1], ..., nums[j]) * (j - i 1) 。一个 好 子数组的两个端点下标需要满足 i < k < j 。 请你返回…