V8Js é uma extensão PHP para o mecanismo Javascript V8 do Google.
A extensão permite executar código Javascript em uma sandbox segura do PHP. O código executado pode ser restringido usando um limite de tempo e/ou limite de memória. Isso oferece a possibilidade de executar código não confiável com confiança.
Biblioteca V8 Javascript Engine (libv8) mestre https://github.com/v8/v8-git-mirror (tronco)
V8 é o mecanismo Javascript de código aberto do Google. V8 é escrito em C++ e é usado no Google Chrome, o navegador de código aberto do Google. V8 implementa ECMAScript conforme especificado em ECMA-262, 5ª edição.
Esta extensão requer V8 9.0 ou superior.
As versões V8 são publicadas rapidamente e a equipe V8 geralmente fornece suporte de segurança para a linha de versões fornecida com o navegador Chrome (canal estável) e mais recentes (apenas). Para obter uma visão geral da versão, consulte https://chromiumdash.appspot.com/branches.
PHP 8.0.0+
Esta implementação incorporada do mecanismo V8 usa bloqueio de thread para funcionar com ZTS habilitado.
Atualmente, o Windows não é oficialmente suportado. Principalmente porque não tenho tempo para manter o suporte sozinho e não tenho caixas do Windows para experimentar. Seria ótimo se alguém pudesse melhorar e consertar as coisas no Windows, fornecer binários V8 pré-construídos, etc.
Existe um branch chamado php7
que tem como alvo o PHP 7.0.0+
Para algumas primeiras etapas, em vez de compilar manualmente, você pode experimentar a imagem docker V8Js. Possui v8, v8js e php-cli pré-instalados para que você possa experimentar o PHP no "modo interativo". No entanto, não há Apache, etc. em execução.
Construir no Microsoft Windows é um pouco mais complicado, consulte o arquivo README.Win32.md para uma rápida execução. Construir em GNU/Linux e MacOS X é simples, consulte os arquivos README.Linux.md e README.MacOS.md para obter um guia com notas específicas da 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" ) ;
O operador in
do JavaScript, quando aplicado a um objeto PHP empacotado, funciona da mesma forma que a função isset()
do PHP. Da mesma forma, quando aplicado a um objeto PHP empacotado, 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 possui namespaces separados para propriedades e métodos, enquanto JavaScript possui apenas um. Normalmente, isso não é um problema, mas se necessário, você pode usar um $
inicial para especificar uma propriedade ou __call
para invocar especificamente um 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"]); ' );
?>
Os tipos de dados PHP e JavaScript não correspondem exatamente. É claro que ambas as linguagens possuem tipos de dados para lidar com números. No entanto, o PHP diferencia números inteiros e números de ponto flutuante, ao contrário do JavaScript, que possui apenas um tipo Number
, que é um número de ponto flutuante IEEE 754. Em muitos casos, isso não importa, quando ambos os idiomas podem representar bem o mesmo número. No entanto, existem casos extremos.
Em sistemas de 64 bits, o PHP permite que números inteiros tenham 64 bits significativos, o tipo de número JavaScript (ou seja, IEEE 754), porém possui apenas mantissa de 52 bits. Conseqüentemente, alguma precisão será perdida. Isso começa a importar se você passar valores inteiros com mais de 15 dígitos decimais precisos.
Apesar do nome comum, o conceito de arrays é muito diferente entre PHP e JavaScript. Em JavaScript, um array é uma coleção contígua de elementos indexados por números inteiros de zero em diante. Em PHP os arrays podem ser esparsos, ou seja, as chaves integrais não precisam ser contíguas e podem até ser negativas. Além disso, os arrays PHP podem não apenas usar números inteiros como chaves, mas também strings (os chamados arrays associativos). Matrizes JavaScript contrárias permitem que propriedades sejam anexadas a matrizes, o que não é suportado pelo PHP. Essas propriedades não fazem parte da coleção de arrays, por exemplo, o método Array.prototype.forEach
não as "vê".
Geralmente arrays PHP são mapeados para arrays "nativos" de JavaScript se isso for possível, ou seja, o array PHP usa chaves numéricas contíguas de zero em diante. Matrizes associativas e esparsas são mapeadas para objetos JavaScript. Esses objetos têm um construtor também chamado de "Array", mas não são arrays nativos e não compartilham o Array.prototype, portanto, não suportam (diretamente) as funções típicas de array como join
, forEach
, etc. exportado imediatamente valor por valor sem vinculação ao vivo. Isto é, se você alterar um valor no lado do JavaScript ou inserir mais valores no array, essa alteração não será refletida no lado do PHP.
Se os arrays JavaScript forem passados de volta ao PHP, o array JavaScript será sempre convertido em um array PHP. Se o array JavaScript tiver propriedades (próprias) anexadas, elas também serão convertidas em chaves do array PHP.
Objetos PHP passados para JavaScript são mapeados para objetos JavaScript nativos que possuem uma função construtora "virtual" com o nome da classe do objeto PHP. Esta função construtora pode ser usada para criar novas instâncias da classe PHP, desde que a classe PHP não tenha um método __construct
não público. Todos os métodos e propriedades públicas são visíveis para o código JavaScript e as propriedades são vinculadas ao vivo, ou seja, se o valor de uma propriedade for alterado pelo código JavaScript, o objeto PHP também será afetado.
Se um objeto JavaScript nativo for passado para PHP, o objeto JavaScript será mapeado para um objeto PHP da classe V8Object
. Este objeto possui todas as propriedades do objeto JavaScript e é totalmente mutável. Se uma função for atribuída a uma dessas propriedades, ela também poderá ser chamada pelo código PHP. A função executeString
pode ser configurada para sempre mapear objetos JavaScript para arrays PHP definindo o sinalizador V8Js::FLAG_FORCE_ARRAY
. Em seguida, aplica-se o comportamento padrão do array de que os valores não são vinculados ao vivo, ou seja, se você alterar os valores do array PHP resultante, o objeto JavaScript não será afetado.
A regra acima de que objetos PHP são geralmente convertidos em objetos JavaScript também se aplica a objetos PHP do tipo ArrayObject
ou outras classes, que implementam tanto a interface ArrayAccess
quanto a Countable
- mesmo assim eles se comportam como arrays PHP.
Este comportamento pode ser alterado habilitando o sinalizador php.ini v8js.use_array_access
. Se definido, os objetos das classes PHP que implementam as interfaces mencionadas acima são convertidos em objetos do tipo JavaScript Array. Este é o acesso por índice deste objeto que resulta em chamadas imediatas para os métodos PHP offsetGet
ou offsetSet
(efetivamente, esta é uma ligação ao vivo de JavaScript em relação ao objeto PHP). Esse objeto do tipo Array também suporta a chamada de todos os métodos públicos anexados do objeto PHP + métodos dos métodos Array.prototype nativos do JavaScript (desde que não sejam sobrecarregados por métodos PHP).
Em primeiro lugar, os instantâneos de inicialização personalizados são um recurso fornecido pelo próprio V8, construído sobre seu recurso geral de instantâneos de heap. A ideia é que, como é bastante comum carregar alguma biblioteca JavaScript antes de qualquer trabalho real a ser feito, esse código da biblioteca também seja inserido no instantâneo do heap.
Esta extensão fornece uma maneira fácil de criar esses instantâneos personalizados. Para criar tal snapshot com uma função fibonacci
incorporada, basta chamar V8Js::createSnapshot
estaticamente assim:
$ snapshot = V8Js:: createSnapshot ( ' var fibonacci = n => n < 3 ? 1 : fibonacci(n - 1) + fibonacci(n - 2) ' );
Em seguida, persista o conteúdo de $snapshot
para onde quiser, por exemplo, no sistema de arquivos local ou talvez no Redis.
Se você precisar criar uma nova instância do V8Js, simplesmente passe o snapshot como quinto argumento para o construtor do V8Js:
$ jscript = new V8Js ( ' php ' , array (), array (), true , $ snapshot );
echo $ jscript -> executeString ( ' fibonacci(43) ' ) . "n" ;
Lembre-se de que o código a ser incluído no snapshot não pode chamar diretamente nenhuma das funções exportadas do PHP, pois elas são adicionadas logo após a execução do código do snapshot.
Se o código JavaScript for lançado (sem captura), causar erros ou não compilar, exceções V8JsScriptException
serão lançadas.
As exceções PHP que ocorrem devido a chamadas do código JavaScript por padrão não são lançadas novamente no contexto JavaScript, mas fazem com que a execução do JavaScript seja interrompida imediatamente e, em seguida, são relatadas no local que chama o código JS.
Este comportamento pode ser alterado definindo o sinalizador FLAG_PROPAGATE_PHP_EXCEPTIONS
. Se estiver definido, as exceções PHP (objetos) são convertidas em objetos JavaScript obedecendo às regras acima e lançadas novamente no contexto JavaScript. Se eles não forem capturados pelo código JavaScript, a execução será interrompida e uma V8JsScriptException
será lançada, que possui a exceção PHP original acessível através do método getPrevious
.
Considere que o código JS tem acesso a métodos como getTrace
no objeto de exceção. Este pode ser um comportamento indesejado se você executar código não confiável. Usando o método setExceptionFilter
um callable pode ser fornecido, que pode converter a exceção PHP em algum outro valor que seja seguro para expor. O filtro também pode decidir não propagar a exceção para JS, lançando novamente a exceção passada ou lançando outra exceção.