11

Im having trouble understanding how to access the instance of Slim when a route is in a seperate class than index.php

When using Slim Framework 2 I always used the following, but its not working in Slim 3:

$this->app = \Slim\Slim::getInstance();

Im trying to access a database connection I have setup in the container, but from a separate class. This is what I currently got in my index.php to initiate a Slim app:

require_once("rdb/rdb.php");
$conn = r\connect('localhost');
$container = new \Slim\Container;
$container['rdb'] = function ($c){return $conn;}
$app = new \Slim\App($container);

And here is my route:

$app->get('/test','\mycontroller:test');

And this is what I got in my mycontroller.php class which my route points to, which obviously is not working as $this->app doesn't exist:

class mycontroller{
public function test($request,$response){
$this->app->getContainer()->get('rdb');
}

The error message is the following, due to getinstance not being part of Slim 3 compared to Slim 2:

Call to undefined method Slim\App::getInstance() 

Grateful for any help,

Regards Dan

user3507740
  • 113
  • 1
  • 1
  • 5

5 Answers5

13

Have a look at the Slim 3 Skeleton created by Rob Allen.

Slim 3 heavily uses dependency injection, so you might want to use it too.

In your dependencies.php add something like:

$container = $app->getContainer();

$container['rdb'] = function ($c) {
    return $conn;
};

$container['Your\Custom\Class'] = function ($c) {
    return new \Your\Custom\Class($c['rdb']);
};

And in your Your\Custom\Class.php:

class Class {
    private $rdb;
    function __construct($rdb) {
        $this->rdb = $rdb;
    }

    public function test($request, $response, $args) {
        $this->rdb->doSomething();
    }
}

I hope this helps, if you have any more questions feel free to ask.

Update:

When you define your route like this

$app->get('/test', '\mycontroller:test');

Slim looks up \mycontroller:test in your container:

$container['\mycontroller'] = function($c) {
    return new \mycontroller($c['rdb']);
}

So when you open www.example.com/test in your browser, Slim automatically creates a new instance of \mycontroller and executes the method test with the arguments $request, $response and $args. And because you accept the database connection as an argument for the constructor of your mycontroller class, you can use it in the method as well :)

Saran
  • 3,845
  • 3
  • 37
  • 59
Martin
  • 2,754
  • 1
  • 15
  • 35
  • Having spent most o the last 24 hours banging my head against this problem, even ditching slim fully as i could not figure it out or get help, reading about DI, your answer is the conclusion i came to this morning while walking my dog, that i would try. Just find it a bit odd to pass the whole DI around, but if thats the way it should be, i wont complain. Thank you mgansler for the answer, and the sample code, it will help greatly. – user3507740 Sep 04 '15 at 09:25
  • Forgot to ask, mgansler, how would I pass the DI to my controller function in this route: $app->get('/test','\mycontroller:test'); – user3507740 Sep 04 '15 at 09:29
  • Thank you a million times mgansler! – user3507740 Sep 04 '15 at 10:23
  • what is $c in your function parameter list and where from it come? – Gohel Kiran Dec 08 '15 at 09:22
  • `$c` is the container itself. I'm not really sure how it works, but it does :D See [Pimple Documentation](http://pimple.sensiolabs.org/#defining-services) – Martin Dec 09 '15 at 15:45
6

With Slim 3 RC2 and onwards given a route of:

$app->get('/test','MyController:test');

The CallableResolver will look for a key in the DIC called 'MyController' and expect that to return the controller, so you can register with the DIC like this:

// Register controller with DIC
$container = $app->getContainer();
$container['MyController'] = function ($c) {
    return new MyController($c->get('rdb'));   
}

// Define controller as:
class MyController
{
    public function __construct($rdb) {
        $this->rdb = $rdb;
    }

    public function test($request,$response){
        // do something with $this->rdb
    }
}

Alternatively, if you don't register with the DIC, then the CallableResolver will pass the container to your constructor, so you can just create a controller like this:

class MyController
{
    public function __construct($container) {
        $this->rdb = $container->get('rdb');
    }

    public function test($request,$response){
        // do something with $this->rdb
    }
}
Rob Allen
  • 12,643
  • 1
  • 40
  • 49
  • When using namespaces, DI becomes so cumbersome. I'd have loved to simply have `$app->get("/foo", "Foo:foo");` instead of `$app->get("/foo", "\App\Controllers\Foo:foo");` for every controller. I've setup psr4 in my `composer.json` but unfortunately I have to pass `Foo:class."foo"`. – TheRealChx101 Oct 04 '18 at 22:35
  • 1
    @TheRealChx101 It's easier to have one action class per route as you can use `__invoke()`, so it then becomes: `$app->get('/foo', GetFoo::class);` – Rob Allen Oct 05 '18 at 14:35
  • Ah. I get the concept of `action` now. It really does simplify things. I'd like to thank you specially for taking your time to respond to my comment. It's been a week or two since I'v been using Slim and every website I have come across that mentions Slim, you are always there to help people. It seems like you actually follow its trend. I truly appreciate your help, it means a lot and I think it's something that goes unnoticed by a lot of folks. Again, thank you. – TheRealChx101 Oct 05 '18 at 22:59
  • You're very welcome! Good luck with your work with Slim. – Rob Allen Oct 07 '18 at 07:50
2

I created the following base controller and extended from that. Only just started playing with Slim but it works if you need access to to the DI in your controllers.

namespace App\Controllers;

use Interop\Container\ContainerInterface;

abstract class Controller
{
    protected $ci;

    /**
     * Controller constructor.
     *
     * @param ContainerInterface $container
     */
    public function __construct(ContainerInterface  $container)
    {
        $this->ci = $container;
    }

    /**
     * @param $name
     * @return mixed
     */
    public function __get($name)
    {
        if ($this->ci->has($name)) {
            return $this->ci->get($name);
        }
    }
}

Then in your other controllers you can use it like this.

namespace App\Controllers;

 /**
 * Class HomeController
 *
 * @package App\Controllers
 */
class HomeController extends Controller
{

    /**
     * @param $request
     * @param $response
     * @param $args
     * @return \Slim\Views\Twig
     */
    public function index($request, $response, $args)
    {
        // Render index view
        return $this->view->render($response, 'index.twig');
    }

}
Lee
  • 479
  • 1
  • 4
  • 18
1

Important

I upvoted @mgansler and you should read that first if dealing with slim 3, and read this only if interested in differences to slim 2.


Update

So it seems those usages were just old code no one cleaned.

However im leaving this post here as it should be helpful to anyone using Slim 2 (as slim 3 is very much still beta) and as a referance point to help see differences.


Old Update (see above)

Following update of OP, i looked at github source code and found that getInstance is still very much there, but with some slight differences perhaps...

https://github.com/slimphp/Slim/search?utf8=%E2%9C%93&q=getInstance

Test files (which maybe outdated, but unlikely) show something like this:

public function testGetCallableAsStaticMethod()
{
    $route = new \Slim\Route('/bar', '\Slim\Slim::getInstance');

    $callable = $route->getCallable();
    $this->assertEquals('\Slim\Slim::getInstance', $callable);
}

But at the same time we see calls like this in some files, which are obviously contextual and either return diff object ($env) or are in same static file (Slim.php)

$env = \Slim\Environment::getInstance(true);

static::getInstance();

But this does show the static function still exists, so use my examples below and try to figure out why not working for you in current form.

Also, this 'maybe' of interest, as only obvious example of slim3 in usage: https://github.com/akrabat/slim3-skeleton

Though other projects prob exist, search with github filters if still having issues.



Original Answer content

Please include more detail on the route and the other class, but here are 3 ways, with execution examples detailed further down.

This info does relate to Slim Framework 2, not the Slim 3 beta, but slim 3 beta shows similar example code and makes no mention of overhauling changes, and in fact links to the Slim 2 documentation: http://docs.slimframework.com/configuration/names-and-scopes/

$this->app->getContainer()->get('rdb');

// Recommended approach, can be used in any file loaded via route() or include()
$app = \Slim\Slim::getInstance();

Slim::getInstance();

App::config('filename');

Slim3 Beta has only one code example, which looks like this:

$app = new \Slim\App();

// which would by extension mean that this 'might' work too

$app = \Slim\App::getInstance();

// but be sure to try with slim2 naming just in case

$app = \Slim\Slim::getInstance()

Though obviously this doesnt fit outside of index.php, but is consistent with Slim2 doco showing GetInstance works.


Which one fits you?

I have multiple files that use these different approaches, though i cant say what fits best as too little context on how this external class fits in and what its composition is.


For example, my controllers (which are endpoints of most my routes) use the same approach, through a base class or just direct:

class ApiBaseController /// extends \BaseController
{

    protected $app;
    protected $data;

    public function __construct()
    {

        $this->app = Slim\Slim::getInstance();
        $this->data = array();

    }

    //...

}


class VideoApiController extends \ApiBaseController
{

    // ... 


    public function embed($uid)
    {
        // trace($this->app->response->headers());
        $vid = \R::findOne('videos'," uid = ? ",array($uid));
        if(!empty($vid))
        {

            // embed logic

        }else{
            // see my baseclass
            $this->app->render('api/404.html', array(), 404);
        }
    }


    // ...




    // Returns the video file, keeping actual location obscured
    function video($uid)
    {
        require_once(APP_PATH.'helpers/player_helper.php');

        $data = \R::findOne('videos'," uid = ? ",array($uid));

        /// trace($_SERVER); die();

        if($data)
        {
            stream_file($data['filename']);
        }else{
            $app = \Slim\Slim::getInstance();
            $app->render('404.html');
        }

        /// NOTE - only same domain for direct /v/:uid call
        header('Access-Control-Allow-Origin : '.$_SERVER['HTTP_HOST']);
        // header('X-Frame-Options: SAMEORIGIN');

        // Exit to be certain nothing else returned
        exit();
    }


    //...
}


My helper files show code like this:

function get_permissions_options_list($context = null)
{
    if(empty($context)) $context = 'user';
    return App::config('permissions')[$context];
}


My middleware:

function checkAdminRoutePermissions($route)
{
    $passed = runAdminRoutePermissionsCheck($route);

    if($passed)
        return true;

    // App::notFound();
    // App::halt(403, $route->getPattern());

    if(!Sentry::check())
        App::unauthorizedNoLogin();
    else
        App::unauthorized();
    return false;
}


Thats example of how i access in the various files, though the code you shared already shows that you have used the recommended approach already

$app = \Slim\Slim::getInstance();

Though again, need more info to say for sure how your external file fits in, but if its at the end of a route or in an 'include()', then it should work.

You said your old approach didnt work though, but gave no info on what the actual result vs expected result was (error msg, ect), so if this doesnt work please update the OP.

Daniel Brose
  • 1,394
  • 10
  • 24
  • Thanks for your reply Daniel, I have added the route and class code. Its very barebone as im just about to try out rethinkdb with php, but got stuck very early. – user3507740 Sep 03 '15 at 01:24
  • @user3507740 - you also have all slim php files in your install, just search for GetInstance and i can assure you it will be there somewhere. – Daniel Brose Sep 03 '15 at 01:31
  • Thanks again for your help Daniel, I will keep trying and using what you have posted. The statement that getInstance() is not (going to be) part of it, i read from a comment on the slim forum by one of the authors posted a few months ago. – user3507740 Sep 03 '15 at 07:50
  • `getInstance()` is NOT in Slim **3** anymore, you have been looking at Slim **2** code. (`\Slim\Slim` is now `\Slim\App`) – Martin Sep 04 '15 at 08:38
  • @mgansler - I know that, in fact I referenced it very clearly - just I can't see where it says it's not and its in test files in slim 3 build. I'm traveling atm, but will update this post when I get back. – Daniel Brose Sep 05 '15 at 04:30
1

This was a tough one. @mgansler answer was really helpful, but in his answer he passed a database connection, and not exactly $app inside the controller

Following the same idea it is possible to send $app though.

First inside your dependencies.php you need to grab the $app and throw it in a container to inject it to the Controller later.

$container['slim'] = function ($c) {
   global $app;
   return $app;
};

Then you got to inject it:

// Generic Controller
$container['App\Controllers\_Controller'] = function ($c) {
    return new _Controller($c->get('slim'));
};

Now on your controller.php:

private $slim;

/**
     * @param \Psr\Log\LoggerInterface       $logger
     * @param \App\DataAccess                $dataaccess
     * @param \App\$app                      $slim
     */
    public function __construct(LoggerInterface $logger, _DataAccess $dataaccess, $slim)
    {       
        $this->logger = $logger;
        $this->dataaccess = $dataaccess;
        $this->slim = $slim;
    }

Now you just got call it like this:

$this->slim->doSomething();
Leo Leao
  • 161
  • 1
  • 5