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
等。按值導出,無需即時綁定。如果您在 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。