type PhpNullish = null | undefined
type PhpInput = {} | PhpNullish
type NumericLike = number | bigint | string
type PhpList<T = PhpInput> = T[]
type PhpAssoc<T = PhpInput> = { [key: string]: T }
type PhpArrayLike<T = PhpInput> = PhpList<T> | PhpAssoc<T>
const entriesOfPhpAssoc = <T>(value: PhpAssoc<T>): Array<[string, T]> => { return Object.entries(value) }
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>
type PhpKeyComparatorDescriptor = PhpCallableDescriptor<[string, string], NumericLike>
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' }
function isPhpCallableDescriptor<TArgs extends PhpCallableArgs = PhpCallableArgs, TResult = PhpInput>( value: PhpInput, ): value is PhpCallableDescriptor<TArgs, TResult> { if (typeof value === 'string') { return true }
if (isPhpCallable<TArgs, TResult>(value)) { return true }
if (!Array.isArray(value) || value.length < 2) { return false }
const callableDescriptor = value[1] return typeof callableDescriptor === 'string' || isPhpCallable<TArgs, TResult>(callableDescriptor) }
function isPhpArrayObject<T = PhpInput>(value: PhpInput): value is PhpAssoc<T> { return isObjectLike(value) }
function toPhpArrayObject<T = PhpInput>(value: PhpInput): PhpAssoc<T> { if (isPhpArrayObject<T>(value)) { return value }
return {} }
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) }
function resolveNumericComparator< TLeft extends CallbackValue = CallbackValue, TRight extends CallbackValue = CallbackValue, >( callback: PhpCallableDescriptor<[TLeft, TRight], NumericLike>, invalidMessage: string, ): (left: TLeft, right: TRight) => number { const resolved = resolvePhpCallable<[TLeft, TRight], NumericLike>(callback, { invalidMessage })
return (left: TLeft, right: TRight): number => Number(resolved.fn.apply(resolved.scope, [left, right])) }
type DiffArray<T> = PhpAssoc<T> | T[]
function array_diff_ukey<T>( arr1: PhpAssoc<T>, ...arraysAndCallback: [arr2: DiffArray<T>, ...rest: Array<DiffArray<T>>, callback: PhpKeyComparatorDescriptor] ): PhpAssoc<T> {
const retArr: PhpAssoc<T> = {} const callback = arraysAndCallback.at(-1) if (typeof callback === 'undefined' || !isPhpCallableDescriptor<[string, string]>(callback)) { throw new Error('array_diff_ukey(): Invalid callback') } const arrays = arraysAndCallback.slice(0, -1).map((value) => toPhpArrayObject<T>(value)) const keyComparator = resolveNumericComparator<string, string>(callback, 'array_diff_ukey(): Invalid callback')
arr1keys: for (const [k1, arr1Value] of entriesOfPhpAssoc(arr1)) { for (const arr of arrays) { for (const [k] of entriesOfPhpAssoc(arr)) { if (keyComparator(k, k1) === 0) { continue arr1keys } } } retArr[k1] = arr1Value }
return retArr }
|