1021 lines
27 KiB
Haxe
Raw Permalink Normal View History

2025-01-22 16:18:30 +01:00
/*
* Copyright (C)2005-2019 Haxe Foundation
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the "Software"),
* to deal in the Software without restriction, including without limitation
* the rights to use, copy, modify, merge, publish, distribute, sublicense,
* and/or sell copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
* DEALINGS IN THE SOFTWARE.
*/
package php;
import haxe.PosInfos;
import haxe.extern.EitherType;
using php.Global;
/**
Various Haxe->PHP compatibility utilities.
You should not use this class directly.
**/
@:keep
@:dox(hide)
class Boot {
/** List of Haxe classes registered by their PHP class names */
@:protected static var aliases = new NativeAssocArray<String>();
/** Cache of HxClass instances */
@:protected static var classes = new NativeAssocArray<HxClass>();
/** List of getters (for Reflect) */
@:protected static var getters = new NativeAssocArray<NativeAssocArray<Bool>>();
/** List of setters (for Reflect) */
@:protected static var setters = new NativeAssocArray<NativeAssocArray<Bool>>();
/** Metadata storage */
@:protected static var meta = new NativeAssocArray<{}>();
/** Cache for closures created of static methods */
@:protected static var staticClosures = new NativeAssocArray<NativeAssocArray<HxClosure>>();
/**
Initialization stuff.
This method is called once before invoking any Haxe-generated user code.
**/
static function __init__() {
Global.mb_internal_encoding('UTF-8');
if (!Global.defined('HAXE_CUSTOM_ERROR_HANDLER') || !Const.HAXE_CUSTOM_ERROR_HANDLER) {
var previousLevel = Global.error_reporting(Const.E_ALL & ~Const.E_DEPRECATED);
var previousHandler = Global.set_error_handler(function(errno:Int, errstr:String, errfile:String, errline:Int) {
if (Global.error_reporting() & errno == 0) {
return false;
}
/*
* Division by zero should not throw
* @see https://github.com/HaxeFoundation/haxe/issues/7034#issuecomment-394264544
*/
if (errno == Const.E_WARNING && errstr == 'Division by zero') {
return true;
}
throw new ErrorException(errstr, 0, errno, errfile, errline);
});
// Already had user-defined handler. Return it.
if (previousHandler != null) {
Global.error_reporting(previousLevel);
Global.set_error_handler(previousHandler);
}
}
}
/**
Returns root namespace based on a value of `-D php-prefix=value` compiler flag.
Returns empty string if no `-D php-prefix=value` provided.
**/
public static function getPrefix():String {
return Syntax.code('self::PHP_PREFIX');
}
/**
Register list of getters to be able to call getters using reflection
**/
public static function registerGetters(phpClassName:String, list:NativeAssocArray<Bool>):Void {
getters[phpClassName] = list;
}
/**
Register list of setters to be able to call getters using reflection
**/
public static function registerSetters(phpClassName:String, list:NativeAssocArray<Bool>):Void {
setters[phpClassName] = list;
}
/**
Check if specified property has getter
**/
public static function hasGetter(phpClassName:String, property:String):Bool {
if(!ensureLoaded(phpClassName))
return false;
var has = false;
var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName;
do {
has = Global.isset(getters[phpClassName][property]);
phpClassName = Global.get_parent_class(phpClassName);
} while (!has && phpClassName != false && Global.class_exists(phpClassName));
return has;
}
/**
Check if specified property has setter
**/
public static function hasSetter(phpClassName:String, property:String):Bool {
if(!ensureLoaded(phpClassName))
return false;
var has = false;
var phpClassName:haxe.extern.EitherType<Bool, String> = phpClassName;
do {
has = Global.isset(setters[phpClassName][property]);
phpClassName = Global.get_parent_class(phpClassName);
} while (!has && phpClassName != false && Global.class_exists(phpClassName));
return has;
}
/**
Save metadata for specified class
**/
public static function registerMeta(phpClassName:String, data:Dynamic):Void {
meta[phpClassName] = data;
}
/**
Retrieve metadata for specified class
**/
public static function getMeta(phpClassName:String):Null<Dynamic> {
if(!ensureLoaded(phpClassName)) return null;
return Global.isset(meta[phpClassName]) ? meta[phpClassName] : null;
}
/**
Associate PHP class name with Haxe class name
**/
public static function registerClass(phpClassName:String, haxeClassName:String):Void {
aliases[phpClassName] = haxeClassName;
}
/**
Returns a list of currently loaded haxe-generated classes.
**/
public static function getRegisteredClasses():Array<Class<Dynamic>> {
var result = [];
Syntax.foreach(aliases, function(phpName, haxeName) {
result.push(cast getClass(phpName));
});
return result;
}
/**
Returns a list of phpName=>haxeName for currently loaded haxe-generated classes.
**/
public static function getRegisteredAliases():NativeAssocArray<String> {
return aliases;
}
/**
Get Class<T> instance for PHP fully qualified class name (E.g. '\some\pack\MyClass')
It's always the same instance for the same `phpClassName`
**/
public static function getClass(phpClassName:String):HxClass {
if (phpClassName.charAt(0) == '\\') {
phpClassName = phpClassName.substr(1);
}
if (!Global.isset(classes[phpClassName])) {
classes[phpClassName] = new HxClass(phpClassName);
}
return classes[phpClassName];
}
/**
Returns Class<HxAnon>
**/
public static inline function getHxAnon():HxClass {
return cast HxAnon;
}
/**
Check if provided value is an anonymous object
**/
public static inline function isAnon(v:Any):Bool {
return Std.isOfType(v, HxAnon);
}
/**
Returns Class<HxClass>
**/
public static inline function getHxClass():HxClass {
return cast HxClass;
}
/**
Returns either Haxe class name for specified `phpClassName` or (if no such Haxe class registered) `phpClassName`.
**/
public static function getClassName(phpClassName:String):String {
var hxClass = getClass(phpClassName);
var name = getHaxeName(hxClass);
return (name == null ? hxClass.phpClassName : name);
}
/**
Returns original Haxe fully qualified class name for this type (if exists)
**/
public static function getHaxeName(hxClass:HxClass):Null<String> {
switch (hxClass.phpClassName) {
case 'Int':
return 'Int';
case 'String':
return 'String';
case 'Bool':
return 'Bool';
case 'Float':
return 'Float';
case 'Class':
return 'Class';
case 'Enum':
return 'Enum';
case 'Dynamic':
return 'Dynamic';
case _:
}
inline function exists()
return Global.isset(aliases[hxClass.phpClassName]);
if (exists()) {
return aliases[hxClass.phpClassName];
} else if (Global.class_exists(hxClass.phpClassName) && exists()) {
return aliases[hxClass.phpClassName];
} else if (Global.interface_exists(hxClass.phpClassName) && exists()) {
return aliases[hxClass.phpClassName];
}
return null;
}
/**
Find corresponding PHP class name.
Returns `null` if specified class does not exist.
**/
public static function getPhpName(haxeName:String):Null<String> {
var prefix = getPrefix();
var phpParts = (Global.strlen(prefix) == 0 ? [] : [prefix]);
var haxeParts = haxeName.split('.');
for (part in haxeParts) {
if (isPhpKeyword(part)) {
part += '_hx';
}
phpParts.push(part);
}
return phpParts.join('\\');
}
/**
Check if the value of `str` is a reserved keyword in PHP
@see https://www.php.net/manual/en/reserved.keywords.php
**/
@:pure(false)
static public function isPhpKeyword(str:String):Bool {
//The body of this method is generated by the compiler
return false;
}
/**
Unsafe cast to HxClosure
**/
public static inline function castClosure(value:Dynamic):HxClosure {
return value;
}
/**
Unsafe cast to HxClass
**/
public static inline function castClass(cls:Class<Dynamic>):HxClass {
return cast cls;
}
/**
Unsafe cast to HxEnum
**/
public static inline function castEnumValue(enm:EnumValue):HxEnum {
return cast enm;
}
/**
Returns `Class<T>` for `HxClosure`
**/
public static inline function closureHxClass():HxClass {
return cast HxClosure;
}
/**
Implementation for `cast(value, Class<Dynamic>)`
@throws haxe.ValueError if `value` cannot be casted to this type
**/
public static function typedCast(hxClass:HxClass, value:Dynamic):Dynamic {
if (value == null)
return null;
switch (hxClass.phpClassName) {
case 'Int':
if (Boot.isNumber(value)) {
return Global.intval(value);
}
case 'Float':
if (Boot.isNumber(value)) {
return value.floatval();
}
case 'Bool':
if (value.is_bool()) {
return value;
}
case 'String':
if (value.is_string()) {
return value;
}
case 'array':
if (value.is_array()) {
return value;
}
case _:
if (value.is_object() && Std.isOfType(value, cast hxClass)) {
return value;
}
}
throw 'Cannot cast ' + Std.string(value) + ' to ' + getClassName(hxClass.phpClassName);
}
/**
Returns string representation of `value`
**/
public static function stringify(value:Dynamic, maxRecursion:Int = 10):String {
if (maxRecursion <= 0) {
return '<...>';
}
if (value == null) {
return 'null';
}
if (value.is_string()) {
return value;
}
if (value.is_int() || value.is_float()) {
return Syntax.string(value);
}
if (value.is_bool()) {
return value ? 'true' : 'false';
}
if (value.is_array()) {
var strings = Syntax.arrayDecl();
Syntax.foreach(value, function(key:Dynamic, item:Dynamic) {
strings.push(Syntax.string(key) + ' => ' + stringify(item, maxRecursion - 1));
});
return '[' + Global.implode(', ', strings) + ']';
}
if (value.is_object()) {
if (Std.isOfType(value, Array)) {
return inline stringifyNativeIndexedArray(value.arr, maxRecursion - 1);
}
if (Std.isOfType(value, HxEnum)) {
var e:HxEnum = value;
var result = e.tag;
if (Global.count(e.params) > 0) {
var strings = Global.array_map(function(item) return Boot.stringify(item, maxRecursion - 1), e.params);
result += '(' + Global.implode(',', strings) + ')';
}
return result;
}
if (value.method_exists('toString')) {
return value.toString();
}
if (value.method_exists('__toString')) {
return value.__toString();
}
if (Std.isOfType(value, StdClass)) {
if (Global.isset(Syntax.field(value, 'toString')) && value.toString.is_callable()) {
return value.toString();
}
var result = new NativeIndexedArray<String>();
var data = Global.get_object_vars(value);
for (key in data.array_keys()) {
result.array_push('$key : ' + stringify(data[key], maxRecursion - 1));
}
return '{ ' + Global.implode(', ', result) + ' }';
}
if (isFunction(value)) {
return '<function>';
}
if (Std.isOfType(value, HxClass)) {
return '[class ' + getClassName((value : HxClass).phpClassName) + ']';
} else {
return '[object ' + getClassName(Global.get_class(value)) + ']';
}
}
throw "Unable to stringify value";
}
static public function stringifyNativeIndexedArray<T>(arr:NativeIndexedArray<T>, maxRecursion:Int = 10):String {
var strings = Syntax.arrayDecl();
Syntax.foreach(arr, function(index:Int, value:T) {
strings[index] = Boot.stringify(value, maxRecursion - 1);
});
return '[' + Global.implode(',', strings) + ']';
}
static public inline function isNumber(value:Dynamic) {
return value.is_int() || value.is_float();
}
/**
Check if specified values are equal
**/
public static function equal(left:Dynamic, right:Dynamic):Bool {
if (isNumber(left) && isNumber(right)) {
return Syntax.equal(left, right);
}
if (Std.isOfType(left, HxClosure) && Std.isOfType(right, HxClosure)) {
return (left : HxClosure).equals(right);
}
return Syntax.strictEqual(left, right);
}
/**
Concat `left` and `right` if both are strings or string and null.
Otherwise return sum of `left` and `right`.
**/
public static function addOrConcat(left:Dynamic, right:Dynamic):Dynamic {
if (left.is_string() || right.is_string()) {
return (left : String) + (right : String);
}
return Syntax.add(left, right);
}
@:deprecated('php.Boot.is() is deprecated. Use php.Boot.isOfType() instead')
public static inline function is(value:Dynamic, type:HxClass):Bool {
return isOfType(value, type);
}
/**
`Std.isOfType()` implementation
**/
public static function isOfType(value:Dynamic, type:HxClass):Bool {
if (type == null)
return false;
var phpType = type.phpClassName;
#if php_prefix
var prefix = getPrefix();
if (Global.substr(phpType, 0, Global.strlen(prefix) + 1) == '$prefix\\') {
phpType = Global.substr(phpType, Global.strlen(prefix) + 1);
}
#end
switch (phpType) {
case 'Dynamic':
return value != null;
case 'Int':
return (value.is_int() || (value.is_float() && Syntax.equal(Syntax.int(value), value) && !Global.is_nan(value)))
&& Global.abs(value) <= 2147483648;
case 'Float':
return value.is_float() || value.is_int();
case 'Bool':
return value.is_bool();
case 'String':
return value.is_string();
case 'php\\NativeArray', 'php\\_NativeArray\\NativeArray_Impl_':
return value.is_array();
case 'Enum' | 'Class':
if (Std.isOfType(value, HxClass)) {
var valuePhpClass = (cast value : HxClass).phpClassName;
var enumPhpClass = (cast HxEnum : HxClass).phpClassName;
var isEnumType = Global.is_subclass_of(valuePhpClass, enumPhpClass);
return (phpType == 'Enum' ? isEnumType : !isEnumType);
}
case _:
if (value.is_object()) {
var type:Class<Dynamic> = cast type;
return Syntax.instanceof(value, type);
}
}
return false;
}
/**
Check if `value` is a `Class<T>`
**/
public static inline function isClass(value:Dynamic):Bool {
return Std.isOfType(value, HxClass);
}
/**
Check if `value` is an enum constructor instance
**/
public static inline function isEnumValue(value:Dynamic):Bool {
return Std.isOfType(value, HxEnum);
}
/**
Check if `value` is a function
**/
public static inline function isFunction(value:Dynamic):Bool {
return Std.isOfType(value, Closure) || Std.isOfType(value, HxClosure);
}
/**
Check if `value` is an instance of `HxClosure`
**/
public static inline function isHxClosure(value:Dynamic):Bool {
return Std.isOfType(value, HxClosure);
}
/**
Performs `left >>> right` operation
**/
public static function shiftRightUnsigned(left:Int, right:Int):Int {
if (right == 0) {
return left;
} else if (left >= 0) {
return (left >> right) & ~((1 << (8 * Const.PHP_INT_SIZE - 1)) >> (right - 1));
} else {
return (left >> right) & (0x7fffffff >> (right - 1));
}
}
/**
Helper method to avoid "Cannot use temporary expression in write context" error for expressions like this:
```haxe
(new MyClass()).fieldName = 'value';
```
**/
static public function deref(value:Dynamic):Dynamic {
return value;
}
/**
Create Haxe-compatible anonymous structure of `data` associative array
**/
static public function createAnon(data:NativeArray):Dynamic {
var o = new HxAnon();
Syntax.foreach(data, (field:String, value:Any) -> Syntax.setField(o, field, value));
return o;
}
/**
Make sure specified class is loaded
**/
static public inline function ensureLoaded(phpClassName:String):Bool {
return Global.class_exists(phpClassName) || Global.interface_exists(phpClassName);
}
/**
Get `field` of a dynamic `value` in a safe manner (avoid exceptions on trying to get a method)
**/
static public function dynamicField(value:Dynamic, field:String):Dynamic {
if (Global.method_exists(value, field)) {
return closure(value, field);
}
if (Global.is_string(value)) {
value = @:privateAccess new HxDynamicStr(value);
}
return Syntax.field(value, field);
}
public static function dynamicString(str:String):HxDynamicStr {
return @:privateAccess new HxDynamicStr(str);
}
/**
Creates Haxe-compatible closure of an instance method.
@param obj - any object
**/
public static function getInstanceClosure(obj:{?__hx_closureCache:NativeAssocArray<HxClosure>}, methodName:String):Null<HxClosure> {
var result = Syntax.coalesce(obj.__hx_closureCache[methodName], null);
if (result != null) {
return result;
}
if(!Global.method_exists(obj, methodName) && !Global.isset(Syntax.field(obj, methodName))) {
return null;
}
result = new HxClosure(obj, methodName);
if (!Global.property_exists(obj, '__hx_closureCache')) {
obj.__hx_closureCache = new NativeAssocArray();
}
obj.__hx_closureCache[methodName] = result;
return result;
}
/**
Creates Haxe-compatible closure of a static method.
**/
public static function getStaticClosure(phpClassName:String, methodName:String) {
var result = Syntax.coalesce(staticClosures[phpClassName][methodName], null);
if (result != null) {
return result;
}
result = new HxClosure(phpClassName, methodName);
if (!Global.array_key_exists(phpClassName, staticClosures)) {
staticClosures[phpClassName] = new NativeAssocArray();
}
staticClosures[phpClassName][methodName] = result;
return result;
}
/**
Creates Haxe-compatible closure.
@param type `this` for instance methods; full php class name for static methods
@param func Method name
**/
public static inline function closure(target:Dynamic, func:String):HxClosure {
return target.is_string() ? getStaticClosure(target, func) : getInstanceClosure(target, func);
}
/**
Get UTF-8 code of the first character in `s` without any checks
**/
static public inline function unsafeOrd(s:NativeString):Int {
var code = Global.ord(s[0]);
if (code < 0xC0) {
return code;
} else if (code < 0xE0) {
return ((code - 0xC0) << 6) + Global.ord(s[1]) - 0x80;
} else if (code < 0xF0) {
return ((code - 0xE0) << 12) + ((Global.ord(s[1]) - 0x80) << 6) + Global.ord(s[2]) - 0x80;
} else {
return ((code - 0xF0) << 18) + ((Global.ord(s[1]) - 0x80) << 12) + ((Global.ord(s[2]) - 0x80) << 6) + Global.ord(s[3]) - 0x80;
}
}
static public function divByZero(value:Float):Float {
return value == 0 ? Const.NAN : (value < 0 ? -Const.INF : Const.INF);
}
}
/**
Class<T> implementation for Haxe->PHP internals.
**/
@:keep
@:dox(hide)
private class HxClass {
public var phpClassName(default, null):String;
public function new(phpClassName:String):Void {
this.phpClassName = phpClassName;
}
/**
Magic method to call static methods of this class, when `HxClass` instance is in a `Dynamic` variable.
**/
@:phpMagic
function __call(method:String, args:NativeArray):Dynamic {
var callback = (phpClassName == 'String' ? Syntax.nativeClassName(HxString) : phpClassName) + '::' + method;
return Global.call_user_func_array(callback, args);
}
/**
Magic method to get static vars of this class, when `HxClass` instance is in a `Dynamic` variable.
**/
@:phpMagic
function __get(property:String):Dynamic {
if (Global.defined('$phpClassName::$property')) {
return Global.constant('$phpClassName::$property');
} else if (Boot.hasGetter(phpClassName, property)) {
return Syntax.staticCall(phpClassName, 'get_$property');
} else if (phpClassName.method_exists(property)) {
return Boot.getStaticClosure(phpClassName, property);
} else {
return Syntax.getStaticField(phpClassName, property);
}
}
/**
Magic method to set static vars of this class, when `HxClass` instance is in a `Dynamic` variable.
**/
@:phpMagic
function __set(property:String, value:Dynamic):Void {
if (Boot.hasSetter(phpClassName, property)) {
Syntax.staticCall(phpClassName, 'set_$property', value);
} else {
Syntax.setStaticField(phpClassName, property, value);
}
}
}
/**
Base class for enum types
**/
@:keep
@:dox(hide)
@:allow(php.Boot.stringify)
@:allow(Type)
private class HxEnum {
final tag:String;
final index:Int;
final params:NativeArray;
public function new(tag:String, index:Int, arguments:NativeArray = null):Void {
this.tag = tag;
this.index = index;
params = (arguments == null ? new NativeArray() : arguments);
}
/**
Get string representation of this `Class`
**/
public function toString():String {
return __toString();
}
/**
PHP magic method to get string representation of this `Class`
**/
@:phpMagic
public function __toString():String {
return Boot.stringify(this);
}
extern public static function __hx__list():Array<String>;
}
/**
`String` implementation
**/
@:keep
@:dox(hide)
private class HxString {
public static function toUpperCase(str:String):String {
return Global.mb_strtoupper(str);
}
public static function toLowerCase(str:String):String {
return Global.mb_strtolower(str);
}
public static function charAt(str:String, index:Int):String {
return index < 0 ? '' : Global.mb_substr(str, index, 1);
}
public static function charCodeAt(str:String, index:Int):Null<Int> {
if (index < 0 || str == '') {
return null;
}
if (index == 0) {
return Boot.unsafeOrd(str);
}
var char = Global.mb_substr(str, index, 1);
return char == '' ? null : Boot.unsafeOrd(char);
}
public static function indexOf(str:String, search:String, startIndex:Int = null):Int {
if (startIndex == null) {
startIndex = 0;
} else {
var length = str.length;
if (startIndex < 0) {
startIndex += length;
if (startIndex < 0) {
startIndex = 0;
}
}
if (startIndex >= length && search != '') {
return -1;
}
}
var index:EitherType<Int, Bool> = if (search == '') {
var length = str.length;
startIndex > length ? length : startIndex;
} else {
Global.mb_strpos(str, search, startIndex);
}
return (index == false ? -1 : index);
}
public static function lastIndexOf(str:String, search:String, startIndex:Int = null):Int {
var start = startIndex;
if (start == null) {
start = 0;
} else {
var length = str.length;
if (start >= 0) {
start = start - length;
if (start > 0) {
start = 0;
}
} else if (start < -length) {
start = -length;
}
}
var index:EitherType<Int, Bool> = if (search == '') {
var length = str.length;
startIndex == null || startIndex > length ? length : startIndex;
} else {
Global.mb_strrpos(str, search, start);
}
if (index == false) {
return -1;
} else {
return index;
}
}
public static function split(str:String, delimiter:String):Array<String> {
var arr:NativeArray = if (delimiter == '') {
Global.preg_split('//u', str, -1, Const.PREG_SPLIT_NO_EMPTY);
} else {
delimiter = Global.preg_quote(delimiter, '/');
Global.preg_split('/$delimiter/', str);
}
return @:privateAccess Array.wrap(arr);
}
public static function substr(str:String, pos:Int, ?len:Int):String {
return Global.mb_substr(str, pos, len);
}
public static function substring(str:String, startIndex:Int, ?endIndex:Int):String {
if (endIndex == null) {
if (startIndex < 0) {
startIndex = 0;
}
return Global.mb_substr(str, startIndex);
}
if (endIndex < 0) {
endIndex = 0;
}
if (startIndex < 0) {
startIndex = 0;
}
if (startIndex > endIndex) {
var tmp = endIndex;
endIndex = startIndex;
startIndex = tmp;
}
return Global.mb_substr(str, startIndex, endIndex - startIndex);
}
public static function toString(str:String):String {
return str;
}
public static function fromCharCode(code:Int):String {
return Global.mb_chr(code);
}
}
/**
For Dynamic access which looks like String.
Instances of this class should not be saved anywhere.
Instead it should be used to immediately invoke a String field right after instance creation one time only.
**/
@:dox(hide)
@:keep
private class HxDynamicStr extends HxClosure {
static var hxString:String = (cast HxString : HxClass).phpClassName;
/**
Returns HxDynamicStr instance if `value` is a string.
Otherwise returns `value` as-is.
**/
static function wrap(value:Dynamic):Dynamic {
if (value.is_string()) {
return new HxDynamicStr(value);
} else {
return value;
}
}
static inline function invoke(str:String, method:String, args:NativeArray):Dynamic {
Global.array_unshift(args, str);
return Global.call_user_func_array(hxString + '::' + method, args);
}
function new(str:String) {
super(str, null);
}
@:phpMagic
function __get(field:String):Dynamic {
switch (field) {
case 'length':
return (target : String).length;
case _:
func = field;
return this;
}
}
@:phpMagic
function __call(method:String, args:NativeArray):Dynamic {
return invoke(target, method, args);
}
/**
@see http://php.net/manual/en/language.oop5.magic.php#object.invoke
**/
@:phpMagic
override public function __invoke() {
return invoke(target, func, Global.func_get_args());
}
/**
Generates callable value for PHP
**/
override public function getCallback(eThis:Dynamic = null):NativeIndexedArray<Dynamic> {
if (eThis == null) {
return Syntax.arrayDecl((this : Dynamic), func);
}
return Syntax.arrayDecl((new HxDynamicStr(eThis) : Dynamic), func);
}
/**
Invoke this closure with `newThis` instead of `this`
**/
override public function callWith(newThis:Dynamic, args:NativeArray):Dynamic {
if (newThis == null) {
newThis = target;
}
return invoke(newThis, func, args);
}
}
/**
Anonymous objects implementation
**/
@:keep
@:dox(hide)
private class HxAnon extends StdClass {
@:phpMagic
function __get(name:String) {
return null;
}
@:phpMagic
function __call(name:String, args:NativeArray):Dynamic {
return Syntax.code("($this->{0})(...{1})", name, args);
}
}
/**
Closures implementation
**/
@:keep
@:dox(hide)
private class HxClosure {
/** `this` for instance methods; php class name for static methods */
var target:Dynamic;
/** Method name for methods */
var func:String;
/** A callable value, which can be invoked by PHP */
var callable:Any;
public function new(target:Dynamic, func:String):Void {
this.target = target;
this.func = func;
// Force runtime error if trying to create a closure of an instance which happen to be `null`
if (target.is_null()) {
throw "Unable to create closure on `null`";
}
callable = Std.isOfType(target, HxAnon) ? Syntax.field(target, func) : Syntax.arrayDecl(target, func);
}
/**
@see http://php.net/manual/en/language.oop5.magic.php#object.invoke
**/
@:phpMagic
public function __invoke() {
return Global.call_user_func_array(callable, Global.func_get_args());
}
/**
Generates callable value for PHP
**/
public function getCallback(eThis:Dynamic = null):NativeIndexedArray<Dynamic> {
if (eThis == null) {
eThis = target;
}
if (Std.isOfType(eThis, HxAnon)) {
return Syntax.field(eThis, func);
}
return Syntax.arrayDecl(eThis, func);
}
/**
Check if this is the same closure
**/
public function equals(closure:HxClosure):Bool {
return (target == closure.target && func == closure.func);
}
/**
Invoke this closure with `newThis` instead of `this`
**/
public function callWith(newThis:Dynamic, args:NativeArray):Dynamic {
return Global.call_user_func_array(getCallback(newThis), args);
}
}