type PhpNullish = null | undefined
type PhpInput = {} | PhpNullish
type PhpScalar = string | number | boolean
type PhpPrimitive = PhpScalar | bigint
type PhpList<T = PhpInput> = T[]
type PhpAssoc<T = PhpInput> = { [key: string]: T }
type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
type PhpFunctionValue = (...args: PhpInput[]) => PhpInput
interface PhpRuntimeAssoc { [key: string]: PhpRuntimeValue }
interface PhpRuntimeList extends Array<PhpRuntimeValue> {}
type PhpRuntimeValue = PhpPrimitive | PhpNullish | PhpRuntimeList | PhpRuntimeAssoc | PhpFunctionValue
type PhpCallableArgs = PhpInput[]
type PhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = ( ...args: TArgs ) => TResult
type PhpCallableScope = PhpInput
type PhpCallableTuple<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = readonly [ PhpCallableScope, string | PhpCallable<TArgs, TResult>, ]
type PhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput> = | string | PhpCallable<TArgs, TResult> | PhpCallableTuple<TArgs, TResult>
function isObjectLike(value: PhpInput): value is PhpArrayLike<PhpInput> { return typeof value === 'object' && value !== null }
function isPhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, ): value is PhpCallable<TArgs, TResult> { return typeof value === 'function' }
interface LocutusRuntimeContainer { php?: PhpAssoc<PhpInput> }
interface PhpGlobalProcessLike { env?: PhpAssoc<string | undefined> }
interface PhpGlobalBufferLike { from?: (...args: PhpInput[]) => PhpInput }
interface PhpGlobalKnownEntryMap { process: PhpGlobalProcessLike Buffer: PhpGlobalBufferLike }
type GlobalWithLocutus = { $locutus?: LocutusRuntimeContainer [key: string]: PhpInput }
const globalContext: GlobalWithLocutus = typeof window === 'object' && window !== null ? window : typeof global === 'object' && global !== null ? global : {}
function getPhpGlobalEntry<TKey extends keyof PhpGlobalKnownEntryMap>( key: TKey, ): PhpGlobalKnownEntryMap[TKey] | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined
function getPhpGlobalEntry(key: string): PhpInput | undefined { const value = globalContext[key] return typeof value === 'undefined' ? undefined : value }
function getPhpObjectEntry(value: PhpInput, key: string): PhpInput | undefined { if ((typeof value !== 'object' && typeof value !== 'function') || value === null) { return undefined }
let current: object | null = value while (current) { const descriptor = Object.getOwnPropertyDescriptor(current, key) if (descriptor) { if (typeof descriptor.get === 'function') { const getterValue = descriptor.get.call(value) return typeof getterValue === 'undefined' ? undefined : getterValue } const directValue = descriptor.value return typeof directValue === 'undefined' ? undefined : directValue } current = Object.getPrototypeOf(current) }
return undefined }
type CallbackValue = PhpInput
interface CallbackResolverOptions { invalidMessage: string missingScopeMessage?: (scopeName: string) => string }
interface ResolvedCallback<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue> { fn: PhpCallable<TArgs, TResult> scope: CallbackValue }
function resolvePhpCallable<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = CallbackValue>( callback: PhpCallableDescriptor<TArgs, TResult>, options: CallbackResolverOptions, ): ResolvedCallback<TArgs, TResult> { if (isPhpCallable<TArgs, TResult>(callback)) { return { fn: callback, scope: null } }
if (typeof callback === 'string') { const candidate = getPhpGlobalEntry(callback) if (isPhpCallable<TArgs, TResult>(candidate)) { return { fn: candidate, scope: null } } throw new Error(options.invalidMessage) }
if (Array.isArray(callback) && callback.length >= 2) { const scopeDescriptor = callback[0] const callableDescriptor = callback[1]
let scope: CallbackValue if (typeof scopeDescriptor === 'string') { scope = getPhpGlobalEntry(scopeDescriptor) if (typeof scope === 'undefined' && options.missingScopeMessage) { throw new Error(options.missingScopeMessage(scopeDescriptor)) } } else { scope = scopeDescriptor }
if (isPhpCallable<TArgs, TResult>(callableDescriptor)) { return { fn: callableDescriptor, scope } }
if (typeof callableDescriptor === 'string' && (isObjectLike(scope) || typeof scope === 'function')) { const candidate = getPhpObjectEntry(scope, callableDescriptor) if (isPhpCallable<TArgs, TResult>(candidate)) { return { fn: candidate, scope } } } }
throw new Error(options.invalidMessage) }
type FunctionValue = PhpRuntimeValue
type FunctionScope = FunctionValue | object
function call_user_func_array<TResult = FunctionValue, TArgs extends PhpCallableArgs = PhpCallableArgs>( cb: PhpCallableDescriptor<TArgs, TResult>, parameters: [...TArgs], ): TResult {
let func: PhpCallable<TArgs, TResult> | undefined let scope: FunctionScope = null
try { const resolved = resolvePhpCallable<TArgs, TResult>(cb, { invalidMessage: 'invalid' }) func = resolved.fn scope = resolved.scope } catch { }
if (!func && typeof cb === 'string') { const globalCandidate = getPhpGlobalEntry(cb) if (isPhpCallable<TArgs, TResult>(globalCandidate)) { func = globalCandidate } } else if (!func && Array.isArray(cb)) { if (typeof cb[0] === 'string') { const globalScope = getPhpGlobalEntry(cb[0]) if (isObjectLike(globalScope) || typeof globalScope === 'function') { const method = getPhpObjectEntry(globalScope, String(cb[1])) if (isPhpCallable<TArgs, TResult>(method)) { func = method } scope = globalScope } } else if (isObjectLike(cb[0]) || typeof cb[0] === 'function') { const method = getPhpObjectEntry(cb[0], String(cb[1])) if (isPhpCallable<TArgs, TResult>(method)) { func = method } scope = cb[0] } } else if (!func && isPhpCallable<TArgs, TResult>(cb)) { func = cb }
if (!func) { throw new Error(String(cb) + ' is not a valid function') }
return func.apply(scope, parameters) }
|