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 更先进相关推荐
- 预处理_气体在线分析仪预处理系统的工作内容
" 赛弗美/因为安全,所以放心. Time 在生活和工作中,享受安全和健康的保障,是生命的基本需求. 在线分析仪表在化工行业的应用已经十分的广泛,为化工生产过程控制.提高产品质量.指导操作提 ...
- 预处理_不锈钢锻件预处理的必要性
预处理是不锈钢锻件制件表面在进入表面处理(包括酸洗.化学抛光与电化学抛光.电镀.钝化.着黑色.着彩色.化学加工等)前的重要处理步骤.在不锈钢锻件在成型过程中,表面都有可能粘上油污.存在毛刺.形成粗糙表 ...
- 中文 lda数据预处理_英文文本挖掘预处理流程总结
点击上方"AI遇见机器学习",选择"星标"公众号 重磅干货,第一时间送达 整理:AI算法之心 作者:刘建平Pinard 博客地址:https://www.c ...
- python中文文本预处理_中文文本预处理及表示
文本分类 一.建立语料库 文本数据的获取方法一般有两种: 使用别人做好的语料库 爬虫去获取自己的预料数据 二.文本预处理 1.除去数据中非文本部分 一般可以使用正则表达式去进行删除 2.处理中文编码问 ...
- 预处理php,如何用预处理让 PHP 更先进
先来点趣事.不久以前, 我尝试在 PHP 通过宏 来添加 Python 的 range 语法.然后, SaraMG 大虾 提到一个 RFC ,并且 LordKabelo 建议为 PHP 添加 C# ...
- R 多变量数据预处理_数据科学 | 第3讲 数据清洗与预处理
点击上方蓝字,记得关注我们! 在实际数据挖掘过程中,我们拿到的初始数据,往往存在缺失值.重复值.异常值或者错误值,通常这类数据被称为"脏数据",需要对其进行清洗.另外有时数据的原始 ...
- 泰坦尼克号python数据预处理_sklearn preprocessing 数据预处理(OneHotEncoder)
1. one hot encoder one hot encoder 不仅对 label 可以进行编码,还可对 categorical feature 进行编码: >>> from ...
- R 多变量数据预处理_超长文详解:C语言预处理命令
一 前言 预处理(或称预编译)是指在进行编译的第一遍扫描(词法扫描和语法分析)之前所作的工作.预处理指令指示在程序正式编译前就由编译器进行的操作,可放在程序中任何位置. 预处理是C语言的一个重要功能 ...
- 『TensorFlow』第九弹_图像预处理_不爱红妆爱武装
部分代码单独测试: 这里实践了图像大小调整的代码,值得注意的是格式问题: 输入输出图像时一定要使用uint8编码, 但是数据处理过程中TF会自动把编码方式调整为float32,所以输入时没问题,输出时 ...
最新文章
- 剑指offer_第6题_旋转数组的最小数字
- 我们每天都在做无用功?
- No serializer found for class org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer解决方法
- Cocos Creator学习目录
- Axure--Web原型开发工具
- k1658停运_最新通知!福州这些列车停运!
- word 宏命令 表头与图名的设置
- 网络正常,QQ无法登录,一直显示登录中
- 【studio】整理了下studio中make Project、clean Project、Rebuild Project的区别
- win7计算机的用户名和密码,win7文件共享访问需要输入用户名和密码如何解决
- 改变磁盘格式gpt_改变游戏规则或结束游戏? 准备好参加GPT-3
- MFC通讯系统项目——操作步骤
- git设置用户名密码
- Grafana接入Elasticsearch数据,绘制dashboard
- 深入理解文字高度和行高的设置
- 【kafka专栏】使用shell脚本快速搭建kafka单机版(含视频)
- javascript制作钟表
- 写入excel文件的ExcelWriter、openpyxl、xlsxwriter
- 移动端游戏开发:差异、挑战,以及全新的解决方案
- 域名中不应出现下划线