edde-framework/edde-framework

View on GitHub
src/Edde/Common/Reflection/ReflectionUtils.php

Summary

Maintainability
B
4 hrs
Test Coverage
<?php
    declare(strict_types=1);

    namespace Edde\Common\Reflection;

    use Closure;
    use Edde\Api\Reflection\ReflectionException;
    use Edde\Common\Object;
    use ReflectionClass;
    use ReflectionFunction;
    use ReflectionMethod;

    /**
     * Set of tools for simplier reflection manipulation.
     */
    class ReflectionUtils extends Object {
        /**
         * @var \ReflectionProperty[]|ReflectionClass[]|ReflectionMethod[]
         */
        static protected $cache;

        static public function getReflectionClass($class): ReflectionClass {
            if (isset(self::$cache[$cacheId = 'class/' . (is_object($class) ? get_class($class) : $class)]) === false) {
                self::$cache[$cacheId] = new ReflectionClass($class);
            }
            return self::$cache[$cacheId];
        }

        /**
         * bypass property visibility and set a given value
         *
         * @param $object
         * @param $property
         * @param $value
         *
         * @throws ReflectionException
         */
        static public function setProperty($object, string $property, $value) {
            try {
                if (isset(self::$cache[$cacheId = 'property/' . (is_object($object) ? get_class($object) : $object) . $property]) === false) {
                    $reflectionClass = new ReflectionClass($object);
                    self::$cache[$cacheId] = $reflectionProperty = $reflectionClass->getProperty($property);
                    $reflectionProperty->setAccessible(true);
                }
                self::$cache[$cacheId]->setValue($object, $value);
            } catch (\ReflectionException $exception) {
                throw new ReflectionException(sprintf('Property [%s::$%s] does not exists.', get_class($object), $property));
            }
        }

        /**
         * bypass visibility and reads the given property of the given object
         *
         * @param $object
         * @param $property
         *
         * @return mixed
         * @throws ReflectionException
         */
        static public function getProperty($object, string $property) {
            try {
                if (isset(self::$cache[$cacheId = 'property/' . (is_object($object) ? get_class($object) : $object) . $property]) === false) {
                    $reflectionClass = new ReflectionClass($object);
                    self::$cache[$cacheId] = $reflectionProperty = $reflectionClass->getProperty($property);
                    $reflectionProperty->setAccessible(true);
                }
                return self::$cache[$cacheId]->getValue($object);
            } catch (\ReflectionException $exception) {
                throw new ReflectionException(sprintf('Property [%s::$%s] does not exists.', get_class($object), $property));
            }
        }

        /**
         * @param string|array|callable $callback
         *
         * @return ReflectionFunction|ReflectionMethod
         *
         * @throws ReflectionException
         */
        static public function getMethodReflection($callback) {
            if (is_string($callback) && class_exists($callback)) {
                $reflectionClass = self::getReflectionClass($callback);
                if ($constructor = $reflectionClass->getConstructor()) {
                    return $constructor;
                }
                return new ReflectionFunction(function () {
                });
            } else if ((is_string($callback) && strpos($callback, '::') === false) || $callback instanceof Closure) {
                return new ReflectionFunction($callback);
            } else if (is_array($callback) || (is_string($callback) && strpos($callback, '::') !== false)) {
                list($class, $method) = is_array($callback) ? $callback : explode('::', $callback);
                return new ReflectionMethod($class, $method);
            }
            throw new ReflectionException('Cannot get reflection method from the given input.');
        }

        /**
         * @param callable|string $callback
         *
         * @return \ReflectionParameter[]
         */
        static public function getParameterList($callback): array {
            $parameterList = [];
            $reflection = ReflectionUtils::getMethodReflection($callback);
            foreach ($reflection->getParameters() as $reflectionParameter) {
                $parameterList[$reflectionParameter->getName()] = $reflectionParameter;
            }
            return $parameterList;
        }

        /**
         * @param mixed    $class
         * @param int|null $filter
         *
         * @return ReflectionMethod[]
         */
        static public function getMethodList($class, int $filter = null): array {
            if (isset(self::$cache[$cacheId = 'method-list/' . $filter . '/' . (is_object($class) ? get_class($class) : $class)]) === false) {
                $reflectionClass = self::getReflectionClass($class);
                self::$cache[$cacheId] = $filter ? $reflectionClass->getMethods($filter) : $reflectionClass->getMethods();
            }
            return self::$cache[$cacheId];
        }

        /**
         * as usual, if there are two lambdas on the same line, this will fail
         *
         * @param callable $callable
         *
         * @return string
         */
        static public function getMethodHash(callable $callable): string {
            $reflectionMethod = self::getMethodReflection($callable);
            return sha1($reflectionMethod->getStartLine() . $reflectionMethod->getEndLine() . $reflectionMethod->getName());
        }
    }