mirror of
				https://github.com/yiisoft/yii2.git
				synced 2025-11-04 06:37:55 +08:00 
			
		
		
		
	Fix #17722: Add action injection support
This commit is contained in:
		@ -9,6 +9,7 @@ Yii Framework 2 Change Log
 | 
			
		||||
- Bug #18026: Fix `ArrayHelper::getValue()` did not work with `ArrayAccess` objects (mikk150)
 | 
			
		||||
- Enh #18048: Use `Instance::ensure()` to set `User::$accessChecker` (lav45)
 | 
			
		||||
- Bug #18051: Fix missing support for custom validation method in EachValidator (bizley)
 | 
			
		||||
- Enh #17722: Add action injection support (SamMousa, samdark)
 | 
			
		||||
- Bug #18041: Fix RBAC migration for MSSQL (darkdef)
 | 
			
		||||
- Bug #18081: Fix for PDO_DBLIB/MSSQL. Set flag ANSI_NULL_DFLT_ON to ON for current connect to DB (darkdef)
 | 
			
		||||
- Bug #13828: Fix retrieving inserted data for a primary key of type uniqueidentifier for SQL Server 2005 or later (darkdef)
 | 
			
		||||
 | 
			
		||||
@ -523,4 +523,34 @@ class Controller extends Component implements ViewContextInterface
 | 
			
		||||
 | 
			
		||||
        return $path;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Fills parameters based on types and names in action method signature.
 | 
			
		||||
     * @param \ReflectionType $type The reflected type of the action parameter.
 | 
			
		||||
     * @param string $name The name of the parameter.
 | 
			
		||||
     * @param array &$args The array of arguments for the action, this function may append items to it.
 | 
			
		||||
     * @param array &$requestedParams The array with requested params, this function may write specific keys to it.
 | 
			
		||||
     * @throws ErrorException when we cannot load a required service.
 | 
			
		||||
     * @throws \yii\base\InvalidConfigException Thrown when there is an error in the DI configuration.
 | 
			
		||||
     * @throws \yii\di\NotInstantiableException Thrown when a definition cannot be resolved to a concrete class
 | 
			
		||||
     * (for example an interface type hint) without a proper definition in the container.
 | 
			
		||||
     * @since 2.0.36
 | 
			
		||||
     */
 | 
			
		||||
    final protected function bindInjectedParams(\ReflectionType $type, $name, &$args, &$requestedParams)
 | 
			
		||||
    {
 | 
			
		||||
        // Since it is not a builtin type it must be DI injection.
 | 
			
		||||
        $typeName = $type->getName();
 | 
			
		||||
        if (($component = $this->module->get($name, false)) instanceof $typeName) {
 | 
			
		||||
            $args[] = $component;
 | 
			
		||||
            $requestedParams[$name] = "Component: " . get_class($component) . " \$$name";
 | 
			
		||||
        } elseif (\Yii::$container->has($typeName) && ($service = \Yii::$container->get($typeName)) instanceof $typeName) {
 | 
			
		||||
            $args[] = $service;
 | 
			
		||||
            $requestedParams[$name] = "DI: $typeName \$$name";
 | 
			
		||||
        } elseif ($type->allowsNull()) {
 | 
			
		||||
            $args[] = null;
 | 
			
		||||
            $requestedParams[$name] = "Unavailable service: $name";
 | 
			
		||||
        } else {
 | 
			
		||||
            throw new Exception('Could not load required service: ' . $name);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -182,19 +182,35 @@ class Controller extends \yii\base\Controller
 | 
			
		||||
            $method = new \ReflectionMethod($action, 'run');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        $args = array_values($params);
 | 
			
		||||
 | 
			
		||||
        $args = [];
 | 
			
		||||
        $missing = [];
 | 
			
		||||
        $actionParams = [];
 | 
			
		||||
        $requestedParams = [];
 | 
			
		||||
        foreach ($method->getParameters() as $i => $param) {
 | 
			
		||||
            if ($param->isArray() && isset($args[$i])) {
 | 
			
		||||
                $args[$i] = $args[$i] === '' ? [] : preg_split('/\s*,\s*/', $args[$i]);
 | 
			
		||||
            $name = $param->getName();
 | 
			
		||||
            $key = null;
 | 
			
		||||
            if (array_key_exists($i, $params)) {
 | 
			
		||||
                $key = $i;
 | 
			
		||||
            } elseif (array_key_exists($name, $params)) {
 | 
			
		||||
                $key = $name;
 | 
			
		||||
            }
 | 
			
		||||
            if (!isset($args[$i])) {
 | 
			
		||||
                if ($param->isDefaultValueAvailable()) {
 | 
			
		||||
                    $args[$i] = $param->getDefaultValue();
 | 
			
		||||
                } else {
 | 
			
		||||
                    $missing[] = $param->getName();
 | 
			
		||||
 | 
			
		||||
            if ($key !== null) {
 | 
			
		||||
                if ($param->isArray()) {
 | 
			
		||||
                    $params[$key] = $params[$key] === '' ? [] : preg_split('/\s*,\s*/', $params[$key]);
 | 
			
		||||
                }
 | 
			
		||||
                $args[] = $actionParams[$key] = $params[$key];
 | 
			
		||||
                unset($params[$key]);
 | 
			
		||||
            } elseif (PHP_VERSION_ID >= 70100 && ($type = $param->getType()) !== null && !$type->isBuiltin()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    $this->bindInjectedParams($type, $name, $args, $requestedParams);
 | 
			
		||||
                } catch (\yii\base\Exception $e) {
 | 
			
		||||
                    throw new Exception($e->getMessage());
 | 
			
		||||
                }
 | 
			
		||||
            } elseif ($param->isDefaultValueAvailable()) {
 | 
			
		||||
                $args[] = $actionParams[$i] = $param->getDefaultValue();
 | 
			
		||||
            } else {
 | 
			
		||||
                $missing[] = $name;
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -202,6 +218,11 @@ class Controller extends \yii\base\Controller
 | 
			
		||||
            throw new Exception(Yii::t('yii', 'Missing required arguments: {params}', ['params' => implode(', ', $missing)]));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // We use a different array here, specifically one that doesn't contain service instances but descriptions instead.
 | 
			
		||||
        if (\Yii::$app->requestedParams === null) {
 | 
			
		||||
            \Yii::$app->requestedParams = array_merge($actionParams, $requestedParams);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $args;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -8,6 +8,8 @@
 | 
			
		||||
namespace yii\web;
 | 
			
		||||
 | 
			
		||||
use Yii;
 | 
			
		||||
use yii\base\ErrorException;
 | 
			
		||||
use yii\base\Exception;
 | 
			
		||||
use yii\base\InlineAction;
 | 
			
		||||
use yii\helpers\Url;
 | 
			
		||||
 | 
			
		||||
@ -125,6 +127,7 @@ class Controller extends \yii\base\Controller
 | 
			
		||||
        $args = [];
 | 
			
		||||
        $missing = [];
 | 
			
		||||
        $actionParams = [];
 | 
			
		||||
        $requestedParams = [];
 | 
			
		||||
        foreach ($method->getParameters() as $param) {
 | 
			
		||||
            $name = $param->getName();
 | 
			
		||||
            if (array_key_exists($name, $params)) {
 | 
			
		||||
@ -162,6 +165,12 @@ class Controller extends \yii\base\Controller
 | 
			
		||||
                }
 | 
			
		||||
                $args[] = $actionParams[$name] = $params[$name];
 | 
			
		||||
                unset($params[$name]);
 | 
			
		||||
            } elseif (PHP_VERSION_ID >= 70100 && ($type = $param->getType()) !== null && !$type->isBuiltin()) {
 | 
			
		||||
                try {
 | 
			
		||||
                    $this->bindInjectedParams($type, $name, $args, $requestedParams);
 | 
			
		||||
                } catch (Exception $e) {
 | 
			
		||||
                    throw new ServerErrorHttpException($e->getMessage(), 0, $e);
 | 
			
		||||
                }
 | 
			
		||||
            } elseif ($param->isDefaultValueAvailable()) {
 | 
			
		||||
                $args[] = $actionParams[$name] = $param->getDefaultValue();
 | 
			
		||||
            } else {
 | 
			
		||||
@ -177,6 +186,11 @@ class Controller extends \yii\base\Controller
 | 
			
		||||
 | 
			
		||||
        $this->actionParams = $actionParams;
 | 
			
		||||
 | 
			
		||||
        // We use a different array here, specifically one that doesn't contain service instances but descriptions instead.
 | 
			
		||||
        if (\Yii::$app->requestedParams === null) {
 | 
			
		||||
            \Yii::$app->requestedParams = array_merge($actionParams, $requestedParams);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return $args;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user