$args * @return mixed */ public static function call(object $object, string $method, array $args = []) { $reflectionClass = new ReflectionClass($object); try { $reflectionMethod = $reflectionClass->getMethod($method); $reflectionMethod->setAccessible(true); return $reflectionMethod->invoke($object, ...$args); } catch (ReflectionException $exception) { if (method_exists($object, '__call')) { return $object->__call($method, $args); } if (is_callable($method)) { return self::bindCallable($method, $args); } throw $exception; } } /** * Bind a callable to the TestCase and return the result. * * @param array $args * @return mixed */ public static function bindCallable(callable $callable, array $args = []) { return Closure::fromCallable($callable)->bindTo(TestSuite::getInstance()->test)(...$args); } /** * Bind a callable to the TestCase and return the result, * passing in the current dataset values as arguments. * * @return mixed */ public static function bindCallableWithData(callable $callable) { $test = TestSuite::getInstance()->test; return $test instanceof \PHPUnit\Framework\TestCase ? Closure::fromCallable($callable)->bindTo($test)(...$test->providedData()) : self::bindCallable($callable); } /** * Infers the file name from the given closure. */ public static function getFileNameFromClosure(Closure $closure): string { $reflectionClosure = new ReflectionFunction($closure); return (string) $reflectionClosure->getFileName(); } /** * Gets the property value from of the given object. * * @return mixed */ public static function getPropertyValue(object $object, string $property) { $reflectionClass = new ReflectionClass($object); $reflectionProperty = null; while (! $reflectionProperty instanceof ReflectionProperty) { try { /* @var ReflectionProperty $reflectionProperty */ $reflectionProperty = $reflectionClass->getProperty($property); } catch (ReflectionException $reflectionException) { $reflectionClass = $reflectionClass->getParentClass(); if (! $reflectionClass instanceof ReflectionClass) { throw new ShouldNotHappen($reflectionException); } } } $reflectionProperty->setAccessible(true); return $reflectionProperty->getValue($object); } /** * Sets the property value of the given object. * * @template TValue of object * * @param TValue $object */ public static function setPropertyValue(object $object, string $property, mixed $value): void { /** @var ReflectionClass $reflectionClass */ $reflectionClass = new ReflectionClass($object); $reflectionProperty = null; while (! $reflectionProperty instanceof ReflectionProperty) { try { /* @var ReflectionProperty $reflectionProperty */ $reflectionProperty = $reflectionClass->getProperty($property); } catch (ReflectionException $reflectionException) { $reflectionClass = $reflectionClass->getParentClass(); if (! $reflectionClass instanceof ReflectionClass) { throw new ShouldNotHappen($reflectionException); } } } $reflectionProperty->setAccessible(true); $reflectionProperty->setValue($object, $value); } /** * Get the class name of the given parameter's type, if possible. * * @see https://github.com/laravel/framework/blob/v6.18.25/src/Illuminate/Support/Reflector.php */ public static function getParameterClassName(ReflectionParameter $parameter): ?string { $type = $parameter->getType(); if (! $type instanceof ReflectionNamedType) { return null; } if ($type->isBuiltin()) { return null; } $name = $type->getName(); if (($class = $parameter->getDeclaringClass()) instanceof ReflectionClass) { if ($name === 'self') { return $class->getName(); } if ($name === 'parent' && ($parent = $class->getParentClass()) instanceof ReflectionClass) { return $parent->getName(); } } return $name; } /** * Receive a map of function argument names to their types. * * @return array */ public static function getFunctionArguments(Closure $function): array { $parameters = (new ReflectionFunction($function))->getParameters(); $arguments = []; foreach ($parameters as $parameter) { /** @var ReflectionNamedType|ReflectionUnionType|null $types */ $types = ($parameter->hasType()) ? $parameter->getType() : null; if (is_null($types)) { $arguments[$parameter->getName()] = 'mixed'; continue; } $arguments[$parameter->getName()] = implode('|', array_map( static fn (ReflectionNamedType $type): string => $type->getName(), ($types instanceof ReflectionNamedType) ? [$types] // NOTE: normalize as list of to handle unions : $types->getTypes(), )); } return $arguments; } /** * @return mixed */ public static function getFunctionVariable(Closure $function, string $key) { return (new ReflectionFunction($function))->getStaticVariables()[$key] ?? null; } }