V8Js は、Google の V8 Javascript エンジン用の PHP 拡張機能です。
この拡張機能を使用すると、PHP から安全なサンドボックスで Javascript コードを実行できます。実行されるコードは、時間制限やメモリ制限を使用して制限できます。これにより、信頼できないコードを安心して実行できるようになります。
V8 Javascript Engine ライブラリ (libv8) マスター https://github.com/v8/v8-git-mirror (トランク)
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 バイナリを提供したりできるようになれば素晴らしいと思います。
PHP 7.0.0+ をターゲットとするphp7
という名前のブランチがあります。
最初のステップでは、手動でコンパイルする代わりに、V8J の 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 には 1 つの名前空間しかありません。通常、これは問題ではありませんが、必要な場合は、先頭の$
使用してプロパティを指定するか、 __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 には IEEE 754 浮動小数点数であるNumber
しかありません。両方の言語が同じ数値を適切に表現できる場合、多くの場合、これはまったく問題になりません。ただし、特殊なケースもあります。
64 ビット システムでは、PHP では整数の有効ビット数が 64 ビットであることが許可されていますが、JavaScript の数値型 (つまり、IEEE 754) では仮数部が 52 ビットのみです。したがって、ある程度の精度は失われます。これは、15 桁を超える正確な 10 進数の整数値を渡す場合に問題になり始めます。
共通の名前にもかかわらず、配列の概念は 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 オブジェクトは、PHP オブジェクトのクラスの名前を持つ「仮想」コンストラクター関数を持つネイティブ JavaScript オブジェクトにマップされます。 PHP クラスに非パブリック__construct
メソッドがない限り、このコンストラクター関数を使用して PHP クラスの新しいインスタンスを作成できます。すべてのパブリック メソッドとプロパティは JavaScript コードから可視であり、プロパティはライブ バインドされています。つまり、プロパティの値が JavaScript コードによって変更されると、PHP オブジェクトも影響を受けます。
ネイティブ JavaScript オブジェクトが PHP に渡される場合、JavaScript オブジェクトはV8Object
クラスの PHP オブジェクトにマップされます。このオブジェクトには JavaScript オブジェクトが持つすべてのプロパティがあり、完全に変更可能です。関数がこれらのプロパティのいずれかに割り当てられている場合、その関数は PHP コードからも呼び出すことができます。 V8Js::FLAG_FORCE_ARRAY
フラグを設定することにより、 executeString
関数は JavaScript オブジェクトを常に PHP 配列にマップするように構成できます。この場合、値はライブ バインドされないという標準の配列動作が適用されます。つまり、結果として得られる PHP 配列の値を変更しても、JavaScript オブジェクトは影響を受けません。
PHP オブジェクトは一般に JavaScript オブジェクトに変換されるという上記のルールは、 ArrayAccess
インターフェイスとCountable
インターフェイスの両方を実装するArrayObject
タイプまたは他のクラスの PHP オブジェクトにも適用されます。たとえそれらが PHP 配列のように動作するとしてもです。
この動作は、php.ini フラグv8js.use_array_access
有効にすることで変更できます。設定すると、前述のインターフェイスを実装する PHP クラスのオブジェクトが JavaScript 配列のようなオブジェクトに変換されます。これは、このオブジェクトへのインデックスによるアクセスであり、その結果、 offsetGet
またはoffsetSet
PHP メソッドが即座に呼び出されます (事実上、これは PHP オブジェクトに対する JavaScript のライブ バインディングです)。このような 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 など) に永続化します。
新しい V8J インスタンスを作成する必要がある場合は、スナップショットを 5 番目の引数として V8J コンストラクターに渡すだけです。
$ 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 にまったく伝播しないことを決定することもあります。