Access app in class in Slim Framework 3

2019-03-18 15:35发布

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

5条回答
等我变得足够好
2楼-- · 2019-03-18 16:23

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.

查看更多
戒情不戒烟
3楼-- · 2019-03-18 16:24

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
    }
}
查看更多
迷人小祖宗
4楼-- · 2019-03-18 16:26

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');
    }

}
查看更多
SAY GOODBYE
5楼-- · 2019-03-18 16:29

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 :)

查看更多
男人必须洒脱
6楼-- · 2019-03-18 16:33

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();
查看更多
登录 后发表回答