Best design method to access container functions - php

I'm new to SLIM3 and followed the tutorial to get some functions in the container that I want to access from anywhere in the code. So here's my index.php file where I initalise everything:
<?php
use \Psr\Http\Message\ServerRequestInterface as Request;
use \Psr\Http\Message\ResponseInterface as Response;
// Require for loading the vendor libraries installed by composer
require 'vendor/autoload.php';
$config['displayErrorDetails'] = true;
$config['addContentLengthHeader'] = false;
$app = new \Slim\App(["settings" => $config]);
$container = $app->getContainer();
// Monolog initalisation. To use it write: $this->logger->addInfo("what you want to write here");
$container['logger'] = function($c) {
$logger = new \Monolog\Logger('eq_logger');
$file_handler = new \Monolog\Handler\StreamHandler("logs/app.log");
$logger->pushHandler($file_handler);
return $logger;
};
// Database connections
$container['dbteacher'] = function ($c) {
$pdo = new PDO($_SERVER['PGSQL_CONNECTION_STR']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
};
$container['dbagent'] = function ($c) {
$pdo = new PDO($_SERVER['PGSQL_CONNECTION_STR_AGENT']);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
return $pdo;
};
$app->post('/{controller}/{function}', function (Request $request, Response $response) {
$headers = $request->getHeaders();
$params = $request->getParsedBody();
$classname = $request->getAttribute('controller');
$controller = new $classname($this->logger);
$function = $request->getAttribute('function');
$result = $controller->$function($params);
$response->getBody()->write($result);
return $response;
});
$app->run();
Here I can access logger by typing $this->logger, same goes for the dbteacher and dbagent, but I can only do that inside where these containers are created, when I am calling another function from a different class I want to be able to access them as well but I don't want to pass them in the parameter because that will be hard to maintain, I also though about having a config.php class that initalises these containers and the $app variable and extending it in every class I use but that doesn't sound right.
What's the best way to approach this?

You should use the functions of the dependency injection container (Pimple) which Slim3 uses.
That being said, I want to say that dynamically creating "controller"s isn't that nice, that abstraction shouldn't be there and you should just do $response->getBody()->write($result); or the simpler method $response->write($result); in every controller. Also, I don't see why a whole routing framework is needed for this construct.
But anyway if you want to stay with that solution, you can use Pimple, I'll explain that on an example.
You have several classes with different constructor parameter:
class A {
public function __construct($logger) {}
}
class B {
public function __construct($logger, $myHelper) {}
}
Firstly you add them all to the Pimple container:
$container['A'] = function($c) { // or $container[A::class] for type safety
return new A($c['logger']);
};
$container['B'] = function($c) {
return new A($c['logger'], $c['myHelper']);
};
And then you can get them in your route by calling get on the container on the app instance.
$app->post('/{controller}/{function}', function (Request $request, Response $response) {
$headers = $request->getHeaders();
$params = $request->getParsedBody();
$classname = $request->getAttribute('controller');
$controller = $this->getContainer()->get($classname);
$function = $request->getAttribute('function');
$result = $controller->$function($params);
$response->getBody()->write($result);
return $response;
});

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.

Slim3 - Argument 1 passed to constructor must be instance of Slim\Views\Twig

I'm using Slim3, but I'm having an issue registering a dependency. According to the error the constructor I created expects the type of argument 1 to be Slim\Views\Twig.
The problem is I am passing an instance of Slim\Views\Twig - at least I think I am. I've not used Slim in a few months so I might be missing something obvious. Never the less I can't find the issue.
The error I'm getting is:
Catchable fatal error: Argument 1 passed to App\Controllers\RegistrationController::__construct() must be an instance of Slim\Views\Twig, instance of Slim\Container given
controllers/RegistrationController.php
<?php
namespace App\Controllers;
class RegistrationController {
protected $view;
public function __construct(\Slim\Views\Twig $view) {
$this->view = $view;
}
public function register($request, $response, $args) {
// Does some stuff ...
}
}
dependencies.php
<?php
use \App\Controllers\RegistrationController;
$container = $app->getContainer();
// Twig View
$container['view'] = function ($c) {
$settings = $c->get('settings')['renderer'];
$view = new \Slim\Views\Twig($settings['template_path']);
$basePath = rtrim(str_ireplace('index.php', '', $c['request']->getUri()->getBasePath()), '/');
$view->addExtension(new Slim\Views\TwigExtension($c['router'], $basePath));
return $view;
};
// monolog
$container['logger'] = function ($c) {
$settings = $c->get('settings')['logger'];
$logger = new Monolog\Logger($settings['name']);
$logger->pushProcessor(new Monolog\Processor\UidProcessor());
$logger->pushHandler(new Monolog\Handler\StreamHandler($settings['path'], $settings['level']));
return $logger;
};
// sqlite3
$container['db'] = function ($c) {
return new SQLite3(__DIR__ . '/../db/prod.db');
};
// Registration Controller
$container['RegistrationController'] = function($c) {
return new RegistrationController($c->get('view'));
};
route
$app->post('/signup', '\App\Controllers\RegistrationController:register');
Also tried the following:
$app->post('/signup', \App\Controllers\RegistrationController::class . ':register');
Any ideas?
The problem was I was defining the route incorrectly and I completely missed the section on Container Resolution
Here's how it should look:
$app->post('/signup', 'RegistrationController:register');

Pass arguments in Slim DI service

I have a service that I want to access from a route but pass arguments to.
$container = new \Slim\Container();
$container['myService'] = function($arg1, $arg2) {
//my code here
};
$app = new \Slim\App($container);
and inside my route, I try to call the service like so:
$this->myService('my arg1', 'my arg2');
This is not working. When I try to call it without specifying the arguments, it works.
How to call with arguments? Or is that an alternative way to specify a function or method to be called from inside a route?
so you are pretty close.
$container = new \Slim\Container();
$container['myService'] = function ($c) {
return function($arg1, $arg2) {
//my code here
}
};
$app = new \Slim\App($container);
$app->get('/', function ($req, $res, $args) {
$this->myService($a, $b);
});
This should work.
Optionally, with your original code... you have to save it to a variable first before invoking it.
$service = $this->myService;
$service('my arg1', 'my arg2');
Both of these should work.

Slim PHP Framework: can middleware be lazy-loaded?

While building a small Slim app, I noticed that all my middleware instances are getting created for every request. I have four routes in my app, only one of which requires a PDO instance, but all of which create an instance. Another route requires a SoapClient instance, but again, requests to any route result in instances being created. Can I do something differently to avoid that?
Here is a stripped down example showing what I mean:
<?php
require __DIR__ . '/../vendor/autoload.php';
class Middleware {
private $dep;
public function __construct($dep = null) {
$this->dep = $dep;
}
public function __invoke($req, $res) {
$res->getBody()->write($this->dep ? 'Got dependency' : 'No dependency');
return $res;
}
}
$app = new \Slim\App();
$c = $app->getContainer();
$c['pdo'] = function ($c) {
echo "PDO connection here<br/>\n";
return (object) ['pdo' => true];
};
$c['middleware.nodep'] = function ($c) {
return new Middleware();
};
$c['middleware.withdep'] = function ($c) {
return new Middleware($c['pdo']);
};
$app->get('/nopdo', function () {})->add($c['middleware.nodep']);
$app->get('/withpdo', function () {})->add($c['middleware.withdep']);
$app->run();
Requests to /withpdo get the following response, as you would expect:
PDO connection hereGot dependency
Requests to /nopdo get this, which I don't want:
PDO connection here No dependency
So, is there a way to only instantiate the "PDO" instance for requests to /withpdo?
You are calling the factory in add(), rather than just telling Slim about the DIC key.
i.e. change:
->add($c['middleware.withdep']);
to
->add('middleware.withdep');
This way, Slim will ask the DIC for 'middleware.withdep' when it needs it.

PHP Type Hinting is not working

I am trying to use Type Hinting feature in my app but something is not working correctly. I tried the following
define('PULSE_START', microtime(true));
require('../Pulse/Bootstrap/Bootstrap.php');
$app = new Application();
$app->run();
$app->get('/404', function(Application $app)
{
$app->error(404);
});
And instead of 404 output i got this
Catchable fatal error: Argument 1 passed to {closure}() must be an instance of Pulse\Core\Application, none given in E:\Server\xampp\htdocs\web\pulse\WWW\Index.php on line 23
I dont understand it, the Application class is a namespaced class (Pulse\Core\Application) but I have created an Alias, so I dont think thats the issue.
From the fact that none is being given as the passed in type value I'm thinking get isn't passing a parameter when using the closure. To get $app into the closure you can use the application instead.
$app->get('/404', function() use ($app)
{
$app->error(404);
});
And verify that your get method is passing $this as the first argument of the anonymous function.
Typehinting does not work that way - it requires parameter to be of given type, but you have to create code that will adjust parameters passed to the closure for yourself. Very simple implementation of such smart arguments:
class Application{
private $args = array(); //possible arguments for closure
public function __construct(){
$this->args[] = $this; //Application
$this->args[] = new Request;
$this->args[] = new Session;
$this->args[] = new DataBase;
}
public function get($function){
$rf = new ReflectionFunction($function);
$invokeArgs = array();
foreach($rf->getParameters() as $param){
$class = $param->getClass()->getName();
foreach($this->args as $arg) {
if(get_class($arg) == $class) {
$invokeArgs[] = $arg;
break;
}
}
}
return $rf->invokeArgs($invokeArgs);
}
}
$app = new Application();
$app->get(function (Application $app){
var_dump($app);
});

Categories