Browse Source

Merge pull request #16309 from cakephp/issue-16307

Fix passedParams with object values
Mark Story 4 years ago
parent
commit
770fcd2398

+ 11 - 2
src/Controller/ControllerFactory.php

@@ -174,8 +174,16 @@ class ControllerFactory implements ControllerFactoryInterface, RequestHandlerInt
 
             // Check for dependency injection for classes
             if ($type instanceof ReflectionNamedType && !$type->isBuiltin()) {
-                if ($this->container->has($type->getName())) {
-                    $resolved[] = $this->container->get($type->getName());
+                $typeName = $type->getName();
+                if ($this->container->has($typeName)) {
+                    $resolved[] = $this->container->get($typeName);
+                    continue;
+                }
+
+                // Use passedParams as a source of typed dependencies.
+                // The accepted types for passedParams was never defined and userland code relies on that.
+                if ($passedParams && is_object($passedParams[0]) && $passedParams[0] instanceof $typeName) {
+                    $resolved[] = array_shift($passedParams);
                     continue;
                 }
 
@@ -189,6 +197,7 @@ class ControllerFactory implements ControllerFactoryInterface, RequestHandlerInt
                 throw new InvalidParameterException([
                     'template' => 'missing_dependency',
                     'parameter' => $parameter->getName(),
+                    'type' => $typeName,
                     'controller' => $this->controller->getName(),
                     'action' => $this->controller->getRequest()->getParam('action'),
                     'prefix' => $this->controller->getRequest()->getParam('prefix'),

+ 2 - 1
src/Controller/Exception/InvalidParameterException.php

@@ -27,7 +27,8 @@ class InvalidParameterException extends CakeException
      */
     protected $templates = [
         'failed_coercion' => 'Unable to coerce "%s" to `%s` for `%s` in action %s::%s().',
-        'missing_dependency' => 'Failed to inject dependency from service container for `%s` in action %s::%s().',
+        'missing_dependency' => 'Failed to inject dependency from service container for parameter `%s` ' .
+            'with type `%s` in action %s::%s().',
         'missing_parameter' => 'Missing passed parameter for `%s` in action %s::%s().',
         'unsupported_type' => 'Type declaration for `%s` in action %s::%s() is unsupported.',
     ];

+ 33 - 4
tests/TestCase/Controller/ControllerFactoryTest.php

@@ -246,7 +246,7 @@ class ControllerFactoryTest extends TestCase
     }
 
     /**
-     * Test create() injecting dependcies on defined controllers.
+     * Test create() injecting dependencies on defined controllers.
      */
     public function testCreateWithContainerDependenciesNoController(): void
     {
@@ -265,7 +265,7 @@ class ControllerFactoryTest extends TestCase
     }
 
     /**
-     * Test create() injecting dependcies on defined controllers.
+     * Test create() injecting dependencies on defined controllers.
      */
     public function testCreateWithContainerDependenciesWithController(): void
     {
@@ -439,7 +439,7 @@ class ControllerFactoryTest extends TestCase
     }
 
     /**
-     * Ensure that a controllers startup process can emit a response
+     * Test invoke passing basic typed data from pass parameters.
      */
     public function testInvokeInjectParametersOptionalWithPassedParameters(): void
     {
@@ -464,6 +464,33 @@ class ControllerFactoryTest extends TestCase
     }
 
     /**
+     * Test invoke() injecting dependencies that exist in passed params as objects.
+     * The accepted types of `params.pass` was never enforced and userland code has
+     * creative uses of this previously unspecified behavior.
+     */
+    public function testCreateWithContainerDependenciesWithObjectRouteParam(): void
+    {
+        $inject = new stdClass();
+        $inject->id = uniqid();
+
+        $request = new ServerRequest([
+            'url' => 'test_plugin_three/dependencies/index',
+            'params' => [
+                'plugin' => null,
+                'controller' => 'Dependencies',
+                'action' => 'requiredDep',
+                'pass' => [$inject],
+            ],
+        ]);
+        $controller = $this->factory->create($request);
+        $response = $this->factory->invoke($controller);
+
+        $data = json_decode((string)$response->getBody());
+        $this->assertNotNull($data);
+        $this->assertEquals($data->dep->id, $inject->id);
+    }
+
+    /**
      * Ensure that a controllers startup process can emit a response
      */
     public function testInvokeInjectParametersRequiredDefined(): void
@@ -503,7 +530,9 @@ class ControllerFactoryTest extends TestCase
         $controller = $this->factory->create($request);
 
         $this->expectException(InvalidParameterException::class);
-        $this->expectExceptionMessage('Failed to inject dependency from service container for `dep` in action Dependencies::requiredDep()');
+        $this->expectExceptionMessage(
+            'Failed to inject dependency from service container for parameter `dep` with type `stdClass` in action Dependencies::requiredDep()'
+        );
         $this->factory->invoke($controller);
     }