V8Js adalah ekstensi PHP untuk mesin Javascript V8 Google.
Ekstensi ini memungkinkan Anda mengeksekusi kode Javascript di kotak pasir aman dari PHP. Kode yang dieksekusi dapat dibatasi menggunakan batas waktu dan/atau batas memori. Hal ini memberikan kemungkinan untuk mengeksekusi kode yang tidak dipercaya dengan percaya diri.
Pustaka Mesin Javascript V8 (libv8) master https://github.com/v8/v8-git-mirror (trunk)
V8 adalah mesin Javascript sumber terbuka Google. V8 ditulis dalam C++ dan digunakan di Google Chrome, browser sumber terbuka dari Google. V8 mengimplementasikan ECMAScript sebagaimana ditentukan dalam ECMA-262, edisi ke-5.
Ekstensi ini memerlukan V8 9.0 atau lebih tinggi.
Rilis V8 diterbitkan dengan cukup cepat dan tim V8 biasanya memberikan dukungan keamanan untuk baris versi yang dikirimkan bersama browser Chrome (saluran stabil) dan yang lebih baru (hanya). Untuk ikhtisar versi, lihat https://chromiumdash.appspot.com/branches.
PHP 8.0.0+
Implementasi mesin V8 yang tertanam ini menggunakan penguncian ulir sehingga dapat berfungsi dengan ZTS yang diaktifkan.
Windows saat ini tidak didukung secara resmi. Terutama karena saya sendiri tidak mempunyai waktu untuk mempertahankan dukungannya, dan tidak memiliki kotak Windows untuk mencoba berbagai hal. Akan sangat bagus jika seseorang dapat meningkatkan dan memperbaiki berbagai hal di Windows, menyediakan binari V8 yang sudah dibuat sebelumnya, dll.
Ada cabang bernama php7
yang menargetkan PHP 7.0.0+
Untuk beberapa langkah pertama, daripada mengkompilasi secara manual, Anda mungkin ingin mencoba image buruh pelabuhan V8Js. Ini memiliki v8, v8js dan php-cli yang sudah diinstal sebelumnya sehingga Anda dapat mencobanya dengan PHP dalam "mode interaktif". Namun tidak ada Apache, dll. yang berjalan.
Membangun di Microsoft Windows sedikit lebih rumit, lihat file README.Win32.md untuk menjalankannya dengan cepat. Membangun GNU/Linux dan MacOS X sangatlah mudah, lihat file README.Linux.md dan README.MacOS.md untuk mengetahui catatan khusus platform.
<?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" ) ;
Operator JavaScript in
, ketika diterapkan pada objek PHP yang dibungkus, bekerja sama dengan fungsi PHP isset()
. Demikian pula, ketika diterapkan pada objek PHP yang dibungkus, delete
JavaScript berfungsi seperti 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 memiliki namespace terpisah untuk properti dan metode, sedangkan JavaScript hanya memiliki satu namespace. Biasanya ini tidak menjadi masalah, tetapi jika perlu, Anda dapat menggunakan $
di depannya untuk menentukan properti, atau __call
untuk memanggil metode secara khusus.
<?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"]); ' );
?>
Tipe data PHP dan JavaScript tidak sama persis. Hal ini tentu saja kedua bahasa memiliki tipe data untuk menangani angka. Namun PHP membedakan antara bilangan bulat dan bilangan floating point, sebaliknya JavaScript hanya memiliki tipe Number
, yang merupakan bilangan floating point IEEE 754. Dalam banyak kasus, hal ini tidak menjadi masalah sama sekali, ketika kedua bahasa dapat mewakili angka yang sama dengan baik. Namun ada kasus-kasus ekstrem.
Pada sistem 64-bit PHP mengizinkan bilangan bulat memiliki 64 bit signifikan, tipe nomor JavaScript (yaitu IEEE 754) namun hanya memiliki mantissa 52 bit. Oleh karena itu beberapa presisi akan hilang. Ini mulai menjadi masalah jika Anda meneruskan nilai bilangan bulat dengan lebih dari 15 angka desimal yang akurat.
Meskipun namanya umum, konsep array sangat berbeda antara PHP dan JavaScript. Dalam JavaScript, array adalah kumpulan elemen yang berdekatan dan diindeks dengan bilangan integral dari nol ke atas. Dalam PHP array dapat menjadi sparse, yaitu kunci integral tidak harus bersebelahan dan bahkan mungkin negatif. Selain itu array PHP tidak hanya menggunakan bilangan integral sebagai kunci tetapi juga string (disebut array asosiatif). Array JavaScript yang berlawanan memungkinkan properti dilampirkan ke array, yang tidak didukung oleh PHP. Properti tersebut bukan bagian dari kumpulan array, misalnya metode Array.prototype.forEach
tidak "melihat" properti tersebut.
Umumnya array PHP dipetakan ke array "asli" JavaScript jika hal ini memungkinkan, yaitu array PHP menggunakan kunci numerik yang berdekatan dari nol ke atas. Array asosiatif dan array sparse dipetakan ke objek JavaScript. Objek-objek tersebut memiliki konstruktor yang juga disebut "Array", tetapi objek tersebut bukan array asli dan tidak menggunakan Array.prototype yang sama, oleh karena itu objek tersebut tidak (secara langsung) mendukung fungsi array pada umumnya seperti join
, forEach
, dll. Array PHP adalah segera diekspor nilai demi nilai tanpa pengikatan langsung. Ini terjadi jika Anda mengubah nilai di sisi JavaScript atau memasukkan nilai lebih lanjut ke dalam array, perubahan ini tidak tercermin di sisi PHP.
Jika array JavaScript diteruskan kembali ke PHP, array JavaScript selalu dikonversi ke array PHP. Jika array JavaScript memiliki properti (sendiri), properti ini juga akan dikonversi menjadi kunci array PHP.
Objek PHP yang diteruskan ke JavaScript dipetakan ke objek JavaScript asli yang memiliki fungsi konstruktor "virtual" dengan nama kelas objek PHP. Fungsi konstruktor ini dapat digunakan untuk membuat instance baru dari kelas PHP selama kelas PHP tersebut tidak memiliki metode __construct
non-publik. Semua metode dan properti publik dapat dilihat oleh kode JavaScript dan propertinya bersifat live-bound, yaitu jika nilai properti diubah oleh kode JavaScript, objek PHP juga akan terpengaruh.
Jika objek JavaScript asli diteruskan ke PHP, objek JavaScript dipetakan ke objek PHP kelas V8Object
. Objek ini memiliki semua properti yang dimiliki objek JavaScript dan dapat diubah sepenuhnya. Jika suatu fungsi ditugaskan ke salah satu properti tersebut, fungsi tersebut juga dapat dipanggil dengan kode PHP. Fungsi executeString
dapat dikonfigurasi untuk selalu memetakan objek JavaScript ke array PHP dengan mengatur flag V8Js::FLAG_FORCE_ARRAY
. Kemudian perilaku array standar berlaku bahwa nilai-nilai tidak terikat langsung, yaitu jika Anda mengubah nilai-nilai array PHP yang dihasilkan, objek JavaScript tidak terpengaruh.
Aturan di atas bahwa objek PHP umumnya dikonversi ke objek JavaScript juga berlaku untuk objek PHP bertipe ArrayObject
atau kelas lain, yang mengimplementasikan antarmuka ArrayAccess
dan Countable
-- meskipun demikian mereka berperilaku seperti array PHP.
Perilaku ini dapat diubah dengan mengaktifkan flag php.ini v8js.use_array_access
. Jika disetel, objek kelas PHP yang mengimplementasikan antarmuka yang disebutkan di atas akan diubah menjadi objek mirip Array JavaScript. Akses berdasarkan indeks dari objek ini menghasilkan panggilan langsung ke metode offsetGet
atau offsetSet
PHP (secara efektif ini adalah pengikatan langsung JavaScript terhadap objek PHP). Objek mirip Array juga mendukung pemanggilan setiap metode publik yang terlampir dari objek PHP + metode metode Array.prototype asli JavaScript (selama metode tersebut tidak kelebihan beban oleh metode PHP).
Pertama-tama, snapshot startup kustom adalah fitur yang disediakan oleh V8 itu sendiri, yang dibangun di atas fitur snapshot heap umumnya. Idenya adalah, karena cukup umum untuk memuat beberapa pustaka JavaScript sebelum pekerjaan sebenarnya dilakukan, kode pustaka ini juga dimasukkan ke dalam heap snapshot.
Ekstensi ini menyediakan cara mudah untuk membuat snapshot khusus tersebut. Untuk membuat snapshot seperti itu dengan fungsi fibonacci
yang dimasukkan ke dalamnya, panggil saja V8Js::createSnapshot
secara statis seperti ini:
$ snapshot = V8Js:: createSnapshot ( ' var fibonacci = n => n < 3 ? 1 : fibonacci(n - 1) + fibonacci(n - 2) ' );
Kemudian simpan konten $snapshot
ke mana pun Anda suka, misalnya sistem file lokal atau mungkin Redis.
Jika Anda perlu membuat instance V8Js baru, cukup teruskan snapshot sebagai argumen ke-5 ke konstruktor V8Js:
$ jscript = new V8Js ( ' php ' , array (), array (), true , $ snapshot );
echo $ jscript -> executeString ( ' fibonacci(43) ' ) . "n" ;
Perlu diingat, bahwa kode yang akan disertakan dalam snapshot tidak boleh secara langsung memanggil fungsi apa pun yang diekspor dari PHP, karena kode tersebut ditambahkan segera setelah kode snapshot dijalankan.
Jika kode JavaScript muncul (tanpa tertangkap), menyebabkan kesalahan, atau tidak dapat dikompilasi, pengecualian V8JsScriptException
akan ditampilkan.
Pengecualian PHP yang terjadi karena panggilan dari kode JavaScript secara default tidak dimasukkan kembali ke dalam konteks JavaScript tetapi menyebabkan eksekusi JavaScript segera dihentikan dan kemudian dilaporkan di lokasi pemanggilan kode JS.
Perilaku ini dapat diubah dengan menyetel tanda FLAG_PROPAGATE_PHP_EXCEPTIONS
. Jika disetel, pengecualian PHP (objek) diubah menjadi objek JavaScript dengan mematuhi aturan di atas dan ditampilkan kembali dalam konteks JavaScript. Jika mereka tidak ditangkap oleh kode JavaScript, eksekusi akan berhenti dan V8JsScriptException
dilempar, yang memiliki pengecualian PHP asli yang dapat diakses melalui metode getPrevious
.
Pertimbangkan bahwa kode JS memiliki akses ke metode seperti getTrace
pada objek pengecualian. Ini mungkin merupakan perilaku yang tidak diinginkan, jika Anda menjalankan kode yang tidak tepercaya. Dengan menggunakan metode setExceptionFilter
callable dapat disediakan, yang dapat mengonversi pengecualian PHP ke beberapa nilai lain yang aman untuk diekspos. Filter juga dapat memutuskan untuk tidak menyebarkan pengecualian ke JS sama sekali dengan melemparkan kembali pengecualian yang diteruskan atau melemparkan pengecualian lain.