Slim 3 - Container Resolution

class MyAction {
protected $ci;
//Constructor
public function __construct(ContainerInterface $ci) {
$this->ci = $ci;
}

public function __invoke($request, $response, $args) {

    // Return Empty Array.
    $attr = $this->ci->get('request')->getAttributes();
    var_dump($attr);

   // Return Array of Attributes.
   $attr = $request->getAttributes();
   var_dump($attr);
}

}

I don’t understand why the following are not working. ( I used the doc code for exemplification, the real code is not here but the problem is the same )

$attr = $this->ci->get(‘request’)->getAttributes()

The getAttributes method was supposed to return array of attributes about the route but using the ContainerInterface it return an empty array.

Using var_dump($attr) on it this what it shows:

array(0) {
}

Now using var_dump($request->getAttributes()) it shows all the attributes that should show. ( Not going to post here because is a lot of things ).

The point is that I wanted to access a specific attribute with getAttribute() ( $this->ci->get(‘request’)->getAttribute(‘routeInfo’) ) using the ContainerInterface and not the function($request,$response,$args).

What is wrong with my approuch ?

Note:

  • The method getParams() , getBody() works perfectly fine with ContainerInterface.

  • $this->ci->get(‘request’)->getParams() - Works

  • $this->ci->get(‘request’)->getBody() - Works

  • $this->ci->get(‘request’)->getAttributes() - Don’t work

You MUST never use the request and response from the container. These are for internal use only.

That being said. The reason your example won’t work is because the request object is immutable. Getting a new one from the container will not have the attributes you added to the request instance used in the application stack.

I[quote=“JoeBengalen, post:2, topic:200, full:true”]
Getting a new one from the container will not have the attributes you added to the request instance used in the application stack.
[/quote]

It work as expected, I can retrieve the body, params, method, Headers same as using $request. I was thinking that the Container Request/Response Object were the same as the Application one.

There is anyway to retrieve Request and Response object instance from the Application ? The response from the Container worked fine, what is so bad about it ?

I was using Container’s Request and Response Object inside my Controller Class. Using this code as Example MyAction Class were going to extend my Controller class and it has all the methods to use Slim Request and Response object safely.

I wanted my Controller to encapsulate the Request and Response so I can manage better my Requests and Responses without repeating a lot of code.

I gonna put here an example of my code just for clarification, I wanted to know if this is possible to achieve:

class MyAction extends RoutableController{

  // Method that return a json on the response body the same json that was sended on the request body with a nice message.
 public function myMethod1(){
   // ( Success, Payload, Message, Code )
   $this->setResponse(true,$this->getRequest()->getArray(),'Operation Complete',200);
   // Return formated Response using Slim Object.
   return $this->getResponse();     
 }
 public function myMethod2(){
  // Return default: Success = true, Payload = null, Message = '' , Code = 200.
  return $this->getResponse();
}

}

class RoutableController extends ResponseCode{

private $ci;

private $request;

public function __construct(ContainerInterface $ci){

    parent::__construct($ci->get('response'));
    $this->request = $ci->get('request');

}

public function getRequest(){

if ($request->getMethod !== 'GET')
     return new JSONObjectDecoder($request->getBody())
else
   throw new Exception('Get don't have body');

}
}

I wrote something generic that represent what I want to achieve, my Controller parent Class manages the Request and Response Slim Object but I don’t want on every method to write:

$this->setRequest($request);
$this->setResponse($response);

I want to avoid this.

Note:

( My real class code is very similar to that but with a lot of methods and classes. )

You ask what’s wrong with it? I think you have shown an example yourself :wink:
If you get it from the container all changes made by middleware is gone (like the attributes).
You should just use the instances passed into the route action. If not you defeat the whole purpose of Slim.
The only case to use a new response object is if you intentionally want to undo al previous made changes.

Also in your example the JSONObjectDecoder seems not needed. Slim does json decoding automaticly if you use getParsedBody().

1 Like

Got it, now I see what is wrong about my approuch. I was really naive with my solution. :slight_smile:
Thanks for the explanation.

I need my own Json class, getParsedBody() does the job but I needed more than that.

Also the last question ( Hopefully :smiley: ) : Is there a way to get the request/response objects instance on the constructor ? Like the example bellow:

public function __construct(Request $request, Response $response, Array $args)

public function myMethod1()

instead of

public function __construct(ContainerInterface $ci)

public function myMethod1(Request $request, Response $response, Array $args)

Maybe you can get it working by writing a custom invocation strategy and a custom callable resolver, but I doubt it’s worth the effort.

Related docs
http://www.slimframework.com/docs/objects/router.html#route-strategies
http://www.slimframework.com/docs/objects/router.html#container-resolution

And the implementations you will have to replace: CallableResolver and RequestResponse

1 Like

Thinking more about it.
If you really want something like this I think the easiest approach would be to use a custom InvocationStrategy which sets the request and response instances via setters.

Something like this: (untested!)

public function __invoke(
    callable $callable,
    ServerRequestInterface $request,
    ResponseInterface $response,
    array $routeArguments
) {
    // if using the default CallableResolver callable will be [controller, method]
    $controller = $callable[0];

    $controller->setRequest($request);
    $controller->setResponse($response);

    return call_user_func_array($callable, $routeArguments);
}
1 Like

class RequestResponse implements InvocationStrategyInterface{

public function __invoke(
    callable $callable,
    ServerRequestInterface $request,
    ResponseInterface $response,
    array $routeArguments
) {
    foreach ($routeArguments as $k => $v) {
        $request = $request->withAttribute($k, $v);
    }

    if (!$callable instanceof Closure && $callable[0] instanceof RoutableController) {
        $controller = $callable[0];

        $controller($request, $response, $routeArguments);
    }

    return call_user_func($callable, $request, $response, $routeArguments);
 }
}

@JoeBengalen You were right, did exactly as you told me and worked perfectly the way I wanted. I gonna keep doing tests to see if this breaks anything.

I needed to add some verifications for the $callable, because when using normal routing ( Without Mvc pattern ) it throws an Exception telling you can’t use Array on Closure. Pretty sure I not gonna be in trouble with this code.

This is a good idea that I would like to see on Slim so we can have better Controllers ( Not the way that are implemented here ). With this way I don’t have to keep repeating setters inside my methods for Request and Response.

Thanks for the help and advising, it was very constructive to me. :wink:

Any news or problems with this work around I’ll post here.

Hey. What if you use single action classes with __invoke(), instead of controllers with more actions. Then you can get your action object from the container with all your dependencies:

$container[HomepageAction::class] = function(Container $container)
{
	return new HomepageAction($container[SomethingDependency::class]);
};

You can have a very simple routing:

$app->get('/', $container[HomepageAction::class]);

Your action class will look like this:

class HomepageAction
{
	private $somethingDependency;

	public function __construct(SomethingDependency $somethingDependency)
	{
		$this->somethingDependency = $somethingDependency;
	}

	public function __invoke(ServerRequestInterface $request, ResponseInterface $response): ResponseInterface
	{
		// payload
	}
}

And there is one more advantage: there is no hard-coded (string) class name or method name in the routing what the IDE will not find in case of renaming! So if you rename your class or method with IDE refact feature, nothing will break.