Accessing App from within function


#1

I realize this is a scope thing, and a fundamental part of PHP I’m sure, but I’m learning as I go here and I’m a little confused. Part of the tutorial has you define some config settings and plug them into the App instance:

$config['displayErrorDetails'] = true;
//$config['addContentLengthHeader'] = false;
$config['custom']['minPasswordLength'] = 8;
$config['custom']['blah'] = true;

then…

$app = new \Slim\App(["settings" => $config]);

I’m confused now, because I’m inside one of my route definitions and need to access one of these config settings and can’t figure out how.

Here’s what I’m trying to do, please tell me the proper way to do this, as this just doesn’t work.

// Get all users
$app->get('/users',function (Request $request, Response $response) {
    $users = UserQuery::create()->find();
    $response->getBody()->write($users->toJSON());
    // I'm incorrectly accessing the configs here...
    error_log("Test> MinPasswordLength: " + $app['config']['custom']['minPasswordLength']);
    return $response;
});

Thank you for your time!


#2

Within the route callback you can use either $this->get('item_name') or $this->item_name to get an item from the container. In this case it would be:

$app->get('/users',function (Request $request, Response $response) {
    error_log("Test> MinPasswordLength: " + $this->get('custom')['minPasswordLength']);
    // or
    error_log("Test> MinPasswordLength: " + $this->custom['minPasswordLength']);
});

You can find some more information in the documentation.


#3

This helped immensely!

One thing though, I had to change:

to

Thanks again for your help!


#4

How would I also access this from within a class?

class RandomAuthenticator implements AuthenticatorInterface {
public function __invoke(array $arguments) {
// $this->get(‘settings’)[‘custom’][‘minPasswordLength’] won’t work here.
return (bool)rand(0,1);
}
}

This would result an error like this:

Call to undefined method RandomAuthenticator::get() in /var/www/html/config/middleware.php on line 16

Thanks in advance!


#5

To access it from a class, it needs to be given to the class as currently the class doesn’t know about it. If RandomAuthenticator is in your container you can define it there. To see an example of this, checkout the Slim-Skeleton.

In that example, the Slim\Views\PhpRendered class is being given the settings for the template path. Then in your RandomAuthenticator class you would accept it through the constructor. You can find an example of that in this other Skeleton project.

In that example see how the view is being accepted in the constructor, setup as $this->view and then used later.


#6

I’m learning a lot in this thread :wink:

I think I’m almost there:

  3 use \Slim\Middleware\HttpBasicAuthentication\AuthenticatorInterface;
  4 
  5 class RandomAuthenticator implements AuthenticatorInterface {
  6     private $settings;
  7 
  8     public function __construct($settings) {
  9         $this->settings = $settings;
 10     }
 11 
 12     public function __invoke(array $arguments) {
 13         // db check stuff here
 14         return (bool)rand(0,1);
 15     }
 16 } 

Now…

 77 $app->add(new \Slim\Middleware\HttpBasicAuthentication([
 80     "relaxed" => ["localhost", "192.168.1.3"],
 81     "realm" => "Protected",
 82     "authenticator" => new RandomAuthenticator($this->get('settings')),

But I get an error that $this is out of context:

Using $this when not in object context in /var/www/html/config/middleware.php on line 76


#7

This looks like tuupola’s slim-basic-auth? Are you using a DIC container? If so, you should setup your HttpBasicAuthentication Middleware in there.

$container['HttpBasicAuthMiddleware'] = function ($c) {
    $httpBasicAuth = new \Slim\Middleware\HttpBasicAuthentication([
        "relaxed" => ["localhost", "192.168.1.3"],
        "authenticator" => new RandomAuthenticator($c['settings'])
    ]);
    return $httpBasicAuth;
};

But I’m a bit confused. You might be looking for this.

$app->getContainer()->get('settings');
// or maybe...
$app->getContainer()->get('settings')['custom']['minPasswordLength'];

#8

Hi all,

I’m wondering !! Last couple of day I tried some xdebug things and saw that there a reference to the Slim App object in the $GLOBALS namespace …
I was searching for a good way to have acces to the database from several points :

  • In my Controllers (so I set et self.db in the constructor and use D.I.)
  • In my Models (same thing).

Now i’m moving my DB logic to specific classes for each Model ( mimic the repository in Symfony I think ).
So first think with moving things to the Repository was to test :

$pdo = $GLOBALS[“app”]->getContainer()->get(‘db’);

wich works pretty well (db is a container object wich returns a PDO instance)…

But i’d like to have some review about this practice. Doesn’t it look like Slim::getInstance() in Slim 2.x ?
What problem can I get (like having multiple PDO instances). I’ve tried

$pdo = $GLOBALS[“app”]->getContainer()->get(‘db’);
$pdo2 = $GLOBALS[“app”]->getContainer()->get(‘db’);
var_dump($pdo == $pdo2); die;
It returns True (but as it’s only initialized it might be misleading).

I’m feeling that i’m killing good pratices here. Any opinion ?
Thanks


#9

@Mout If I understand your question correctly, here is an example I use in my applications. I create a new container before the app:

$di = new Slim\Container;

$di['pdo'] = function() {
	return new App\DB($host,$user,$pass,$dbname);
};

$di['loginModel'] = function($di) {
	return new App\Model\Login($di->pdo);
};

// app init
$app = new Slim\App;

$c = $app->getContainer();

$c['LoginCtrl'] = function() use ($di) {
	return new App\Controller\Login($di->loginModel);
};

$app->post('/login','LoginCtrl:submit');

Alternatively, you can add all your containers under the $app->getContainer():

$app = new Slim\App;
$c = $app->getContainer();

$c['pdo'] = function() {
    return new App\DB($host,$user,$pass,$dbname);
};

#10

@robrothedev Yes, that’s how i’m currently doing it. But instead of using D.I. I was wondering about using the $GLOBALS namespace wich contains the ‘app’ reference, wich means an access point to the controller without D.I.

Here’s my bootstrap:

$app = new \Slim\App($app_settings);
$container = $app->getContainer();

// - PDO
$container[‘db’] = function ($container) {
$config = $container->get(‘settings’)[‘db’];
$dsn = sprintf(
’%s:host=%s;dbname=%s;charset=%s’,
$config[‘engine’],
$config[‘host’],
$config[‘database’],
$config[‘charset’]
);
return new PDO($dsn, $config[‘username’], $config[‘password’], $config[‘options’]);
};

An exemple of a controller in wich a $container is injected :

class BaseController
{
protected $container;
protected $modelInstance;

public function __construct($container)
{
    $this->container = $container;
}
public function __get($property)
{
    if ($this->container->{$property}) {
        return $this->container->{$property};
    }
}

// controller’s methods // …
}

Now trying to avoid always passing $this->container to my models and controllers, i’ve tested this way :

class CompanyRepository
{

protected static $view  = "v_company";
public static function get($page, $itemPerPage)
{

    $pdo = $GLOBALS["app"]->getContainer()->get('db');
    $sth = $pdo->prepare("SELECT legal_name FROM " . CompanyRepository::$view . ";");
    $sth->execute();
    $r = $sth->fetchAll();
    return $r;
}

}

Here I use the $GLOBALS variables to get the App refernce wich gives me the container from wich I get the DB ‘instance’.
I’ve tried to monitor the DB acces and it seems that I only get 1 connection no matter if in the same execution I call the database from the D.I. container in the Controller and in the static GLOBALS way. So it seems that I get the same PDO object. Wich could be cool !


#11

Your method works but there seems to be a lot of heavy dependency with Slim and your source code. I would advocate dependency injections for classes rather than accessing Slim $GLOBALS.


#12

Ok ! Meaning that any changes to Slim might broke the App ?


#13

@Mout I think Rob Allen’s Tweet sums up Slim development pretty well. You want to write your components separately and then integrate them into Slim rather than writing Slim specific code, although Controllers are a bit of an exception.

I would lean on dependency injection like this:

$di = new Slim\Container;

$di['pdo'] = function() {
	return new App\DB($host,$user,$pass,$name);
};

$di['companyRepo'] = function($di) {
	return new App\CompanyRepository($di->pdo);
};

$di['CompanyCtrl'] = function($di) {
	return new CompanyController($di->companyRepo);
};

$app = new Slim\App;
$app->get('/company','CompanyCtrl:get');

// classes
class CompanyRepository {

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

	public function get($view) {
		$sth = $this->pdo->prepare("SELECT legal_name FROM " . $view);
    	$sth->execute();
    	return $sth->fetchAll();
	}
}

class CompanyController {

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

	public function get($request,$response) {
		$view = "v_company";
		$record = $this->companyRepo->get($view);
		// return your page with the record data
	}
}

#14

You are correct, and your solution worked beautifully.

Thank you!