V8Js 是 Google V8 Javascript 引擎的 PHP 扩展。
该扩展允许您在 PHP 的安全沙箱中执行 Javascript 代码。可以使用时间限制和/或内存限制来限制执行的代码。这提供了放心执行不受信任的代码的可能性。
V8 Javascript 引擎库 (libv8) master https://github.com/v8/v8-git-mirror (trunk)
V8 是 Google 的开源 Javascript 引擎。 V8 用 C++ 编写,用于 Google 的开源浏览器 Google Chrome。 V8 按照 ECMA-262 第 5 版的规定实现 ECMAScript。
此扩展需要 V8 9.0 或更高版本。
V8 版本的发布速度相当快,V8 团队通常为 Chrome 浏览器(稳定通道)和更新版本(仅)附带的版本线提供安全支持。有关版本概述,请参阅 https://chromiumdash.appspot.com/branches。
PHP 8.0.0+
V8 引擎的这种嵌入式实现使用线程锁定,因此它可以在启用 ZTS 的情况下工作。
Windows 目前尚未得到官方支持。主要是因为我自己没有时间维护对它的支持,也没有真正的 Windows 机器来尝试。如果有人能够修复 Windows 上的问题、提供预构建的 V8 二进制文件等,那就太好了。
有一个名为php7
的分支,其目标是 PHP 7.0.0+
对于最初的一些步骤,您可能想尝试使用 V8Js docker 映像,而不是手动编译。它预装了 v8、v8js 和 php-cli,因此您可以在“交互模式”下尝试使用 PHP。但是没有 Apache 等在运行。
在 Microsoft Windows 上构建有点复杂,请参阅 README.Win32.md 文件以快速浏览。在 GNU/Linux 和 MacOS X 上构建非常简单,请参阅 README.Linux.md 和 README.MacOS.md 文件以了解平台特定说明的演练。
<?php
class V8Js
{
/* Constants */
const V8_VERSION = '' ;
const FLAG_NONE = 1 ;
const FLAG_FORCE_ARRAY = 2 ;
const FLAG_PROPAGATE_PHP_EXCEPTIONS = 4 ;
/* Methods */
/**
* Initializes and starts V8 engine and returns new V8Js object with it's own V8 context.
* @param string $object_name
* @param array $variables
* @param string $snapshot_blob
*/
public function __construct ( $ object_name = " PHP " , array $ variables = [], $ snapshot_blob = NULL )
{}
/**
* Provide a function or method to be used to load required modules. This can be any valid PHP callable.
* The loader function will receive the normalised module path and should return Javascript code to be executed.
* @param callable $loader
*/
public function setModuleLoader ( callable $ loader )
{}
/**
* Provide a function or method to be used to normalise module paths. This can be any valid PHP callable.
* This can be used in combination with setModuleLoader to influence normalisation of the module path (which
* is normally done by V8Js itself but can be overriden this way).
* The normaliser function will receive the base path of the current module (if any; otherwise an empty string)
* and the literate string provided to the require method and should return an array of two strings (the new
* module base path as well as the normalised name). Both are joined by a '/' and then passed on to the
* module loader (unless the module was cached before).
* @param callable $normaliser
*/
public function setModuleNormaliser ( callable $ normaliser )
{}
/**
* Provate a function or method to be used to convert/proxy PHP exceptions to JS.
* This can be any valid PHP callable.
* The converter function will receive the PHP Exception instance that has not been caught and
* is due to be forwarded to JS. Pass NULL as $filter to uninstall an existing filter.
*/
public function setExceptionFilter ( callable $ filter )
{}
/**
* Compiles and executes script in object's context with optional identifier string.
* A time limit (milliseconds) and/or memory limit (bytes) can be provided to restrict execution. These options will throw a V8JsTimeLimitException or V8JsMemoryLimitException.
* @param string $script
* @param string $identifier
* @param int $flags
* @param int $time_limit in milliseconds
* @param int $memory_limit in bytes
* @return mixed
*/
public function executeString ( $ script , $ identifier = '' , $ flags = V8Js:: FLAG_NONE , $ time_limit = 0 , $ memory_limit = 0 )
{}
/**
* Compiles a script in object's context with optional identifier string.
* @param $script
* @param string $identifier
* @return resource
*/
public function compileString ( $ script , $ identifier = '' )
{}
/**
* Executes a precompiled script in object's context.
* A time limit (milliseconds) and/or memory limit (bytes) can be provided to restrict execution. These options will throw a V8JsTimeLimitException or V8JsMemoryLimitException.
* @param resource $script
* @param int $flags
* @param int $time_limit
* @param int $memory_limit
*/
public function executeScript ( $ script , $ flags = V8Js:: FLAG_NONE , $ time_limit = 0 , $ memory_limit = 0 )
{}
/**
* Set the time limit (in milliseconds) for this V8Js object
* works similar to the set_time_limit php
* @param int $limit
*/
public function setTimeLimit ( $ limit )
{}
/**
* Set the memory limit (in bytes) for this V8Js object
* @param int $limit
*/
public function setMemoryLimit ( $ limit )
{}
/**
* Set the average object size (in bytes) for this V8Js object.
* V8's "amount of external memory" is adjusted by this value for every exported object. V8 triggers a garbage collection once this totals to 192 MB.
* @param int $average_object_size
*/
public function setAverageObjectSize ( $ average_object_size )
{}
/**
* Returns uncaught pending exception or null if there is no pending exception.
* @return V8JsScriptException|null
*/
public function getPendingException ()
{}
/**
* Clears the uncaught pending exception
*/
public function clearPendingException ()
{}
/** Static methods **/
/**
* Creates a custom V8 heap snapshot with the provided JavaScript source embedded.
* @param string $embed_source
* @return string|false
*/
public static function createSnapshot ( $ embed_source )
{}
}
final class V8JsScriptException extends Exception
{
/**
* @return string
*/
final public function getJsFileName ( ) {}
/**
* @return int
*/
final public function getJsLineNumber ( ) {}
/**
* @return int
*/
final public function getJsStartColumn ( ) {}
/**
* @return int
*/
final public function getJsEndColumn ( ) {}
/**
* @return string
*/
final public function getJsSourceLine ( ) {}
/**
* @return string
*/
final public function getJsTrace ( ) {}
}
final class V8JsTimeLimitException extends Exception
{
}
final class V8JsMemoryLimitException extends Exception
{
}
// Print a string.
print ( string ) ;
// Dump the contents of a variable.
var_dump ( value ) ;
// Terminate Javascript execution immediately.
exit ( ) ;
// CommonJS Module support to require external code.
// This makes use of the PHP module loader provided via V8Js::setModuleLoader (see PHP API above).
require ( "path/to/module" ) ;
JavaScript in
运算符应用于包装的 PHP 对象时,其工作方式与 PHP isset()
函数相同。类似地,当应用于包装的 PHP 对象时,JavaScript delete
工作方式类似于 PHP unset
。
<?php
class Foo {
var $ bar = null ;
}
$ v8 = new V8Js ();
$ v8 -> foo = new Foo ;
// This prints "no"
$ v8 -> executeString ( ' print( "bar" in PHP.foo ? "yes" : "no" ); ' );
?>
PHP 对于属性和方法有单独的命名空间,而 JavaScript 只有一个。通常这不是问题,但如果需要,可以使用前导$
来指定属性,或使用__call
来专门调用方法。
<?php
class Foo {
var $ bar = " bar " ;
function bar ( $ what ) { echo " I'm a " , $ what , " ! n" ; }
}
$ foo = new Foo ;
// This prints 'bar'
echo $ foo -> bar , "n" ;
// This prints "I'm a function!"
$ foo -> bar ( " function " );
$ v8 = new V8Js ();
$ v8 -> foo = new Foo ;
// This prints 'bar'
$ v8 -> executeString ( ' print(PHP.foo.$bar, "n"); ' );
// This prints "I'm a function!"
$ v8 -> executeString ( ' PHP.foo.__call("bar", ["function"]); ' );
?>
PHP 和 JavaScript 数据类型不完全匹配。当然,两种语言都有处理数字的数据类型。然而,PHP 区分整数和浮点数,而 JavaScript 只有Number
类型,它是 IEEE 754 浮点数。在许多情况下,当两种语言都可以很好地表示相同的数字时,这根本不重要。然而也有一些边缘情况。
在 64 位系统上,PHP 允许整数有 64 位有效位,但 JavaScript 数字类型(即 IEEE 754)只有 52 位尾数。因此,将会损失一些精度。如果您传递超过 15 位精确小数位的整数值,这一点就变得很重要。
尽管数组的名称很常见,但 PHP 和 JavaScript 之间的数组概念却有很大不同。在 JavaScript 中,数组是由从零开始向上的整数索引的元素的连续集合。在 PHP 中,数组可以是稀疏的,即整数键不需要是连续的,甚至可以是负数。此外,PHP 数组不仅可以使用整数作为键,还可以使用字符串(所谓的关联数组)。相反,JavaScript 数组允许将属性附加到数组,而 PHP 不支持这一点。这些属性不是数组集合的一部分,例如Array.prototype.forEach
方法不会“看到”这些属性。
一般来说,如果可能的话,PHP 数组会映射到 JavaScript“本机”数组,即 PHP 数组使用从零开始的连续数字键。关联数组和稀疏数组都映射到 JavaScript 对象。这些对象有一个也称为“Array”的构造函数,但它们不是本机数组,并且不共享 Array.prototype,因此它们不(直接)支持典型的数组函数,如join
、 forEach
等。PHP 数组是立即按值导出,无需实时绑定。如果您在 JavaScript 端更改值或将更多值推送到数组中,则此更改不会反映在 PHP 端。
如果 JavaScript 数组传回 PHP,JavaScript 数组始终会转换为 PHP 数组。如果 JavaScript 数组附加了(自己的)属性,这些属性也会转换为 PHP 数组的键。
传递给 JavaScript 的 PHP 对象被映射到本机 JavaScript 对象,该对象具有一个带有 PHP 对象类名称的“虚拟”构造函数。只要 PHP 类没有非公共__construct
方法,此构造函数就可用于创建 PHP 类的新实例。所有公共方法和属性对于 JavaScript 代码都是可见的,并且这些属性是实时绑定的,即如果 JavaScript 代码更改了属性的值,则 PHP 对象也会受到影响。
如果将本机 JavaScript 对象传递给 PHP,则 JavaScript 对象将映射到V8Object
类的 PHP 对象。该对象具有 JavaScript 对象所具有的所有属性,并且是完全可变的。如果将某个函数分配给这些属性之一,则它也可以由 PHP 代码调用。通过设置V8Js::FLAG_FORCE_ARRAY
标志,可以将executeString
函数配置为始终将JavaScript对象映射到PHP数组。然后,标准数组行为适用于值不是实时绑定的,即,如果更改生成的 PHP 数组的值,JavaScript 对象不会受到影响。
上述 PHP 对象通常转换为 JavaScript 对象的规则也适用于ArrayObject
类型或其他类的 PHP 对象,它们实现了ArrayAccess
和Countable
接口 - 即使它们的行为类似于 PHP 数组。
可以通过启用 php.ini 标志v8js.use_array_access
来更改此行为。如果设置,实现上述接口的 PHP 类的对象将转换为 JavaScript 类数组对象。这是对该对象的按索引访问,会导致立即调用offsetGet
或offsetSet
PHP 方法(实际上,这是 JavaScript 对 PHP 对象的实时绑定)。这样的 Array 式对象还支持调用 PHP 对象的每个附加公共方法 + JavaScript 原生 Array.prototype 方法的方法(只要它们不被 PHP 方法重载)。
首先,自定义启动快照是 V8 本身提供的一项功能,建立在其常规堆快照功能之上。这个想法是,由于在完成任何实际工作之前加载一些 JavaScript 库是很常见的,因此该库代码也被烘焙到堆快照中。
此扩展提供了一种创建这些自定义快照的简单方法。为了创建这样一个包含fibonacci
函数的快照,只需静态调用V8Js::createSnapshot
即可,如下所示:
$ snapshot = V8Js:: createSnapshot ( ' var fibonacci = n => n < 3 ? 1 : fibonacci(n - 1) + fibonacci(n - 2) ' );
然后将$snapshot
的内容保存到您喜欢的任何位置,例如本地文件系统或 Redis。
如果您需要创建一个新的 V8Js 实例,只需将快照作为第 5 个参数传递给 V8Js 构造函数:
$ jscript = new V8Js ( ' php ' , array (), array (), true , $ snapshot );
echo $ jscript -> executeString ( ' fibonacci(43) ' ) . "n" ;
请记住,要包含在快照中的代码可能不会直接调用从 PHP 导出的任何函数,因为它们是在快照代码运行后立即添加的。
如果 JavaScript 代码抛出(未捕获)、导致错误或无法编译,则会抛出V8JsScriptException
异常。
默认情况下,由于 JavaScript 代码调用而发生的 PHP 异常不会重新抛出到 JavaScript 上下文中,而是会导致 JavaScript 执行立即停止,然后在调用 JS 代码的位置报告。
可以通过设置FLAG_PROPAGATE_PHP_EXCEPTIONS
标志来更改此行为。如果设置了,PHP 异常(对象)将转换为遵守上述规则的 JavaScript 对象,并在 JavaScript 上下文中重新抛出。如果 JavaScript 代码没有捕获它们,则执行将停止并抛出V8JsScriptException
,其中可以通过getPrevious
方法访问原始 PHP 异常。
考虑到 JS 代码可以访问异常对象上的getTrace
等方法。如果您执行不受信任的代码,这可能是不需要的行为。使用setExceptionFilter
方法可以提供可调用的,它可以将 PHP 异常转换为可以安全公开的其他值。过滤器还可以通过重新抛出传递的异常或抛出另一个异常来决定根本不将异常传播到 JS。