Injecting in the constructor when using pimple - php

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?

Related

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

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...");
}
}

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.

Injecting parameters into constructor with PHP-DI

I am struggling to get dependency injection to work the way I expect -
I am trying to inject a class, Api, which needs to know which server to connect to for a particular user. This means that overriding constructor properties in a config file is useless, as each user may need to connect to a different server.
class MyController {
private $api;
public function __construct(Api $api) {
$this->api = $api;
}
}
class Api {
private $userServerIp;
public function __construct($serverip) {
$this->userServerIp = $serverip;
}
}
How can I inject this class with the correct parameters? Is it possible to override the definition somehow? Is there some way of getting the class by calling the container with parameters?
To (hopefully) clarify - I'm trying to call the container to instantiate an object, while passing to it the parameters that would otherwise be in a definition.
Since IP depends on the user you probably have some piece of logic that does the user=>serverIP mapping. It might be reading from the db or simple id-based sharding, or whatever. With that logic you can build ApiFactory service that creates Api for a particular user:
class ApiFactory {
private function getIp(User $user) {
// simple sharding between 2 servers based on user id
// in a real app this logic is probably more complex - so you will extract it into a separate class
$ips = ['api1.example.com', 'api2.example.com'];
$i = $user->id % 2;
return $ips[$i];
}
public function createForUser(User $user) {
return new Api($this->getIp($user);
}
}
Now instead of injecting Api into your controller you can inject ApiFactory (assuming your controller knows the user for which it needs the Api instance)
class MyController {
private $apiFactory;
public function __construct(ApiFactory $apiFactory) {
$this->apiFactory = $apiFactory;
}
public function someAction() {
$currentUser = ... // somehow get the user - might be provided by your framework, or might be injected as well
$api = $this->apiFactory->createForUser($currentUser);
$api->makeSomeCall();
}
}
I am not sure I understand your question fully, but you can configure your Api class like this:
return [
'Foo' => function () {
return new Api('127.0.0.1');
},
];
Have a look at the documentation for more examples or details: http://php-di.org/doc/php-definitions.html
Edit:
return [
'foo1' => function () {
return new Api('127.0.0.1');
},
'foo2' => function () {
return new Api('127.0.0.2');
},
];

How to inject framework into custom class?

I'm currently developing an application with the FlightPHP framework and wondering how am I able to inject FlightPHP into my custom class so that I am able to use specific classes I have injected into it's dependency container.
use Flight;
use Logger;
class DB{
public function __construct(...){
$this->app = $app; // Flight:: instance
}
public function doStuff($stuff){
return $this->app->log()->function($stuff);
}
}
Flight::register('log', 'Logger', ['app'], function($log) {
return $log->pushHandler(new StreamHandler('app.log'));
});
Flight::register('database', 'DB', array($data), function($db) {
return $db;
});
I'm attempting to inject Flight into my database class constructor so that I am able to use the log function which was previously injected into the Flight dependency container.
The "Logger" works in the index.php when used under the Flight instance "Flight::log()->function("test");", however when I attempt to use it in another scope(within the Database class), it doesn't allow me to use it in the context of "Flight".
Update:
Flight::register('log', 'Monolog\Logger', ['app'], function($log) {
return $log->pushHandler(new StreamHandler('app.log'));
});
class DB{
function __construct(Monolog\Logger $engine){
#var_dump($engine);
$engine->addInfo("injected"); // works
}
}
Flight::register('database', 'DB', array(Flight::log()), function($db) {
return $db;
});
Flight::database();
Is correct usage?
You could pass instance of \Flight\Engine in the array of third parameter at register method to pass framework instance in you DB controller. \Flight\Engine does not use interface sou you are coupling your code with framework implementation I guess. In this case you can use Flight::app() everywhere to obtain framework instance.
<?php error_reporting(E_ALL);
require 'vendor/autoload.php';
class DB
{
function __construct(\Flight\Engine $engine)
{
var_dump($engine->get('connectionString'));
}
}
Flight::set('connectionString', 'mssql');
Flight::register('database', 'DB', array(Flight::app()), function($db) {
return $db;
});
Flight::database();
Looks like that Flight does not have such a concept as Dependency Injection Container. You have to specify your parameter values explicitly.
Update:
By doing this ['app'] you are injecting string into constructor of Monolog\Logger. This line return $log->pushHandler(new StreamHandler('app.log')); should raise error.
Read more carefully http://flightphp.com/learn

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.

Categories