V8Js는 Google의 V8 Javascript 엔진을 위한 PHP 확장입니다.
확장 기능을 사용하면 PHP의 보안 샌드박스에서 Javascript 코드를 실행할 수 있습니다. 실행되는 코드는 시간 제한 및/또는 메모리 제한을 사용하여 제한될 수 있습니다. 이를 통해 신뢰할 수 없는 코드를 자신있게 실행할 수 있습니다.
V8 Javascript 엔진 라이브러리(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
이라는 분기가 있습니다.
첫 번째 단계에서는 수동으로 컴파일하는 대신 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 객체에 적용될 때 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에는 IEEE 754 부동 소수점 숫자인 Number
유형만 있습니다. 많은 경우 두 언어가 모두 동일한 숫자를 잘 표현할 수 있다면 이는 전혀 중요하지 않습니다. 그러나 극단적인 경우도 있습니다.
64비트 시스템에서 PHP는 정수가 64개의 유효 비트를 가질 수 있도록 허용하지만 JavaScript 숫자 유형(예: IEEE 754)은 52비트 가수만 갖습니다. 따라서 일부 정밀도가 손실됩니다. 15개 이상의 정확한 십진수 숫자로 정수 값을 전달하는 경우 이는 문제가 되기 시작합니다.
일반적인 이름에도 불구하고 배열의 개념은 PHP와 JavaScript 간에 매우 다릅니다. JavaScript에서 배열은 0부터 위쪽으로 정수로 색인이 지정된 요소의 연속 모음입니다. PHP에서 배열은 희박할 수 있습니다. 즉, 정수 키는 연속적일 필요가 없으며 음수일 수도 있습니다. 게다가 PHP 배열은 정수를 키로 사용할 수 있을 뿐만 아니라 문자열(소위 연관 배열)도 사용할 수 있습니다. 반대 JavaScript 배열을 사용하면 PHP에서 지원하지 않는 속성을 배열에 연결할 수 있습니다. 이러한 속성은 배열 컬렉션의 일부가 아닙니다. 예를 들어 Array.prototype.forEach
메서드는 이러한 속성을 "참조"하지 않습니다.
일반적으로 PHP 배열은 가능한 경우 JavaScript "기본" 배열에 매핑됩니다. 즉, PHP 배열은 0부터 위쪽으로 연속 숫자 키를 사용합니다. 연관 배열과 희소 배열 모두 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
플래그를 설정하여 JavaScript 객체를 항상 PHP 배열에 매핑하도록 executeString
함수를 구성할 수 있습니다. 그런 다음 값이 라이브 바인딩되지 않는다는 표준 배열 동작이 적용됩니다. 즉, 결과 PHP 배열의 값을 변경하는 경우 JavaScript 개체는 영향을 받지 않습니다 .
PHP 객체가 일반적으로 JavaScript 객체로 변환된다는 위의 규칙은 ArrayObject
유형의 PHP 객체나 ArrayAccess
및 Countable
인터페이스를 모두 구현하는 다른 클래스에도 적용됩니다. 그럼에도 불구하고 이들은 PHP 배열처럼 동작합니다.
이 동작은 php.ini 플래그 v8js.use_array_access
활성화하여 변경할 수 있습니다. 설정된 경우 앞서 언급한 인터페이스를 구현하는 PHP 클래스의 개체가 JavaScript 배열과 유사한 개체로 변환됩니다. 이는 이 개체에 대한 인덱스별 액세스이므로 offsetGet
또는 offsetSet
PHP 메서드가 즉시 호출됩니다(효과적으로 이는 PHP 개체에 대한 JavaScript의 라이브 바인딩입니다). 이러한 Array-esque 객체는 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) ' );
그런 다음 로컬 파일 시스템이나 Redis 등 원하는 위치에 $snapshot
의 내용을 유지합니다.
새로운 V8Js 인스턴스를 생성해야 하는 경우 스냅샷을 V8Js 생성자에 5번째 인수로 전달하기만 하면 됩니다.
$ 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에 전혀 전파하지 않기로 결정할 수도 있습니다.