In Slim v3 how to access $container properties in routes? - php

My Setup is like this:
I request my settings from this file and store them in the settings variable.
$settings = require __DIR__ . '/settings.php';
Next create a new Slim instance like so:
$app = new \Slim\App($settings);
$container = $app->getContainer();
$container['logger'] = function($c) {
$settings = $c->get('settings')['logger'];
$logger = new \Monolog\Logger($settings['name']);
$file_handler = new \Monolog\Handler\StreamHandler($settings['path']);
$logger->pushHandler($file_handler);
return $logger;
};
Then i am calling my route:
$this->get('/testlogger, __testReq::class . ':test);
The above route calls the "test" method inside of my class. Which gets loaded over autoload. Below my class (controller) in which i am trying to access the container like explained on Slim Website.
class __testReq {
function test($request, $response){
//According to Documentation i am supposed to be able to call logger like so:
$this->logger->addInfo("YEY! I am logging...");
}
}
Why is it not working?

From Slim documentation (Documentation uses HomeController class as example):
Slim first looks for an entry of HomeController in the container, if it’s found it will use that instance otherwise it will call it’s constructor with the container as the first argument.
So in your class __testReq constructor, you need to set up the object:
class __testReq {
// logger instance
protected $logger;
// Use container to set up our newly created instance of __testReq
function __construct($container) {
$this->logger= $container->get('logger');
}
function test($request, $response){
// Now we can use $this->logger that we set up in constructor
$this->logger->addInfo("YEY! I am logging...");
}
}

Related

How to inject DI container into a controller?

I have successfuly installed Router using PHP-DI.
There is some simple app example based on the article.
There are 2 files: index.php and controller.php. I want to use $container from controller. However, I have no idea how to inject it?
// index.php
use...
require_once...
$containerBuilder = new ContainerBuilder();
....
$container = $containerBuilder->build(); // I succesfuilly build a container here with all needed definitions, including Database, Classes and so on.
$routes = simpleDispatcher(function (RouteCollector $r) {
$r->get('/hello', Controller::class);
});
$middlewareQueue[] = new FastRoute($routes);
$middlewareQueue[] = new RequestHandler($container);
$requestHandler = new Relay($middlewareQueue);
$response = $requestHandler->handle(ServerRequestFactory::fromGlobals());
$emitter = new SapiEmitter();
return $emitter->emit($response);
So the code just receive Response from the dispatcher and pass it off to the emitter.
namespace ExampleApp;
use Psr\Http\Message\ResponseInterface;
class Controller
{
private $foo;
private $response;
public function __construct(
string $foo,
ResponseInterface $response
) {
$this->foo = $foo;
$this->response = $response;
}
public function __invoke(): ResponseInterface
{
$response = $this->response->withHeader('Content-Type', 'text/html');
$response->getBody()
->write("<html><head></head><body>Hello, {$this->foo} world!</body></html>");
return $response;
}
}
Now I want to add logic into Controller based on my $container: database, logger and so on. I want somehow to use $container instance which was created in index.php. I have tried a lot of ways, but nothing works proper.

Injecting in the constructor when using pimple

Background. I am using Slim where an ID is either in the endpoint or parameters. Based on the ID, a factory creates the appropriate object to perform the needed action.
I have a service which needs some data obtained in the request injected into it. I could therefore do the following:
//index.php
require '../vendor/autoload.php';
use Pimple\Container;
$container = new Container();
class SomeService
{
private $dataFromTheRequest;
public function __construct($dataFromTheRequest){
$this->dataFromTheRequest=$dataFromTheRequest;
echo($dataFromTheRequest);
}
}
$dataFromTheRequest='stuff1';
$container['someService1'] = function ($c) use($dataFromTheRequest) {
return new SomeService($dataFromTheRequest);
};
$someService=$container['someService1'];
But the service is not used in index.php where the service is defined but in another class, so i can do the following:
class SomeOtherClass1
{
public function someMethod($dataFromTheRequest){
$someService=new SomeService($dataFromTheRequest);
}
}
$someOtherClass1=new SomeOtherClass1();
$someOtherClass1->someMethod('stuff2');
But I want to use the instance assigned in index.php, so I can do the following:
$container['someService2'] = function ($c) {
return new SomeService($c['dataFromTheRequest']);
};
class SomeOtherClass2
{
public function __construct($container){
$this->container=$container;
}
public function someMethod($dataFromTheRequest){
$this->container['dataFromTheRequest']='stuff3';
$someService=$this->container['someService2'];
}
}
$someOtherClass2=new SomeOtherClass2($container);
$someOtherClass2->someMethod();
But using the container to pass data just seems wrong.
How should data be injected in a pimple service if that data is not known when the service is defined?

Why can Container::getInstance() return an application class

I am I'm wondering why the Container::getInstance() can return a application class.
For example:
I want to make a hash str, I want to know how they work:
app('hash')->make('password');
and I found the source code in laravel :
vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
if (! function_exists('app')) {
/**
* Get the available container instance.
*
* #param string $make
* #param array $parameters
* #return mixed|\Illuminate\Foundation\Application
*/
function app($make = null, $parameters = [])
{
if (is_null($make)) {
return Container::getInstance();
}
return Container::getInstance()->make($make, $parameters);
}
}
I dont know what the Container::getInstance() will return, then I dd(Container::getInstance()) and I know it will can return an application class, but I dont know how they work.
Maybe I'm a little bit late with my answer, but anyway.
Description is current as of Laravel framework version 5.3.24.
Why calling app(), that then calls Container::getInstance() returns object, instance of Application?
For example, why
Route::get('/', function () {
var_dump(app());
});
outputs:
object(Illuminate\Foundation\Application)
...
Because... And here we have to go step by step though it to understand everything.
User initiates a web request. The request is processed by /public/index.php
/public/index.php contains the following:
$app = require_once __DIR__.'/../bootstrap/app.php';
/bootstrap/app.php has the following lines:
$app = new Illuminate\Foundation\Application(
realpath(__DIR__.'/../')
);
When an $app object is instantiated, the Illuminate\Foundation\Application class constructor is called.
/vendor/laravel/framework/src/Illuminate/Foundation/Application.php
class Application extends Container implements ...
{
// ...
public function __construct($basePath = null)
{
// 5. constructor triggers the following method:
$this->registerBaseBindings();
// ...
}
// ...
protected function registerBaseBindings()
{
// 6. which then triggers the following:
static::setInstance($this);
// 7. Important! $this points to class Application here
// and is passed to Container
// ...
}
// ...
}
static::setInstance($this); refers us to class Container, because class Application extends Container
/vendor/laravel/framework/src/Illuminate/Container/Container.php
class Container implements ...
{
// ...
// 11. $instance now contains an object,
// which is an instance of Application class
protected static $instance;
// ...
public static function setInstance(ContainerContract $container = null)
{
// 9. $container = Application here, because it has been passed
// from class Application while calling static::setInstance($this);
// 10. Thus, static::$instance is set to Application here
return static::$instance = $container;
}
// ...
}
Now, suppose, we have written the following lines in our routes file.
/routes/web.php
Route::get('/', function () {
dd(app()); // 13. We a calling an app() helper function
});
14 Calling app() leads us to
/vendor/laravel/framework/src/Illuminate/Foundation/helpers.php
// ...
/** #return mixed|\Illuminate\Foundation\Application */
function app($make = null, $parameters = [])
{
// 15. $make is null, so this is the case
if (is_null($make)) {
// 16. The following static method is called:
return Container::getInstance();
}
// ...
}
// ...
Now we are back in our Container class
/vendor/laravel/framework/src/Illuminate/Container/Container.php
public static function getInstance()
{
// 18. Important!
// To this point static::$instance is NOT null,
// because it has already been set (up to "step 11").
if (is_null(static::$instance)) {
static::$instance = new static; // Thus, we skip this.
}
// 19. static::$instance is returned
// that contains an object,
// which is an instance of Application class
return static::$instance;
}
Some important notes for steps 16-19.
Important note 1!
Static Keyword
Declaring class properties or methods as static makes them accessible
without needing an instantiation of the class.
Important note 2!
static::$instance = new static; is NOT related to calling our app() function in step 13. And was somewhat misleading for me at first...
But just to note, it makes use of Late Static Bindings.
The Application class (Illuminate\Foundation\Application) extends the Container class. This is the core of the framework and allows all the dependency injection magic, and it follows a Sigleton pattern, this means that when you request the Application object (with the app() helper function, or more internally Container::getInstance()) anywhere in your code you get the same global instance.
Without getting to complex: this let's you bind any class into that Container instance:
app()->bind('MyModule', new MyModuleInstace());
So then you can "resolve" that class out of the container with:
app()->make('MyModule);
But remember there are several methods to do this, with different pattern targets, this is just a basic demonstration of the concept.

Slim 3 render method not valid

I want to make simple template rendering in Slim3 but I get an error:
Here is my code :
namespace controller;
class Hello
{
function __construct() {
// Instantiate the app
$settings = require __DIR__ . '/../../src/settings.php';
$this->app = new \Slim\App($settings);
}
public function index(){
return $this->app->render('web/pages/hello.phtml'); //LINE20
}
}
This is the error I get :
Message: Method render is not a valid method
The App object doesn't handle any rendering on its own, you'll need a template add-on for that, probably this one based on your template's .phtml extension. Install with composer:
composer require slim/php-view
Then your controller method will do something like this:
$view = new \Slim\Views\PhpRenderer('./web/pages');
return $view->render($response, '/hello.phtml');
You'll eventually want to put the renderer in the dependency injection container instead of creating a new instance in your controller method, but this should get you started.
I handle this by sticking my renderer in the container. Stick this in your main index.php file.
$container = new \Slim\Container($configuration);
$app = new \Slim\App($container);
$container['renderer'] = new \Slim\Views\PhpRenderer("./web/pages");
Then in your Hello class's file.
class Hello
{
protected $container;
public function __construct(\Slim\Container $container) {
$this->container = $container;
}
public function __invoke($request, $response, $args) {
return $this->container->renderer->render($response, '/hello.php', $args);
}
}
To clean up this code, make a base handler that has this render logic encapsulated for you.

Accessing Silex Application class from other classes; how to inject?

I'm currently working on my first Silex (2.0) project. I have some Pheasant models defined, which I can access from within my controller:
// it is used both static
$p = \Model\Post::oneById(3);
// .. and with an instance
$p = new \Model\Post;
$p->title = 'foobar';
$p->save();
Now, in some cases I'd like to access the Application class within my model. For example, to check if we're running in debug mode (or not). Right now:
public function beforeSave() {
global $app;
if($app['debug']) {
// ...
}
}
But that doesn't feel like Silex. So I figured I need some kind of thing that'll automatically inject the $app class to my models:
class PheasantModelReflector {
protected $app;
public function __construct(\Silex\Application $app) {
$this->app = $app;
}
public function __get($className) {
$r = (new ReflectionClass(sprintf('Model\%s', $className)))->newInstance();
$r->__invoke($this->app);
return $r;
}
}
$app['model'] = function ($app) {
return new PheasantModelReflector($app);
};
This works to a certain level, but always returns a new instance of the Post model when called like $app['model']->Post.
Is there any way to fix this?
Without having worked with Pheasant, I may try to create a service factory that injects the $app (or the $debug var) into your entity class like so (one problem here is that your entity must extend \Pheasant\DomainObject and you can't override the constructor as it's marked as final):
<?php
// in the example below I inject the whole $app, if you only need the
// debug var, inject only that, injecting the whole $app may
// tempt you to use it as a service locator
// creating a new instance
$app['model_post_factory'] = $app->factory(function ($app) {
$post = new \Model\Post();
$post->setApp($app);
return $post;
});
$post = $app['model_post_factory'];
// getting an instance by ID, here it gets a little tricky
$app['model_post_static_factory'] = function($app, $id) {
$post = \Model\Post::oneById($id);
$post->setApp($app);
return $post;
}
$postById = $app->raw('model_post_static_factory');
$post = $postById($app, $id); // $id comes from somewhere else
// depending on the PHP version you may try directly:
// $post = $app->raw('model_post_static_factory')($app, $id);
The problem with the static method is passing the id parameter. You can import into the factory scope the $id from the outside scope using the use keyword, but IMHO this is too magic (though I don't quite like the alternative either). There may be some more elegant way, but I can't think of right now.
To avoid to generate a new instance of model everytime you need to create a shared service (http://silex.sensiolabs.org/doc/services.html).
For example:
$app['model'] = $app->share(function () {
return new PheasantModelReflector();
});
Anyway inject $app is not necessarily a good idea, maybe you can just inject the dependency every object has? Something like:
new \Model\Post($app['debug']);

Categories