Get Dependency Injection in a class with Slim 3 - php

I have a problem with DI, it's new for me. I would like to give the Container as DI in my Project class, but i got an error :
Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
I created a class Project :
use Slim\Container;
class Project {
protected $ci;
public function __construct(Container $ci) {
$this->ci = $ci;
}
}
Here my DIC configuration :
//dependencies.php
$container = $app->getContainer();
$container[Project::class] = function ($c) {
return new Project($c);
};
And here my code index.php to run the code
// Instantiate the app
$settings = require __DIR__ . '/../app/configs/settings.php';
$app = new \Slim\App($settings);
// Set up dependencies
require __DIR__ . '/../app/configs/dependencies.php';
// Register middleware
require __DIR__ . '/../app/configs/middleware.php';
// Register routes
require __DIR__ . '/../app/configs/routes.php';
require __DIR__ . '/../app/model/Project.php';
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project();
return $response;
});
$app->run();
I dont know where i failed, i even tried to use slim-brige, but i got the same result. I also tried to give a string instead of the the Container, and i still get null

Argument 1 passed to Project::__construct() must be an instance of Slim\Container, none given
Well, no argument given to the constructor indeed:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = new Project(); // You should pass container instance here, but you don't
return $response;
});
Since you have registered your Project class in the container, you can get instance from the container:
$app->get('/project/{id}', function (Request $request, Response $response) {
$project = $this->container->get('Project'); // Get Project instance from the container, you have already registered it
return $response;
});

Related

PHP Slim 3 - Accessing class object instances within a slim route

So I’m learning how to write a Slim 3 PHP authentication app and I'm using an example code structure to get me started. The example code has a file called dependencies.php which has a series of functions that create object instances of other classes. These are then assigned to a $container variable with a name for each function. An example of these functions in the dependencies.php file can be seen here:
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig(
$container['settings']['view']['template_path'],
$container['settings']['view']['twig'],
[
'debug' => true // This line should enable debug mode
]
);
$basePath = rtrim(str_ireplace('index.php', '', $container['request']->getUri()->getBasePath()), '/');
$view->addExtension(new Slim\Views\TwigExtension($container['router'], $basePath));
$view->addExtension(new \Twig_Extension_Debug());
return $view;
};
$container['validate_sanitize'] = function ($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'ValidateSanitize.php';
$validator = new ValidateSanitize();
return $validator;
};
$container['hash_password'] = function($container)
{
$class_path = $container->get('settings')['class_path'];
require $class_path . 'HashPassword.php';
$hash = new HashPassword();
return $hash;
};
These functions are then somehow called in my slim routes. For example in my register.php slim route the validate_sanitize class object is called with a simple
$this->get(‘validate_sanitize’);
and assigned to a variable which I can then use to call methods from the validate_sanitize class.
However what I don’t understand is how this get method works on calling a class object from the dependencies.php file.
Here is the aforementioned register route that is a post request for incoming form data:
$app->post('/register', function(Request $request, Response $response)
{
$arr_tainted_params = $request->getParsedBody();
$sanitizer_validator = $this->get('validate_sanitize'); //here for example
$password_hasher = $this->get('hash_password');
$tainted_email = $arr_tainted_params['email'];
$tainted_username = $arr_tainted_params['username'];
$tainted_password = $arr_tainted_params['password'];
$model = $this->get('model');
$sql_wrapper = $this->get('sql_wrapper');
$sql_queries = $this->get('sql_queries');
$db_handle = $this->get('dbase');
$cleaned_email = $sanitizer_validator->sanitize_input($tainted_email, FILTER_SANITIZE_EMAIL);
$cleaned_username = $sanitizer_validator->sanitize_input($tainted_username, FILTER_SANITIZE_STRING);
$cleaned_password = $sanitizer_validator->sanitize_input($tainted_password, FILTER_SANITIZE_STRING);
});
All my routes are contained within a routes.php file that looks like this:
<?php
require 'routes/change_password.php';
require 'routes/forgot_password.php';
require 'routes/homepage.php';
require 'routes/login.php';
require 'routes/logout.php';
require 'routes/register.php';
There is also a bootstrap file that creates a new Slim container, Slim App instance and also includes necessary files. I’m also not entirely sure what a Slim\Container is or what it does. This bootstrap file looks like this:
<?php
session_start();
require __DIR__ . '/../vendor/autoload.php';
$settings = require __DIR__ . '/app/settings.php'; //an array of options containing database configurations and the path to twig templates
$container = new \Slim\Container($settings); //not sure what this does
require __DIR__ . '/app/dependencies.php';
$app = new \Slim\App($container);
require __DIR__ . '/app/routes.php';
$app→run();
I’ve tried reading a number of articles as well as watching various YouTube videos but they do things differently with controllers which just adds more complexity and confusion for me. I’d much rather an explanation on this specific example as I find the code structure fairly simple.
Thanks.
Inside route callable, $this will point to $container instance.
In Slim 3.0, if you take a look at map() method of Slim\App class, you will see following code:
if ($callable instanceof Closure) {
$callable = $callable->bindTo($this->container);
}
bindTo() is what makes you have access to container inside route callable using $this variable.
If you want to use class as route handler and want access to container instance inside class, you need to pass it manually. For example
<?php
namespace App\Controller;
class MyPostRegisterController
{
private $container;
public function __constructor($container)
{
$this->container = $container;
}
public function __invoke(Request $request, Response $response)
{
$sanitizer_validator = $this->container->get('validate_sanitize');
//do something
}
}
Then you can define route as following
$app->post('/register', App\Controller\MyPostRegisterController::class);
If Slim can not find MyPostController class in dependency container, it tries to create them and pass container instance.
Update
To call other method, append method name after class name separated by colon. For example, following route registration will call method home() in MyPostRegisterController class.
$app->post('/register', App\Controller\MyPostRegisterController::class . ':home');

Slim php - access container from middleware

I am trying to access the $container from my middleware, but i am not getting much luck.
In my index.php file I have
require '../../vendor/autoload.php';
include '../bootstrap.php';
use somename\Middleware\Authentication as Authentication;
$app = new \Slim\App();
$container = $app->getContainer();
$app->add(new Authentication());
And then I have a class Authentication.php like this
namespace somename\Middleware;
class Authentication {
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
but i get an error
Undefined property: somename\Middleware\Authentication::$logger in ***
I have also tried adding the following constructor to the class but I also get no joy.
private $container;
public function __construct($container) {
$this->container = $container;
}
Could anyone help please?
Best Practice to Middleware Implementation is Something like this :
Place this code inside your dependency section :
$app = new \Slim\App();
$container = $app->getContainer();
/** Container will be passed to your function automatically **/
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c);
};
then inside your Authentication class create constructor function like you mentioned :
namespace somename\Middleware;
class Authentication {
protected $container;
public function __invoke($request, $response, $next)
{
$this->container->logger->addInfo('Hi from Authentication middleware');
}
public function __construct($container) {
$this->container = $container;
}
/** Optional : Add __get magic method to easily use container
dependencies
without using the container name in code
so this code :
$this->container->logger->addInfo('Hi from Authentication middleware');
will be this :
$this->logger->addInfo('Hi from Authentication middleware');
**/
public function __get($property)
{
if ($this->container->{$property}) {
return $this->container->{$property};
}
}
}
After inside your index.php add Middleware using name resolution like this:
$app->add('MyAuthenticator');
I disagree with Ali Kaviani's Answer. When adding this PHP __magic function (__get), the code will be a lot more difficult to test.
All the required dependencies should be specified on the constructor.
The benefit is, that you can easily see what dependencies a class has and therefore only need to mock these classes in unit-tests, otherwise you would've to create a container in every test. Also Keep It Simple Stupid
I'll show that on the logger example:
class Authentication {
private $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function __invoke($request, $response, $next) {
$this->logger->addInfo('Hi from Authentication middleware');
}
}
Then add the middleware with the logger parameter to the container:
$app = new \Slim\App();
$container = $app->getContainer();
$container['MyAuthenticator'] = function($c) {
return new somename\Middleware\Authentication($c['logger']);
};
Note: the above registration to the container could be done automatically with using PHP-DI Slim (but that should be also slower).

Dependency injection not working with League\Route and League\Container

I'm building a web application right now and I'm facing a problem with my controller.
I want to send to my controller my League\Plate\Engine (registred in my Container) but I keep having the same error : Argument 3 passed to App\Controller\Main::index() must be an instance of League\Plates\Engine, array given
Here is my files :
dependencies.php
use League\Container\Container;
use Monolog\Handler\StreamHandler;
use Monolog\Logger;
use Yajra\Pdo\Oci8;
use League\Container\ReflectionContainer;
$container = new Container();
// Active auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies
// ...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
return $container;
routes.php
use League\Route\RouteCollection;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
Main.php
namespace App\Controller;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use League\Plates\Engine;
class Main
{
public function index(ServerRequestInterface $request, ResponseInterface $response, Engine $templates) {
//return $response->getBody()->write($this->templates->render('web::home'));
return $response;
}
}
Thank you in advance
EDIT
I've made a progress.
I extended the Main class to extends the abstract class BaseController which looks like this :
namespace App\Controller;
use League\Plates\Engine;
class BaseController
{
protected $templates;
public function __construct(Engine $templates) {
$this->templates = $templates;
}
}
The first error goes away, but another one show up. In the Main class, I would like to use the view object that I instanciate in the container, but the object passed to the constructor is an empty one :
Main.php
class Main extends BaseController
{
public function index(ServerRequestInterface $request, ResponseInterface $response) {
echo '<pre>'.print_r($this->templates,1).'</pre>'; // Return an empty Plate Engine object
return $response->getBody()->write($this->templates->render('web::home'));
//return $response;
}
}
And this doesn't explain why the first error shows up
EDIT 2
After some digging, I finally make it works, but I sense that something is wrong.
I replaced in the container the term view by the namespace of the Engine class :
$container->add('League\Plates\Engine', function () {
// The same as before
});
In Main.php I've updated the index function like this :
public function index(ServerRequestInterface $request, ResponseInterface $response) {
$body = $response->getBody();
$body->write($this->templates->render('web::home'));
return $response->withBody($body);
}
And the page doesn't throw a 500 error and the html file is displayed correctly.
But, what if I want to change the template engine by Twig for example ? This would mean I'll need to change all the call to $container->get('League\Plate\Engine'); by $container->get('What\Ever'); ? That's not very practical!
I probably missed something!
And the problem will rise again when I'll want to use my PDO object... or every other object.
Ok so I solved my problem by registering my Controllers classes in the container itself.
For example, for displaying the index page, the Main class call the index function. In my container, I call
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
To summary :
bootstap.php (called by index.php)
require __DIR__ . '/../../vendor/autoload.php';
$dotenv = new \Dotenv\Dotenv(__DIR__ . '/../');
$dotenv->load();
$config = new Config(__DIR__ . '/../config/');
$container = require __DIR__ . '/../dependencies.php';
$route = require __DIR__ . '/../routes.php';
$response = $route->dispatch($container->get('request'), $container->get('response'));
$container->get('emitter')->emit($response);
dependencies.php
$container = new Container();
// activate auto-wiring
$container->delegate(
new ReflectionContainer
);
// Others dependencies...
// Views
$container->add('view', function () {
$templates = new League\Plates\Engine();
$templates->addFolder('web', __DIR__ . '/templates/views/');
$templates->addFolder('emails', __DIR__ . '/templates/emails/');
// Extension
//$templates->loadExtension(new League\Plates\Extension\Asset('/path/to/public'));
//$templates->loadExtension(new League\Plates\Extension\URI($_SERVER['PATH_INFO']));
return $templates;
});
// THIS IS THE TRICK
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
// others controllers...
return $container;
routes.php
$route = new RouteCollection($container);
// Page index
$route->get('/', 'App\Controller\Main::index');
// Others routes...
return $route;
I have no idea why or how this work !
This works, because you are registering with the container your controller class, and telling the container to dependency inject your View class in the constructor..
The line you added, here, is doing that:
$container->add('App\Controller\Main')
->withArgument($container->get('view'));
The Hello World documentation example explains it perfectly.
http://container.thephpleague.com/3.x/constructor-injection/

phpSlim twg-view not working

i am getting error in my slim framework application. i don't know why twig-view is not working. twig-view is downloaded in vendor directory.
this is my index file
<?php
require __DIR__ . '/vendor/autoload.php';
// Settings
$config = [
'settings' => [
'displayErrorDetails' => true,
'addContentLengthHeader' => false,
],
];
$app = new \Slim\App($config);
// Get container
$container = $app->getContainer();
// Register component on container
$container['view'] = function ($container) {
$view = new \Slim\Views\Twig( __DIR__ . '/resources/views', [
'cache' => false
]);
// Instantiate and add Slim specific extension
$view->addExtension(new Slim\Views\TwigExtension(
$container['router'],
$container['request']->getUri()
));
return $view;
};
// Home
$app->get('/home','index');
function index($request, $response, $args)
{
return $this->view->render($response, 'home.twig'); // here is the error
}
$app->run();
i am getting error om $this keyword
error details
Details
Type: Error
Message: Using $this when not in object context
File: C:\xampp\htdocs\slim\api\index.php
Line: 42
It is not possible to use this when you do not have a closure
If you use a Closure instance as the route callback, the closure’s state is bound to the Container instance. This means you will have access to the DI container instance inside of the Closure via the $this keyword.
(Reference: http://www.slimframework.com/docs/objects/router.html)
You can separate it when you assign the closure to a variable
$indexRoute = function ($request, $response, $args)
{
return $this->view->render($response, 'home.twig'); // here is the error
}
$app->get('/home', $indexRoute);
You are declaring route incorectly, try
// This callback will process GET request to /index URL
$app->get('/index', function($request, $response, $args) {
return $this->view->render($response, 'home.twig');
});
Instead of declaring a function, you should call $app method to register a route.
EDIT
It is also possible to "separate" route declaration from the callback. You can create separate classes (a-la controllers in MVC pattern), like this:
// Declaring a controller class with __invoke method, so it acts as a function
class MyController
{
public function __invoke($request, $resposne)
{
// process a request, return response
}
}
// And here's how you add it to the route
$app->get('/index', 'MyController');
I suggest you read the appropriate section of the documentation. It's dead simple.

Slim pass class instance to $app

<?php
require 'vendor/autoload.php';
// Include all controllers
foreach(glob("controllers/*.php") as $controller)
{
include $controller;
}
// Instantiate a new Slip application
$app = new \Slim\Slim(array(
'debug' => true
));
// HOME CONTROLLER
$home = new Home;
$vr = $home->index();
// Register application routes
$app->get('/', function () {
echo $vr;
});
// Run application
$app->run();
This is my controller I want to use controllers and not keep everything in this single file. Anyhow I have a controllers map where I keep all my controllers. I automatically include them all at start however I can't seem to pass $home variable to get() method so I could call $vr indede it or $home->index()
You can pass it to your function like this:
..., function () use($home){
...
I think Slim also passes $app as the first argument to your function.
Edit: actually it doesn't according to its docs, so you'll have to pass that too inside the use statement (function arguments are URL parameters):
$app->get('/', function () use($home, $app) {
$vr = $home->index();
echo $vr;
// $app is accesible too...
});

Categories