V8Js เป็นส่วนขยาย PHP สำหรับเอ็นจิ้น V8 Javascript ของ Google
ส่วนขยายช่วยให้คุณสามารถรันโค้ด Javascript ในแซนด์บ็อกซ์ที่ปลอดภัยจาก PHP รหัสที่ดำเนินการสามารถจำกัดได้โดยใช้การจำกัดเวลาและ/หรือขีดจำกัดหน่วยความจำ สิ่งนี้ให้ความเป็นไปได้ในการรันโค้ดที่ไม่น่าเชื่อถือด้วยความมั่นใจ
ไลบรารี่ V8 Javascript Engine (libv8) ต้นแบบ https://github.com/v8/v8-git-mirror (ลำตัว)
V8 เป็นเครื่องมือ Javascript แบบโอเพ่นซอร์สของ Google V8 เขียนด้วยภาษา C++ และใช้ใน Google Chrome ซึ่งเป็นเบราว์เซอร์โอเพ่นซอร์สจาก Google V8 ใช้ ECMAScript ตามที่ระบุไว้ใน ECMA-262 ฉบับที่ 5
ส่วนขยายนี้ต้องใช้ 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 มีการติดตั้ง 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 ที่ห่อไว้ delete
JavaScript จะทำงานเหมือนกับ 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 ในจาวาสคริปต์ อาร์เรย์คือชุดขององค์ประกอบที่ต่อเนื่องกัน ซึ่งจัดทำดัชนีโดยตัวเลขอินทิกรัลตั้งแต่ศูนย์ขึ้นไป ในอาเรย์ 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 ด้วย
อ็อบเจ็กต์ PHP ที่ส่งผ่านไปยัง JavaScript จะถูกแมปกับอ็อบเจ็กต์ JavaScript ดั้งเดิมซึ่งมีฟังก์ชันคอนสตรัคเตอร์ "เสมือน" พร้อมด้วยชื่อของคลาสของอ็อบเจ็กต์ PHP ฟังก์ชัน Constructor นี้สามารถใช้เพื่อสร้างอินสแตนซ์ใหม่ของคลาส PHP ได้ ตราบใดที่คลาส PHP ไม่มีเมธอด __construct
ที่ไม่ใช่แบบสาธารณะ วิธีการและคุณสมบัติสาธารณะทั้งหมดสามารถมองเห็นได้ด้วยโค้ด JavaScript และคุณสมบัตินั้นถูกผูกไว้แบบสด เช่น หากค่าของคุณสมบัติถูกเปลี่ยนแปลงด้วยโค้ด JavaScript อ็อบเจ็กต์ PHP ก็ได้รับผลกระทบเช่นกัน
หากอ็อบเจ็กต์ JavaScript ดั้งเดิมถูกส่งไปยัง PHP อ็อบเจ็กต์ JavaScript จะถูกแมปกับอ็อบเจ็กต์ PHP ของคลาส V8Object
อ็อบเจ็กต์นี้มีคุณสมบัติทั้งหมดที่อ็อบเจ็กต์ JavaScript มีและสามารถเปลี่ยนแปลงได้อย่างสมบูรณ์ หากมีการกำหนดฟังก์ชันให้กับหนึ่งในคุณสมบัติเหล่านั้น ก็สามารถเรียกฟังก์ชันนั้นได้ด้วยโค้ด PHP สามารถกำหนดค่าฟังก์ชัน executeString
ให้แมปออบเจ็กต์ JavaScript กับอาร์เรย์ PHP ได้โดยตั้งค่าสถานะ V8Js::FLAG_FORCE_ARRAY
จากนั้น ลักษณะการทำงานของอาร์เรย์มาตรฐานจะใช้โดยที่ค่าต่างๆ จะไม่ถูกผูกไว้แบบสด กล่าวคือ หากคุณเปลี่ยนค่าของอาร์เรย์ PHP ผลลัพธ์ อ็อบเจ็กต์ JavaScript จะ ไม่ ได้รับผลกระทบ
กฎข้างต้นที่โดยทั่วไปแล้วอ็อบเจ็กต์ PHP จะถูกแปลงเป็นอ็อบเจ็กต์ JavaScript ยังใช้กับอ็อบเจ็กต์ PHP ประเภท ArrayObject
หรือคลาสอื่น ๆ ที่ใช้งานทั้ง ArrayAccess
และอินเทอร์เฟซ Countable
แม้ว่าพวกมันจะทำงานเหมือนอาร์เรย์ PHP ก็ตาม
ลักษณะการทำงานนี้สามารถเปลี่ยนแปลงได้โดยการเปิดใช้งานแฟล็ก php.ini v8js.use_array_access
หากตั้งค่าไว้ ออบเจ็กต์ของคลาส PHP ที่ใช้อินเทอร์เฟซดังกล่าวจะถูกแปลงเป็นออบเจ็กต์ที่มีลักษณะคล้าย JavaScript Array นี่คือการเข้าถึงโดยดัชนีของวัตถุนี้ส่งผลให้มีการเรียกเมธอด offsetGet
หรือ offsetSet
PHP ทันที (นี่คือการเชื่อมโยง JavaScript แบบสดกับวัตถุ PHP) อ็อบเจ็กต์ Array-esque ดังกล่าวยังรองรับการเรียกเมธอดสาธารณะทุกอันที่แนบมาของอ็อบเจ็กต์ PHP + เมธอดของเมธอด Array.prototype ดั้งเดิมของ JavaScript (ตราบใดที่เมธอด 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
จะถูกส่งออกไป
ข้อยกเว้น PHP ที่เกิดขึ้นเนื่องจากการเรียกจากโค้ด JavaScript โดยค่าเริ่มต้นจะ ไม่ ถูกโยนอีกครั้งในบริบทของ JavaScript แต่จะทำให้การทำงานของ JavaScript หยุดทันที จากนั้นจะถูกรายงานที่ตำแหน่งที่เรียกใช้โค้ด JS
ลักษณะการทำงานนี้สามารถเปลี่ยนแปลงได้โดยการตั้งค่าสถานะ FLAG_PROPAGATE_PHP_EXCEPTIONS
หากมีการตั้งค่าไว้ ข้อยกเว้น PHP (อ็อบเจ็กต์) จะถูกแปลงเป็นอ็อบเจ็กต์ JavaScript ที่ปฏิบัติตามกฎข้างต้นและโยนใหม่ในบริบท JavaScript หากไม่ถูกดักจับโดยโค้ด JavaScript การดำเนินการจะหยุดลงและ V8JsScriptException
จะถูกส่งออกไป ซึ่งมีข้อยกเว้น PHP ดั้งเดิมที่สามารถเข้าถึงได้ผ่านวิธี getPrevious
พิจารณาว่าโค้ด JS สามารถเข้าถึงวิธีการเช่น getTrace
บนอ็อบเจ็กต์ข้อยกเว้น นี่อาจเป็นพฤติกรรมที่ไม่พึงประสงค์ หากคุณรันโค้ดที่ไม่น่าเชื่อถือ การใช้เมธอด setExceptionFilter
สามารถกำหนดให้สามารถเรียกได้ ซึ่งอาจแปลงข้อยกเว้น PHP เป็นค่าอื่นที่ปลอดภัยในการเปิดเผย ตัวกรองอาจตัดสินใจว่าจะไม่เผยแพร่ข้อยกเว้นไปยัง JS เลยโดยการโยนข้อยกเว้นที่ส่งผ่านอีกครั้งหรือส่งข้อยกเว้นอื่น