V8Js es una extensión PHP para el motor V8 Javascript de Google.
La extensión le permite ejecutar código Javascript en un entorno limitado seguro desde PHP. El código ejecutado se puede restringir mediante un límite de tiempo y/o un límite de memoria. Esto brinda la posibilidad de ejecutar código que no es de confianza con confianza.
Maestro de la biblioteca del motor Javascript V8 (libv8) https://github.com/v8/v8-git-mirror (troncal)
V8 es el motor Javascript de código abierto de Google. V8 está escrito en C++ y se utiliza en Google Chrome, el navegador de código abierto de Google. V8 implementa ECMAScript como se especifica en ECMA-262, quinta edición.
Esta extensión requiere V8 9.0 o superior.
Las versiones V8 se publican con bastante rapidez y el equipo de V8 generalmente brinda soporte de seguridad para la línea de versiones enviada con el navegador Chrome (canal estable) y más reciente (solo). Para obtener una descripción general de la versión, consulte https://chromiumdash.appspot.com/branches.
PHP 8.0.0+
Esta implementación integrada del motor V8 utiliza bloqueo de subprocesos para que funcione con ZTS habilitado.
Actualmente, Windows no es compatible oficialmente. Principalmente porque no tengo tiempo para mantener el soporte yo mismo y realmente no tengo cajas de Windows para probar cosas. Sería fantástico si alguien pudiera mejorar y arreglar cosas en Windows, proporcionar archivos binarios V8 precompilados, etc.
Hay una rama llamada php7
que apunta a PHP 7.0.0+
Para algunos primeros pasos, en lugar de compilar manualmente, es posible que desee probar la imagen de la ventana acoplable V8Js. Tiene v8, v8js y php-cli preinstalados para que puedas probarlo con PHP en "modo interactivo". Sin embargo, no hay ningún Apache, etc. ejecutándose.
Construir en Microsoft Windows es un poco más complicado; consulte el archivo README.Win32.md para obtener una descripción general rápida. Construir sobre GNU/Linux y MacOS X es sencillo; consulte los archivos README.Linux.md y README.MacOS.md para obtener un recorrido con notas específicas de la plataforma.
<?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" ) ;
El operador JavaScript in
, cuando se aplica a un objeto PHP envuelto, funciona igual que la función isset()
de PHP. De manera similar, cuando se aplica a un objeto PHP envuelto, delete
de JavaScript funciona como 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 tiene espacios de nombres separados para propiedades y métodos, mientras que JavaScript tiene solo uno. Por lo general, esto no es un problema, pero si es necesario, puede usar $
al principio para especificar una propiedad o __call
para invocar específicamente un método.
<?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"]); ' );
?>
Los tipos de datos PHP y JavaScript no coinciden exactamente. Por supuesto, ambos idiomas tienen tipos de datos para manejar números. Sin embargo, PHP diferencia entre números enteros y números de punto flotante, mientras que JavaScript solo tiene un tipo Number
, que es un número de punto flotante IEEE 754. En muchos casos esto no importa en absoluto, cuando ambos idiomas pueden representar bien el mismo número. Sin embargo, hay casos extremos.
En sistemas de 64 bits, PHP permite que los números enteros tengan 64 bits significativos; sin embargo, el tipo de número JavaScript (es decir, IEEE 754) solo tiene mantisa de 52 bits. Por tanto, se perderá algo de precisión. Esto comienza a importar si pasa valores enteros con más de 15 dígitos decimales precisos.
A pesar del nombre común, el concepto de matrices es muy diferente entre PHP y JavaScript. En JavaScript, una matriz es una colección contigua de elementos indexados por números enteros desde cero en adelante. En PHP las matrices pueden ser escasas, es decir, las claves integrales no necesitan ser contiguas e incluso pueden ser negativas. Además, los arrays de PHP no sólo pueden utilizar números enteros como claves, sino también cadenas (los llamados arrays asociativos). Por el contrario, las matrices de JavaScript permiten adjuntar propiedades a las matrices, lo cual no es compatible con PHP. Esas propiedades no son parte de la colección de matrices, por ejemplo, el método Array.prototype.forEach
no las "ve".
Generalmente, los arreglos PHP se asignan a arreglos "nativos" de JavaScript si esto es posible, es decir, el arreglo PHP usa claves numéricas contiguas desde cero en adelante. Tanto las matrices asociativas como las dispersas se asignan a objetos JavaScript. Esos objetos tienen un constructor también llamado "Array", pero no son arreglos nativos y no comparten el prototipo Array., por lo tanto, no admiten (directamente) las funciones de arreglo típicas como join
, forEach
, etc. Los arreglos PHP son Exportación inmediata valor por valor sin vinculación en vivo. Esto es si cambia un valor en el lado de JavaScript o inserta más valores en la matriz, este cambio no se refleja en el lado de PHP.
Si las matrices de JavaScript se devuelven a PHP, la matriz de JavaScript siempre se convierte en una matriz de PHP. Si la matriz JavaScript tiene propiedades (propias) adjuntas, estas también se convierten en claves de la matriz PHP.
Los objetos PHP pasados a JavaScript se asignan a objetos JavaScript nativos que tienen una función constructora "virtual" con el nombre de la clase del objeto PHP. Esta función constructora se puede utilizar para crear nuevas instancias de la clase PHP siempre que la clase PHP no tenga un método __construct
no público. Todos los métodos y propiedades públicos son visibles para el código JavaScript y las propiedades están vinculadas en vivo, es decir, si el código JavaScript cambia el valor de una propiedad, el objeto PHP también se ve afectado.
Si un objeto JavaScript nativo se pasa a PHP, el objeto JavaScript se asigna a un objeto PHP de la clase V8Object
. Este objeto tiene todas las propiedades que tiene el objeto JavaScript y es completamente mutable. Si se asigna una función a una de esas propiedades, también se puede llamar mediante código PHP. La función executeString
se puede configurar para asignar siempre objetos JavaScript a matrices PHP configurando el indicador V8Js::FLAG_FORCE_ARRAY
. Entonces se aplica el comportamiento estándar de la matriz: los valores no están vinculados en vivo, es decir, si cambia los valores de la matriz PHP resultante, el objeto JavaScript no se ve afectado.
La regla anterior de que los objetos PHP generalmente se convierten en objetos JavaScript también se aplica a los objetos PHP de tipo ArrayObject
u otras clases, que implementan tanto la interfaz ArrayAccess
como la Countable
; aun así, se comportan como matrices PHP.
Este comportamiento se puede cambiar habilitando el indicador php.ini v8js.use_array_access
. Si se establece, los objetos de las clases PHP que implementan las interfaces antes mencionadas se convierten en objetos tipo matriz de JavaScript. Este acceso por índice de este objeto da como resultado llamadas inmediatas a los métodos PHP offsetGet
o offsetSet
(efectivamente, esto es un enlace en vivo de JavaScript con el objeto PHP). Un objeto Array-esque de este tipo también admite la llamada a todos los métodos públicos adjuntos del objeto PHP + métodos de los métodos nativos Array.prototype de JavaScript (siempre que no estén sobrecargados por métodos PHP).
En primer lugar, las instantáneas de inicio personalizadas son una característica proporcionada por el propio V8, construida sobre su característica general de instantáneas del montón. La idea es que, dado que es bastante común cargar alguna biblioteca de JavaScript antes de realizar cualquier trabajo real, este código de biblioteca también se integra en la instantánea del montón.
Esta extensión proporciona una manera sencilla de crear esas instantáneas personalizadas. Para crear una instantánea de este tipo con una función fibonacci
incorporada, simplemente llame V8Js::createSnapshot
estáticamente de esta manera:
$ snapshot = V8Js:: createSnapshot ( ' var fibonacci = n => n < 3 ? 1 : fibonacci(n - 1) + fibonacci(n - 2) ' );
Luego persista el contenido de $snapshot
en donde quiera, por ejemplo, el sistema de archivos local o tal vez Redis.
Si necesita crear una nueva instancia de V8Js, simplemente pase la instantánea como quinto argumento al constructor de V8Js:
$ jscript = new V8Js ( ' php ' , array (), array (), true , $ snapshot );
echo $ jscript -> executeString ( ' fibonacci(43) ' ) . "n" ;
Tenga en cuenta que es posible que el código que se incluirá en la instantánea no llame directamente a ninguna de las funciones exportadas desde PHP, ya que se agregan justo después de ejecutar el código de la instantánea.
Si el código JavaScript genera (sin detectar), causa errores o no se compila, se generan excepciones V8JsScriptException
.
Las excepciones de PHP que se producen debido a llamadas desde código JavaScript de forma predeterminada no se vuelven a generar en el contexto de JavaScript, pero provocan que la ejecución de JavaScript se detenga inmediatamente y luego se informan en la ubicación que llama al código JS.
Este comportamiento se puede cambiar configurando el indicador FLAG_PROPAGATE_PHP_EXCEPTIONS
. Si está configurada, la excepción PHP (objetos) se convierte en objetos JavaScript obedeciendo las reglas anteriores y se vuelve a generar en el contexto de JavaScript. Si no son detectados por el código JavaScript, la ejecución se detiene y se lanza una V8JsScriptException
, que tiene la excepción PHP original accesible a través del método getPrevious
.
Considere que el código JS tiene acceso a métodos como getTrace
en el objeto de excepción. Este podría ser un comportamiento no deseado si ejecuta código que no es de confianza. Usando el método setExceptionFilter
se puede proporcionar un invocable, que puede convertir la excepción PHP en algún otro valor que sea seguro exponer. El filtro también puede decidir no propagar la excepción a JS, ya sea volviendo a lanzar la excepción pasada o lanzando otra excepción.