4

如何在不实际创建实例的情况下检查对象是否会使用给定参数成功实例化?

实际上我只是检查(没有测试此代码,但应该可以正常工作......)所需参数的数量,忽略类型:

// Filter definition and arguments as per configuration
$filter = $container->getDefinition($serviceId);
$args   = $activeFilters[$filterName];

// Check number of required arguments vs arguments in config
$constructor = $reflector->getConstructor();

$numRequired  = $constructor->getNumberOfRequiredParameters();
$numSpecified = is_array($args) ? count($args) : 1;

if($numRequired < $numSpecified) {
    throw new InvalidFilterDefinitionException(
        $serviceId,
        $numRequired,
        $numSpecified
    );
}

编辑$constructor可以null...

4

1 回答 1

5

简短的回答是,您根本无法确定一组参数是否允许构造函数的无错误实例化。正如评论者在上面提到的那样,没有办法确定一个类是否可以用给定的参数列表实例化,因为如果不实际尝试实例化,就无法知道运行时注意事项。

但是,尝试从构造函数参数列表中实例化一个类是有价值的。此类操作最明显的用例是可配置的依赖注入容器 (DIC)。不幸的是,这比 OP 建议的要复杂得多。

我们需要为提供的定义数组中的每个参数确定它是否与构造函数方法签名中指定的类型提示匹配(如果方法签名实际上具有类型提示)。此外,我们需要解决如何处理默认参数值。此外,为了使我们的代码真正有用,我们需要提前允许“定义”的规范来实例化一个类。对该问题的复杂处理还将涉及反射对象池(缓存),以最大限度地减少重复反射事物的性能影响。

另一个障碍是,如果不调用反射方法参数的ReflectionParameter::getClass方法并随后从返回的类名实例化反射类(如果null返回,则参数没有类型提示),就无法访​​问反射方法参数的类型提示。这就是缓存生成的反射对于任何现实世界的用例都变得特别重要的地方。

下面的代码是我自己的基于字符串的递归依赖注入容器的严重精简版本。它是伪代码和真实代码的混合体(如果您希望免费代码可以复制/粘贴,那您就太不走运了)。您将看到下面的代码将“定义”数组的关联数组键与构造函数签名中的参数名称相匹配。

真正的代码可以在相关的github 项目页面上找到

class Provider {

    private $definitions;

    public function define($class, array $definition) {
        $class = strtolower($class);
        $this->definitions[$class] = $definition;
    }

    public function make($class, array $definition = null) {
        $class = strtolower($class);

        if (is_null($definition) && isset($this->definitions[$class])) {
            $definition = $this->definitions[$class];
        }

        $reflClass = new ReflectionClass($class);
        $instanceArgs = $this->buildNewInstanceArgs($reflClass);


        return $reflClass->newInstanceArgs($instanceArgs);
    }

    private function buildNewInstanceArgs(
        ReflectionClass $reflClass,
        array $definition
    ) {
        $instanceArgs = array();


        $reflCtor = $reflClass->getConstructor();

        // IF no constructor exists we're done and should just
        // return a new instance of $class:
        // return $this->make($reflClass->name);
        // otherwise ...

        $reflCtorParams = $reflCtor->getParameters();

        foreach ($reflCtorParams as $ctorParam) {
            if (isset($definition[$ctorParam->name])) {
                $instanceArgs[] = $this->make($definition[$ctorParam->name]);
                continue;
            }

            $typeHint = $this->getParameterTypeHint($ctorParam);

            if ($typeHint && $this->isInstantiable($typeHint)) {
                // The typehint is instantiable, go ahead and make a new
                // instance of it
                $instanceArgs[] = $this->make($typeHint);
            } elseif ($typeHint) {
                // The typehint is abstract or an interface. We can't
                // proceed because we already know we don't have a 
                // definition telling us which class to instantiate
                throw Exception;
            } elseif ($ctorParam->isDefaultValueAvailable()) {
                // No typehint, try to use the default parameter value
                $instanceArgs[] = $ctorParam->getDefaultValue();
            } else {
                // If all else fails, try passing in a NULL or something
                $instanceArgs[] = NULL;
            }
        }

        return $instanceArgs;
    }

    private function getParameterTypeHint(ReflectionParameter $param) {
        // ... see the note about retrieving parameter typehints
        // in the exposition ...
    }
    private function isInstantiable($class) {
        // determine if the class typehint is abstract/interface
        // RTM on reflection for how to do this
    }
}
于 2012-11-22T18:23:56.397 回答