3

I've set up a new app based on the SlimPHP team's Slim Skeleton application. Inside of my route definitions, I want to be able to access the route parser as described in the Slim4 documentation. So, for example, I'd like to be able to edit the skeleton's app/routes.php file something like this:

    $app->get('/', function (Request $request, Response $response) {
        $routeParser = $app->getRouteCollector()->getRouteParser();  // this doesn't work
        $response->getBody()->write('Hello world! ' . $routeParser->urlFor('something'));
        return $response;
    });

It makes sense that $app->getRouteCollector()->getRouteParser() doesn't work, because $app isn't defined here. But I would think that we'd instead call $this->getRouteCollector()->getRouteParser();, but that gives the error: "Call to undefined method DI\\Container::getRouteCollector()".

It definitely seems that my confusion is about Dependency Injection, which is new for me and not coming naturally to me. I'd honestly love to define the $routeParser variable somewhere else (inside index.php?) so that I could access it in any route definition without having to call $app->getRouteCollector()->getRouteParser() every time. But at the moment I'd settle for anything that worked.

davidreedernst
  • 485
  • 6
  • 11
  • 1
    Does this answer your question? [Accessing outside variable using anonymous function as params](https://stackoverflow.com/questions/8403908/accessing-outside-variable-using-anonymous-function-as-params) – Jeto Jan 06 '20 at 22:18
  • @Jeto if it does, it does so in a way that I can't understand well enough to apply to my situation. Also note that the excellent answers from @odan and @Nima don't use `use` or pass anything by reference, so I don't think that points to the best way. – davidreedernst Jan 07 '20 at 12:01
  • To be fair I'm not familiar with the Slim framework, so I'll retract my close vote as I assumed it was similar to a micro-framework such as Silex, where `use`ing `$app` was common (in your case `$app->get('/', function (Request $request, Response $response) use ($app) { ... }`). It might not be a recommended way in Slim, can't say for sure. – Jeto Jan 07 '20 at 12:39

1 Answers1

6

Slim skeleton actually demonstrate an example of what you need to achieve. After creating the App instance in index.php, there is an assignment like this:

// Instantiate the app
AppFactory::setContainer($container);
$app = AppFactory::create();
$callableResolver = $app->getCallableResolver();

You can do the same:

$routeParser = $app->getRouteCollector()->getRouteParser();

And if you really need this instance of RouteParser to be available inside every route callback, you can put it in dependency container, something like:

$container->set(Slim\Interfaces\RouteParserInterface::class, $routeParser);

Then you can use PHP-DI auto-wiring feature to inject this RouteParser into controller constructor:

use Slim\Interfaces\RouteParserInterface;
class SampleController {
    public function __construct(RouteParserInterface $routeParser) {
        $this->routeParser = $routeParser;
        //...
    }
}

or if you need to call $container->get() inside any of your route callbacks:

$app->get('/', function (Request $request, Response $response) {
    $routeParser = $this->get(Slim\Interfaces\RouteParserInterface::class);
    $response->getBody()->write('Hello world! ' . $routeParser->urlFor('something'));
    return $response;
});
Nima
  • 3,309
  • 6
  • 27
  • 44
  • I love this answer! It not only solves my problem, it helps me get a better sense of how the whole DI thing works. Thank you! – davidreedernst Jan 07 '20 at 12:08
  • Question: if I'm not going to use the auto-wiring thing, it seems I can `set` a simple variable rather than a class, like this: `$container->set('routeParser', $routeParser);`. Then I can `get` that in my route callbacks simply with `$this->get('routeParser')`. Any downside of doing it this way? – davidreedernst Jan 07 '20 at 12:09
  • 1
    `I can set a simple variable rather than a class`...you're not setting a **class**. In php, `MyClass::class` is the fully qualified name of that class (i.e containing its full namespace), it is a string. The first parameter to `$container->set()` should be a string as well, and yes, you can use any name you like (it does not have to be `routeParser`), but in that case you can't use auto wiring and **that** is the downside. – Nima Jan 07 '20 at 12:51