V8Js — это расширение PHP для движка Google V8 Javascript.
Расширение позволяет выполнять код Javascript в защищенной песочнице из PHP. Выполняемый код может быть ограничен с помощью ограничения по времени и/или памяти. Это дает возможность уверенно выполнять ненадежный код.
Мастер библиотеки Javascript Engine V8 (libv8) https://github.com/v8/v8-git-mirror (магистраль)
V8 — это Javascript-движок Google с открытым исходным кодом. V8 написан на C++ и используется в Google Chrome, браузере с открытым исходным кодом от Google. V8 реализует ECMAScript, как указано в ECMA-262, 5-е издание.
Для этого расширения требуется 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. В нем предустановлены 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 delete
JavaScript работает аналогично unset
PHP.
<?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.
Объекты PHP, передаваемые в JavaScript, сопоставляются с собственными объектами JavaScript, которые имеют «виртуальную» функцию-конструктор с именем класса объекта PHP. Эту функцию-конструктор можно использовать для создания новых экземпляров класса PHP, если у класса PHP нет закрытого метода __construct
. Все общедоступные методы и свойства видны коду JavaScript, а свойства привязаны к реальному времени, т. е. если значение свойства изменяется кодом JavaScript, это также влияет на объект PHP.
Если в PHP передается собственный объект JavaScript, объект JavaScript сопоставляется с объектом PHP класса V8Object
. Этот объект обладает всеми свойствами объекта JavaScript и полностью изменяем. Если функция назначена одному из этих свойств, ее также можно вызвать из кода PHP. executeString
можно настроить так, чтобы она всегда сопоставляла объекты JavaScript с массивами PHP, установив флаг V8Js::FLAG_FORCE_ARRAY
. Тогда применяется стандартное поведение массива: значения не привязаны к реальному времени, т. е. если вы измените значения результирующего массива PHP, объект JavaScript не будет затронут.
Вышеупомянутое правило, согласно которому объекты PHP обычно преобразуются в объекты JavaScript, также применимо к объектам PHP типа ArrayObject
или другим классам, которые реализуют как интерфейс ArrayAccess
, так и интерфейс Countable
, даже в этом случае они ведут себя как массивы PHP.
Это поведение можно изменить, включив в php.ini флаг v8js.use_array_access
. Если этот параметр установлен, объекты классов PHP, реализующие вышеупомянутые интерфейсы, преобразуются в объекты, подобные массиву JavaScript. Доступ к этому объекту по индексу приводит к немедленным вызовам методов PHP offsetGet
или offsetSet
(по сути, это живая привязка 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
.
Исключения PHP, возникающие из-за вызовов из кода JavaScript, по умолчанию не перебрасываются повторно в контекст JavaScript, а приводят к немедленной остановке выполнения JavaScript, а затем передаются в место, вызывающее код JS.
Это поведение можно изменить, установив флаг FLAG_PROPAGATE_PHP_EXCEPTIONS
. Если он установлен, исключения PHP (объекты) преобразуются в объекты JavaScript, подчиняющиеся вышеуказанным правилам, и повторно создаются в контексте JavaScript. Если они не перехватываются кодом JavaScript, выполнение останавливается и генерируется исключение V8JsScriptException
, исходное исключение PHP которого доступно через метод getPrevious
.
Учтите, что код JS имеет доступ к таким методам, как getTrace
для объекта исключения. Это может быть нежелательным поведением, если вы выполняете ненадежный код. Используя метод setExceptionFilter
можно предоставить вызываемый объект, который может преобразовать исключение PHP в какое-либо другое значение, которое безопасно предоставить. Фильтр также может принять решение вообще не распространять исключение на JS, либо повторно выдав переданное исключение, либо выдав другое исключение.