badoo /
“软模拟”背后的想法——与在PHP解释器级别(runkit和uopz)工作的“硬核”模拟相对——是在现场重写类代码,以便它可以被插入任何地方。它通过在文件包含期间即时重写代码来工作,而不是使用r这样的扩展。
Requires
- php: >=7.0
- ext-json: *
- ext-mbstring: *
- nikic/php-parser: ^4.15.2
Requires (Dev)
- phpunit/phpunit: >=8.5.21 <8.5.22
- vaimo/composer-patches: 4.22.4
- dev-master
- 3.6.0
- 3.5.4
- 3.5.3
- 3.5.2
- 3.5.1
- 3.5.0
- 3.4.1
- 3.4.0
- 3.3.2
- 3.3.1
- 3.3.0
- 3.2.0
- 3.1.7
- 3.1.6
- 3.1.5
- 3.1.4
- 3.1.3
- 3.1.2
- 3.1.1
- 3.1.0
- 3.0.4
- 3.0.3
- 3.0.2
- 3.0.1
- 3.0.0
- 2.0.6
- 2.0.5
- 2.0.4
- 2.0.3
- 2.0.2
- 2.0.1
- 2.0.0
- 1.3.5
- 1.3.4
- 1.3.3
- 1.3.2
- 1.3.1
- 1.3.0
- 1.2.0
- 1.1.2
- 1.1.1
- 1.1.0
- 1.0
- dev-PLATFORM-12381_php74_php74_compatibility
This package is auto-updated.
Last update: 2024-09-07 17:07:57 UTC
README
“软模拟”背后的想法——与在PHP解释器级别(runkit和uopz)工作的“硬核”模拟相对——是在现场重写类代码,以便它可以被插入任何地方。它通过在文件包含期间即时重写代码来工作,而不是使用r这样的扩展。
安装
您可以通过Composer安装SoftMocks
composer require --dev badoo/soft-mocks
用法
SoftMocks与众不同的地方(也是它们使用的限制)是它们需要在应用启动的最早阶段启动。必须这样做,因为您不能重新定义已加载到PHP内存中的类和函数。有关示例引导预设,请参阅src/bootstrap.php。对于PHPUnit,您应该使用composer.json中的补丁,因为您应该通过SoftMocks要求composer自动加载。
SoftMocks不会重写以下系统部分
- 自己的代码;
- PHPUnit代码(有关详细信息,请参阅
\Badoo\SoftMocks::addIgnorePath()
); - PHP-Parser代码(有关详细信息,请参阅
\Badoo\SoftMocks::addIgnorePath()
); - 已重写的代码;
- 在SoftMocks初始化之前加载的代码。
为了将外部依赖项(例如,vendor/autoload.php)添加到在SoftMocks初始化之前加载的文件中,您需要使用包装器
require_once (\Badoo\SoftMocks::rewrite('vendor/autoload.php'));
require_once (\Badoo\SoftMocks::rewrite('path/to/external/lib.php'));
通过SoftMocks::rewrite()
添加文件后,所有嵌套的包含调用都已经由系统本身“包装”。
您可以通过执行以下命令查看更详细的示例
$ php example/run_me.php
Result before applying SoftMocks = array (
'TEST_CONSTANT_WITH_VALUE_42' => 42,
'someFunc(2)' => 84,
'Example::doSmthStatic()' => 42,
'Example->doSmthDynamic()' => 84,
'Example::STATIC_DO_SMTH_RESULT' => 42,
)
Result after applying SoftMocks = array (
'TEST_CONSTANT_WITH_VALUE_42' => 43,
'someFunc(2)' => 57,
'Example::doSmthStatic()' => 'Example::doSmthStatic() redefined',
'Example->doSmthDynamic()' => 'Example->doSmthDynamic() redefined',
'Example::STATIC_DO_SMTH_RESULT' => 'Example::STATIC_DO_SMTH_RESULT value changed',
)
Result after reverting SoftMocks = array (
'TEST_CONSTANT_WITH_VALUE_42' => 42,
'someFunc(2)' => 84,
'Example::doSmthStatic()' => 42,
'Example->doSmthDynamic()' => 84,
'Example::STATIC_DO_SMTH_RESULT' => 42,
)
API(简要描述)
初始化SoftMocks(设置PHPUnit注入、定义内部模拟、获取内部函数列表等)
\Badoo\SoftMocks::init();
默认情况下,缓存文件存储在/tmp/mocks。如果您想选择不同的路径,您可以按以下方式重新定义它
\Badoo\SoftMocks::setMocksCachePath($cache_path);
此方法应在重写第一个文件之前调用。您还可以使用环境变量SOFT_MOCKS_CACHE_PATH
重新定义缓存路径。
重新定义常量
您可以给$constantName分配新值或创建一个尚未声明的常量。由于它不是使用define()调用创建的,因此操作可以被取消。
支持“常规常量”和类常量,如“className::CONST_NAME”。
\Badoo\SoftMocks::redefineConstant($constantName, $value)
重新定义类常量时可能存在以下情况
- 您可以重新定义基类常量
class A {const NAME = 'A';} class B {} echo A::NAME . "\n"; // A echo B::NAME . "\n"; // A \Badoo\SoftMocks::redefineConstant(A::class . '::NAME', 'B'); echo A::NAME . "\n"; // B echo B::NAME . "\n"; // B
- 您可以添加中间类常量
class A {const NAME = 'A';} class B {} class C {} echo A::NAME . "\n"; // A echo B::NAME . "\n"; // A echo C::NAME . "\n"; // A \Badoo\SoftMocks::redefineConstant(B::class . '::NAME', 'B'); echo A::NAME . "\n"; // A echo B::NAME . "\n"; // B echo C::NAME . "\n"; // B
- 您可以添加常量到基类
class A {const NAME = 'A';} class B {} echo A::NAME . "\n"; // Undefined class constant 'NAME' echo B::NAME . "\n"; // Undefined class constant 'NAME' \Badoo\SoftMocks::redefineConstant(A::class . '::NAME', 'A'); echo A::NAME . "\n"; // A echo B::NAME . "\n"; // A
- 您可以删除中间类常量
class A {const NAME = 'A';} class B {const NAME = 'B';} class C {} echo A::NAME . "\n"; // A echo B::NAME . "\n"; // B echo C::NAME . "\n"; // B \Badoo\SoftMocks::removeConstant(B::class . '::NAME'); echo A::NAME . "\n"; // A echo B::NAME . "\n"; // A echo C::NAME . "\n"; // A
- 其他更简单的情况(只需添加或重新定义常量等)。
重新定义函数
SoftMocks允许您重新定义用户定义的和内建的函数,除了那些依赖于当前上下文(如果您想查看完整的列表,请参阅\Badoo\SoftMocksTraverser::$ignore_functions属性)的函数,或者那些具有内置模拟的函数(debug_backtrace、call_user_func*和少数其他函数,但您可以通过调用\Badoo\SoftMocks::setRewriteInternal(true)
来启用内置模拟的重定义)。
定义
\Badoo\SoftMocks::redefineFunction($func, $functionArgs, $fakeCode)
使用示例(重新定义strlen函数并调用原始函数以获取修剪后的字符串)
\Badoo\SoftMocks::redefineFunction(
'strlen',
'$a',
'return \\Badoo\\SoftMocks::callOriginal("strlen", [trim($a)]));'
);
var_dump(strlen(" a ")); // int(1)
重新定义方法
目前仅支持用户定义的方法重定义。此功能不支持内置类。
定义
\Badoo\SoftMocks::redefineMethod($class, $method, $functionArgs, $fakeCode)
参数与redefineFunction相同,但新增了参数$class。
作为参数,$class可以接受一个类名或特质名。
重定义生成器函数
此方法允许您将生成器函数调用替换为另一个\Generator。生成器与常规函数的不同之处在于,您不能使用"return"返回值;您必须使用"yield"。
\Badoo\SoftMocks::redefineGenerator($class, $method, \Generator $replacement)
恢复值
以下函数可以撤销使用上述重定义方法之一制作的模拟。
\Badoo\SoftMocks::restoreAll()
// You can also undo only chosen mocks:
\Badoo\SoftMocks::restoreConstant($constantName)
\Badoo\SoftMocks::restoreAllConstants()
\Badoo\SoftMocks::restoreFunction($func)
\Badoo\SoftMocks::restoreMethod($class, $method)
\Badoo\SoftMocks::restoreGenerator($class, $method)
\Badoo\SoftMocks::restoreNew()
\Badoo\SoftMocks::restoreAllNew()
\Badoo\SoftMocks::restoreExit()
与PHPUnit一起使用
如果您想使用SoftMocks与PHPUnit 8.x一起使用,那么有以下特殊注意事项
- 如果通过composer安装了phpunit,那么您应该应用以下补丁到
phpunit
patches/phpunit7.x/phpunit_phpunit.patch,以便由SoftMocks重写由composer加载的类; - 如果手动安装了phpunit,那么您应该要求src/bootstrap.php,以便由SoftMocks重写由composer加载的类;
- 为了使跟踪可读,您应该应用针对
phpunit
的补丁patches/phpunit8.x/phpunit_add_ability_to_set_custom_filename_rewrite_callbacks.patch; - 为了正确显示覆盖率,您应该应用针对
php-code-coverage
的补丁patches/phpunit8.x/php-code-coverage_add_ability_to_set_custom_filename_rewrite_callbacks.patch。
对于phpunit7.x
,请使用phpunit7.x
目录代替phpunit8.x
。对于phpunit6.x
,请使用phpunit6.x
目录代替phpunit8.x
。对于phpunit5.x
,请使用phpunit5.x
目录代替phpunit8.x
。对于phpunit4.x
,请使用phpunit4.x
目录代替phpunit8.x
。
如果您希望自动应用补丁,您应该在composer.json中写入以下内容
{ "require-dev": { "vaimo/composer-patches": "3.23.1", "phpunit/phpunit": "^8.4.3" // or "^7.5.17" or "^6.5.5" or "^5.7.20" or "^4.8.35" } }
要强制重新应用补丁,请使用以下命令
composer patch --redo
有关补丁的更多信息,请参阅vaimo/composer-patches文档。
与xdebug一起使用
有两种方法可以将SoftMocks与xdebug一起使用 - 调试重写的文件和使用xdebug-proxy调试原始文件。
调试重写的文件
如果您在本地使用SoftMocks,则可以通过调用xdebug_break()
来调试它。您还可以将断点添加到重写的文件中,但您应该知道重写文件的路径。要获取重写文件的路径,您可以调用\Badoo\SoftMocks::rewrite($file)
,但请注意 - 如果您更改了文件,则会创建新文件,并且路径将不同。
如果您在服务器上使用SoftMocks,则可以使用sshfs之类的工具挂载/tmp/mocks。
使用xdebug-proxy调试原始文件
如您所见,调试重写的文件不方便。您还可以使用xdebug-proxy调试原始文件。
composer.phar require mougrim/php-xdebug-proxy --dev
cp -r vendor/mougrim/php-xdebug-proxy/config xdebug-proxy-config
之后,将xdebug-proxy-config/factory.php
更改如下
<?php use Mougrim\XdebugProxy\Factory\SoftMocksFactory; return new SoftMocksFactory();
如果您在本地使用SoftMocks,则只需运行代理
vendor/bin/xdebug-proxy --configs=xdebug-proxy-config
之后,在127.0.0.1:9001
上注册您的IDE,并运行使用SoftMocks的脚本(例如phpunit)
php -d'zend_extension=xdebug.so' -d'xdebug.remote_autostart=On' -d'xdebug.idekey=idekey' -d'xdebug.remote_connect_back=On' -d'xdebug.remote_enable=On' -d'xdebug.remote_host=127.0.0.1' -d'xdebug.remote_port=9002' /local/php72/bin/phpunit
如果您在服务器上使用SoftMocks,那么您也应该在服务器上运行xdebug-proxy,并修改xdebug-proxy-config/config.php
中的ideRegistrationServer
的ip,从127.0.0.1
更改为0.0.0.0
。
总的来说,xdebug-proxy工作如下
- 第一步是在xdebug-proxy中注册您的IDE(例如:主菜单 -> 工具 -> DBGp代理 -> 在PHPStorm中注册IDE)。使用xdebug-proxy监听的IDE注册的IP:PORT(例如:127.0.0.1:9001)或您的服务器IP:PORT。您可以在xdebug-proxy配置中配置该端口。在这一步中,IDE将发送其监听的IP:PORT到代理。
- 当您使用上述命令行选项运行 php-script 时,xdebug 会连接到
127.0.0.1:9002
。这个 IP 和端口号是 xdebug-proxy 监听 xdebug 连接的地方。Xdebug-proxy 会将 IDEKEY 与已注册的 IDE 匹配。如果匹配到已注册的 IDE,则 xdebug-proxy 将在注册步骤使用提供的 IDE 客户端 IP:PORT 连接到该特定 IDE。
有关更多信息,请阅读 xdebug 文档 和 xdebug-proxy 文档。
SoftMocks 开发
如果您需要修改 SoftMocks,您需要克隆仓库并安装依赖项
composer install
然后您可以修改 SoftMocks 并运行测试,以确保一切正常
./vendor/bin/phpunit
常见问题解答
问:如何防止特定的函数/类/常量被重新定义?
答:使用 \Badoo\SoftMocks::ignore(Class|Function|Constant) 方法。
问:我无法覆盖某些函数调用:call_user_func(_array)?,defined 等。
答:有一些函数有自己的内置模拟,默认情况下无法拦截。以下是一个不完整的列表
- call_user_func_array
- call_user_func
- is_callable
- function_exists
- constant
- defined
- debug_backtrace
因此,您可以在 require bootstrap 后调用 \Badoo\SoftMocks::setRewriteInternal(true)
以启用对它们的拦截,但请注意。例如,如果 strlen 和 call_user_func(_array) 被重新定义,那么您可能会得到不同的 strlen 结果
\Badoo\SoftMocks::redefineFunction('call_user_func_array', '', 'return 20;'); \Badoo\SoftMocks::redefineFunction('strlen', '', 'return 5;'); ... strlen('test'); // will return 5 call_user_func_array('strlen', ['test']); // will return 20 call_user_func('strlen', 'test'); // will return 5
问:SoftMocks 是否与 PHP7 兼容?
答:是的。SoftMocks 的整个想法是它将继续与所有后续的 PHP 版本一起工作,而无需像 runkit 和 uopz 那样进行完整的系统重写。
问:SoftMocks 是否与 HHVM 兼容?
答:似乎在编写此问答时,使用 HHVM 时 SoftMocks 确实可以工作(HipHop VM 3.12.1(rel))。我们内部没有使用 HHVM,因此可能会有一些未涵盖的边缘情况。我们欢迎任何有关 HHVM 支持的问题/拉取请求。
问:为什么我会遇到解析错误或类似“PhpParser::pSmth 未定义”的致命错误?
答:SoftMocks 使用自定义的 PHP Parser 打印机,似乎与所有 PHP Parser 版本不兼容。请在我们找到解决方法之前使用我们提供的版本。