PHP中的魔术是什么? 这通常意味着
_construct()
或
__get()
。 PHP中的魔术方法是漏洞,可帮助开发人员完成出色的工作。 网络中充满了您可能熟悉的使用说明。 但是,如果我们说您还没有看到真正的魔法该怎么办? 毕竟,您似乎对所有事情都了解的越多,魔术越难以捉摸。

让我们放弃已建立的OOP规则框架,并在PHP魔术学校中使不可能变为可能。 学校的主要魔法老师是
亚历山大·利萨琴科 (
NightTiger )。 他将教授魔术思维,也许您会喜欢魔术方法,访问属性的非标准方法,更改上下文,面向方面的编程和流过滤器。
Alexander Lisachenko是Alpari网站开发和架构部门的负责人。 面向方面的框架
Go!的作者和首席开发人员
。 Aop 。 在PHP国际会议上的演讲者。
在好电影《欺骗的幻觉》中,有一个短语:
“你离得越近,看到的东西就越少。”
关于PHP,可以说是一样的,它是一种魔术,使您可以做一些不寻常的事情。 但首先,它是为了欺骗您而创建的:“ ...旨在欺骗他人的行为,既可以作为欺骗某人的方式,也可以作为一种玩笑或娱乐形式。”
如果我们一起使用PHP并尝试编写一些神奇的东西,很可能我会欺骗您。 我会做一些技巧,您很长一段时间以来都会想知道为什么会这样。 这是因为PHP是一种以其不寻常的东西而闻名的编程语言。
魔术装备
我们需要魔术装备什么? 痛苦的熟悉方法。
__construct(), __destruct(), __clone(),
__call(), __callStatic(),
__get(), __set(), __isset(), __unset(),
__sleep(), __wakeup(),
__toString(), __invoke(), __set_state(),
__debugInfo()
我将分别说明最后一种方法-有了它,您就可以解决不寻常的事情。 但这还不是全部。
declare(ticks=1)
。
debug_backtrace()
。 这是我们的同伴,以了解我们在当前代码中的位置,了解谁给我们打电话,为什么给我们打电话,以及带有什么参数。 决定不遵循所有逻辑很有用。
unset(), isset()
。 似乎没什么特别的,但是这些设计隐藏了很多技巧,我们将进一步考虑这些技巧。
by-reference passing
。 当我们通过引用传递某些对象或变量时,我们应该期望不可避免地会发生一些魔术。
bound closures
。 关于闭包以及它们可以绑定的事实,您可以构建很多技巧。
- Reflection API有助于将反射提高到一个新的水平。
- StreamWrapper API。
设备已经准备就绪-我会提醒魔术的第一法则。
永远是房间里最聪明的人。
绝招#1。 不可能的比较
让我们从第一个技巧开始,我将其称为“不可能比较”。
仔细看一下代码,看看这是否可能在PHP中发生。

有一个变量,我们声明它的值,然后突然它不等于自己。
非数字
有一个不可思议的人工产物,例如NaN-非数字。

它的惊人功能是它不等于自身。 这是我们的第一个技巧:使用NaN迷惑一个朋友。 但是,NaN并不是此任务的唯一解决方案。
我们使用常量

诀窍是我们可以将
namespace
false
声明为
true
并进行比较。 不幸的开发人员会很长一段时间想知道为什么存在
false
。
处理程序
下一个招数是比前两个选项更强大的火炮。 让我们看看它是如何工作的。

技巧是基于
tick_function
并且正如我提到的,
declare(ticks=1)
。
开箱即用怎么办? 首先,我们声明一些函数,并通过引用采用
isMagic
参数,然后尝试将该值更改为
true
。 在我们声明了
declare(ticks=1)
,PHP解释器在每个基本操作之后调用
register_tick_function
回调。 在此回调中,我们可以将以前为
false
的值更改为
true
。 魔术!
绝招#2。 魔术表情
以声明两个变量的示例为例。 其中一个是
false
,另一个是
true
。 我们
isBlack
和
isWhite
和var_dump结果。 您认为结果如何?
优先运营商 。 正确答案是
false
,因为在PHP中存在诸如“运算符优先级”之类的东西。

令人惊讶的是,逻辑运算符
or
优先级低于赋值操作。 因此,简单地分配
false
。 在
isWhite
如果第一部分失败
isWhite
可以执行任何其他表达式。
魔术表情
看下面的代码。 有一些包含构造函数的类,还有一些工厂,其代码将更进一步。

注意最后一行。
$value = new $factory->build(Foo::class);
有几种选择可能发生。
$factory
将被用作new
的类名;
- 会有解析错误;
- 将使用调用
$factory->build()
,此函数将返回其值,结果将为new
;
$factory->build
属性的值将用于构造类。
让我们看看最后一个想法。 在
$factory
类中,我们声明了一系列魔术函数。 他们将编写我们的工作:调用属性,调用方法,甚至尝试在对象上调用
invoke
。
class Factory { public function _get($name) {echo "Getting property {$name}"; return Foo::class;} public function _call($name, $arg) {echo "Calling method {$name}"; return Foo::class;} public function _invoke($name) {echo "Invoking {$name}"; return Foo::class;} }
正确答案:我们不是在调用方法,而是在调用属性。 在
$factory->build
有一个用于构造函数的参数,该参数将传递给此类。
顺便说一下,在这个框架中,我实现了一个称为“拦截新对象的创建”的功能-您可以“锁定”该设计。
解析器漏洞
以下示例适用于PHP解析器本身。 您是否尝试过在花括号内调用函数或分配变量?

我在Twitter上获得了这个技巧,它的工作非常不规范。
$result = ${'_' . !$_=getCallback()}(); $_=getCallback();
首先,我们将表达式的值分配给名为
_
(下划线)的变量。 我们已经有一个变量,尝试在逻辑上将其值求反,然后我们得到
false
该行强制转换为
true
。 然后,将它们全部粘贴到变量名中,然后将其放在大括号内。
什么是魔术? 这是一种娱乐活动,使我们感到鼓舞,不寻常,说:“什么? 所以有可能吗?!
绝招#3。 违反规则
我对PHP的满意之处在于,您可以打破每个人创建的规则,以使其变得超级安全。 有一种称为“密封类”的设计,该设计具有私有构造函数。 作为魔术师的学生,您的任务是创建此类的一个实例。

考虑如何执行此操作的三个选项。
解决方法
第一种方法是最明显的。 每个开发人员都应该熟悉-这是该语言为我们提供的标准API。

newInstanceWithoutConstructor
构造允许您绕过构造函数是私有的语言限制,并绕过我们所有的私有构造函数声明创建类的实例。
该选项有效,简单,不需要任何解释。
短路
第二种选择需要更多的关注和技巧。 创建一个匿名函数,然后将其绑定到该类的鱼鹰。

在这里,我们位于类内部,可以安全地调用私有方法。 我们通过在类的上下文中调用
new static
来使用它。

反序列化
我认为,第三个选项是最高级的。

如果您以特定格式编写特定行,请在其中替换某些值-我们的班级将会出来。

反序列化之后,我们得到
instance
。
教义/实例化程序包
Magic通常会成为文档化的框架或库-例如,在
学说/实例化器中,所有这一切都得以实现。 我们可以用任何代码创建任何对象。
composer show doctrine/instantiator --all name : doctrine/instantiator descrip. : A small, lightweight utility to instantiate objects in PHP without invoking their constructors keywords : constructor, instantiate type : library license : MIT License (MIT)
绝招#4。 拦截财产访问
乌云密布:类是秘密的,属性和构造函数是私有的,还有回调。
class Secret { private $secret = 42; private function _construct() { echo 'Secret is: ', $this->secret; } private function onPropAccess(string $name) { echo "Accessing property {$name}"; return 100500; } }
作为向导,我们的任务是以某种方式调用回调。
添加魔术...吸气剂
添加一些魔法使其起作用。

这种魔法是一种神奇的
getter
。 它调用一个函数,到目前为止没有发生任何可怕的事情。 但是,我们将使用前面的技巧,并绕过私有构造创建此对象的实例。

现在我们需要以某种方式调用回调。
电路内部“未设置”
为此,创建一个闭合。 在类作用域内的闭包内部,我们使用
unset()
函数删除此变量。

unset
允许您临时排除变量,这将允许调用我们的magic
get
方法。
我们称私有构造函数
由于我们有一个显示
echo
的私有构造函数,您只需获取此构造函数,即可通过调用它使其可访问。

因此,我们的秘密班级崩溃了。

我们收到一条消息,我们:
leedavis / altr-ego软件包
已经记录了很多魔术。
altr-ego软件包只是伪装成您的组件。
composer show leedavis81/altr-ego --all name : leedavis81/altr-ego descrip. : Access an objects protected / private properties and methods keywords : php, break scope versions : dev-master, v1.0.2, v1.0.1, v1.0.0 type : library license : MIT License (MIT)
您可以创建一个对象,然后再添加一个对象。 这将允许更改设施。 他会乖乖地改变并实现您的所有愿望。
绝招#5。 PHP中的不可变对象
PHP中有不可变对象吗? 是的,而且非常非常长的时间。
namespace Magic { $object = (object) [ "\0Secret\0property" => 'test' ]; var_dump($object); }
以一种有趣的方式获得它们。 有趣的是,我们创建了一个具有
特殊键的数组。 它从结构
\0
-这是一个零字节字符,在
Secret
之后,我们也看到
\0
。
PHP中使用该构造在类中声明私有属性。 如果尝试将对象强制转换为数组,则会看到相同的键。 除了
stdClass
我们什么都没有。 它包含
Secret
类的私有属性,该属性等于
test
。
object(stdClass) [1] private 'property' (Secret) => string 'test' (length=4)
唯一的问题是-那么您无法从那里获得此属性。 它已创建,但不可用。
我认为这很不方便-我们有不可变的对象,但是您不能使用它。 因此,我认为是时候提出我的决定了。 我使用了PHP中提供的所有知识和魔术,根据所有魔术技巧创建了一个结构。
让我们从一个简单的例子开始-创建一个DTO并尝试截取其中的所有属性(请参阅前面的技巧)。
我们将从那里捕获
的值保存在安全的地方 。 它们将无法通过任何方法访问:
reflection
,闭包或其他魔术方法。 但是存在不确定性-PHP中是否有这样的地方可以保证保存一些变量,以便没有一个狡猾的年轻程序员能到达那里?
我们提供了一种魔术方法,以便您可以读取此值。 为此,我们有魔术
getters
,魔术
isset
方法,这些方法使我们能够提供API。
让我们回到一个可靠的地方,尝试搜索。
Global variables
取消-任何人都可以更改它们。
Public properties
也不适合。
Protected properties
的,因为子类将通过。
Private properties
。 没有信任,因为可以通过关闭或reflection
来更改它。
- 可以尝试使用
Private static properties
,但是reflection
也会中断。
似乎没有地方可以隐藏变量的值。 但是有一件神奇的事情-
Static variables in functions
-这些是函数内部的变量。
安全存储价值
我在特殊的Stack Overflow频道上询问了
Nikita Popov和
Jay Watkins 。

这是一个在其中声明静态变量的函数。 是否有可能摆脱它,改变它? 答案是否定的。
我们在另一个受保护的变量世界中发现了一个小漏洞,我们想使用它。
通过链接传递值
我们将非标准地将其用作对象的属性。 但是您不能传递属性,因此我们通过引用使用经典的值传递。

事实证明,有一个类中有一个神奇的
callStatic
方法,并在其中声明了
Static
变量。 在对某个函数的任何调用中,我们都通过引用所有嵌套方法来传递Immutable对象中变量的值。 因此,我们提供了上下文。
保存状态
让我们看看状态如何保存。

很简单 对于传输的对象,我们使用
spl_object_id
函数,该函数为每个实例返回一个单独的标识符。 我们已经从对象获得的状态正在尝试保存在那里。 没什么特别的
应用对象的状态
同样,这里有一种通过引用和未
unset
属性传递值的构造。 我们取消了所有当前属性,之前已将它们保存在
State
变量中,然后为对象设置此上下文。 该对象不再包含任何属性,而仅包含其标识符,该标识符使用
spl_object_id
声明,并在该对象仍然存在时附加到该对象。

获取状态
然后,一切都很简单。

我们在魔术获取器上获取此上下文,并从中调用我们的属性。 现在,连接此
Trait
后,没有人可以更改值。

所有魔术方法都将被覆盖并实现对象的不变性。

利萨琴科/不可变对象
正如预期的那样,所有内容都会立即在库中正式化并可以使用。
composer show /immutable-object --all name : /immutable-object descrip. : Immutable object library keywords : versions : * dev-master type : library license : MIT License (MIT)
该库看起来很简单。 我们将其连接并创建我们的课程。 它具有不同的属性:私有,受保护的和公共的。 我们连接
ImmutableTrait
。

之后,您可以一次初始化对象-查看其值。 它确实保存在那里,甚至看起来像一个真正的DTO。
object (MagicObject) [3] public 'value' => int 200
但是,例如,如果我们尝试更改它,例如...

...然后立即得到
fatal exception
。 我们无法更改属性,因为它是不可变的。 怎么会这样

如果您参与了一个激动人心的挑战并试图降低它的竞争力,您将获得以下收益。

这是我的礼物。 一旦您尝试在此类的PHPStorm中失败,它将立即停止命令的执行。 我不希望您深入研究此代码-这太危险了。 他将警告没有事可做。
绝招#6。 线程处理
考虑设计。
include 'php://filter/read=string.toupper/resource=magic.php';
有一个神奇的东西:一个PHP过滤器,
read
,最后连接了某种magic.php
文件 。 这个文件看起来很简单。
<?php echo 'Hello, world!'
请注意,情况有所不同。 但是,如果我们通过设计“填充”文件,则会得到以下信息:
HELLO, WORLD!
那一刻发生了什么事? 使用
include
PHP过滤器的构造,您可以连接任何过滤器(包括您自己的过滤器)来分析源代码。 您正在管理此源代码中的所有内容。 您可以从类和方法中删除
final
,将属性设为公共-您可以通过此方法进行处理。
我的方面框架的一部分是基于此的。 连接您的类后,它将对其进行分析并将其转换为将要执行的内容。
PHP开箱即用,已经有一大堆现成的过滤器。
var_dump(stream_get_filters()); array (size=10) 0 => string 'zlib.*' (length=6) 1 => string 'bzip2.*' (length=7) 2 => string 'convert.iconv.*' (length=15) 3 => string ' string.rotl3' (length=12) 4 => string 'string.toupper' (length=14) 5 => string 'string.tolower' (length=14) 6 => string 'string.strip_tags' (length=17) 7 => string 'convert.*' (length=9) 8 => string 'consumed' (length=8) 9 => string 'dechunk' (length=7)
它们允许您“压缩”内容,将其转换为大写或小写。
我想展示的主要技巧已经结束。 现在,让我们继续我能做的所有事情的精髓-面向方面的编程。
绝招#7。 面向方面的编程
查看此代码,并从您的角度考虑它是好是坏。

该代码似乎已经足够了。 它检查访问权限,执行日志记录,创建用户,保留,尝试捕获
exception
。
如果您查看所有这些面条,则在我们的每种方法中都将其重复,这里唯一有价值的是将其标记为绿色。

其他所有内容:“次要关注点”或“跨领域关注点”是跨领域功能。
传统的OOP使得无法使用复制粘贴复制所有这些结构,将它们删除并将它们带到某个地方。 这很糟糕,因为您必须重复此操作。 我希望代码总是看起来整洁。

因此它不包含日志记录(以某种方式应用它)并且不包含
security
。

以上就是所有内容,包括日志记录...

...和安全检查,...

...自己表演。
这是可能的。
术语表“方面”
有一个东西叫做Aspect。 这是一个检查权限的简单示例。 多亏了他,您可以看到一些注释,PhpStorm的插件也突出显示了该注释。 “ Aspect”声明SQL表达式,代码中要对此条件应用的点。 例如,在下面,我们要对
UserService
类中的所有公共方法应用闭包。

我们使用
$invocation
方法获得闭包。

这是
reflection
方法顶部的一种包装,其中包含更多参数。
此外,在每种方法的此回调中,您可以检查必要的访问权限,这称为“建议”。

我们用这种语言来表达:“亲爱的PHP,请在每次从
UserService
类调用公共方法之前应用此方法
UserService
” 只需一行,但很有用。
方面与事件侦听器
为了更加清楚,我将Aspect与Event Listener进行了比较。 许多人在Symfony工作,并且知道什么是Event Dispatcher。

它们的相似之处在于,我们传递了某种依赖关系,例如
AutorizationChecker
,并声明了这种情况下的应用位置。 在Listener的情况下,这是一个名为
UserCreate
;在
Aspect
的情况下
Aspect
我们从
UserService
预订了对公共方法的所有调用。 callback : - .
, .
, ,
Aspects
.

. ,
PHP- .

, . , , OPcache.
.
Composer
. Go! AOP,
Composer
, .

Aspects
,
Aspects
. .
.
PHP-Parser . , ,
. ,
,
PHP-Parser . .

. ,
goaop/parser-reflection .

AST- , . , , , .
.

, ,
Aspect
.

, .

, , . — , - , .

, . , .
Aspect MOCK. «», , . .
—
joinPoint
. -
joinPoint
: , , .
接下来是什么?
.
OPcache preloading for AOP Core . AOP- . 10 . Bootstrapping , PHP.
FFI integration to modify binary opcodes . , , . PHP-opcodes,
.bin
. FFI .
Modifying PHP engine internal callbacks PHP- userland. PHP . FFI PHP userland, , , . .
, .
#8. goaop/framework
,
GitHub .
composer show goaop/framework --all name : goaop/framework descrip. : Framework for aspect-oriented programming in PHP. keywords : php, aop, library, aspect versions : dev-master, 3.0.x-dev, 2.x-dev, 2.3.1, … type : library license : MIT License
, PhpStorm.

, Pointcuts. , , . — , , .
Trick #9.
. , .
: , - .
fastcgi_finish_request
. , , , - callback — .
?
,
Deffered
, , .

Aspect
, , ,
Deffered
, .

Aspect
: , , , callback, , callback. .
React ,
promise
. , - , - , .
, .

shutdown_function
Aspect
. , callback, , , , callback
onPhpTerminate
.
fastcgi_finish_request
: «, , , , ». .
,
sendPushNotification
.

, - — 2 .

, , , 2 .
,
Deferred
.

, . - , , .
仅此而已。 , - . , .
PHP Russia 2020 . — . telegram- , PHP Russia 2020.