Short story: I can not get method injection working with Laravel container installed using composer (https://packagist.org/packages/illuminate/container). Injection only works if used in the constructor of objects. For example:
class SomeClass {
function __construct(InjectedClassWorksHere $obj) {}
function someFunction(InjectedClassFailsHere $obj) {}
}
Long story: I was looking at re-factoring a major project to using Laravel, but due to business pressure, I am not able to invest in the time I would like. In an effort to not throw the "baby out with the bath water", I am using the individual Laravel components to up the elegance of the code being developed in the old branch. One of my favourite new techniques I picked up when evaluating Laravel was the concept of dependency injection. I was delighted to find out later that I could use that OUTSIDE of a Laravel project. I now have this working and all is well, except the dev version of the container found online does not seem to support method injection.
Has anyone else been able to get the container to work and do method injection outside of a Laravel project?
My approach so far...
composer.json
"illuminate/support": "5.0.*@dev",
"illuminate/container": "5.0.*@dev",
Application bootstrap code:
use Illuminate\Container\Container;
$container = new Container();
$container->bind('app', self::$container); //not sure if this is necessary
$dispatcher = $container->make('MyCustomDispatcher');
$dispatcher->call('some URL params to find controller');
With the above, I am able to inject in constructors of my controllers, but not their methods methods. What am I missing?
Full source... (C:\workspace\LMS>php cmd\test_container.php)
<?php
// This sets up my include path and calls the composer autoloader
require_once "bare_init.php";
use Illuminate\Container\Container;
use Illuminate\Support\ClassLoader;
use Illuminate\Support\Facades\Facade;
// Get a reference to the root of the includes directory
$basePath = dirname(dirname(__FILE__));
ClassLoader::register();
ClassLoader::addDirectories([
$basePath
]);
$container = new Container();
$container->bind('app', $container);
$container->bind('path.base', $basePath);
class One {
public $two;
public $say = 'hi';
function __construct(Two $two) {
$this->two = $two;
}
}
Class Two {
public $some = 'thing';
public function doStuff(One $one) {
return $one->say;
}
}
/* @var $one One */
$one = $container->make(One);
var_dump($one);
print $one->two->doStuff();
When I run the above, I get...
C:\workspace\LMS>php cmd\test_container.php
object(One)#9 (2) {
["two"]=>
object(Two)#11 (1) {
["some"]=>
string(5) "thing"
}
["say"]=>
string(2) "hi"
}
PHP Catchable fatal error: Argument 1 passed to Two::doStuff() must be an instance of One, none
given, called in C:\workspace\LMS\cmd\test_container.php on line 41
and defined in C:\workspace\LMS\cmd\test_container.php on line 33
Catchable fatal error: Argument 1 passed to Two::doStuff() must be an instance of One, none
given, called in C:\workspace\LMS\cmd\test_container.php on line 41 and
defined in C:\workspace\LMS\cmd\test_container.php on line 33
Or, a more basic example that illustrates the injection working in a constructor but not a method...
class One {
function __construct(Two $two) {}
public function doStuff(Three $three) {}
}
class Two {}
class Three {}
$one = $container->make(One); // totally fine. Injection works
$one->doStuff(); // Throws Exception. (sad trombone)
Simply pass the instance of One
into your call to Two
:
$one = $container->make('One');
var_dump($one);
print $one->two->doStuff($one);
returns...
object(One)#8 (2) {
["two"]=>
object(Two)#10 (1) {
["some"]=>
string(5) "thing"
}
["say"]=>
string(2) "hi"
}
hi
As mentioned below, in Laravel 5.0, method injection is only available on routes and controllers. So you can pull those into your project as well, and get a little more Laravel-y in the process. Here's how:
In composer.json
, need to add in illuminate/routing
and illuminate/events
:
{
"require-dev": {
"illuminate/contracts": "5.0.*@dev",
"illuminate/support": "5.0.*@dev",
"illuminate/container": "5.0.*@dev",
"illuminate/routing": "5.0.*@dev",
"illuminate/events": "5.0.*@dev"
},
"autoload": {
"psr-4": {
"App\\": "app/"
}
},
"minimum-stability": "dev",
"prefer-stable": true
}
In routing.php
, set up Laravel's routing and controller services:
/**
* routing.php
*
* Sets up Laravel's routing and controllers
*
* adapted from http://www.gufran.me/post/laravel-components
* and http://www.gufran.me/post/laravel-illuminate-router-package-in-your-application
*/
$basePath = str_finish(dirname(__FILE__), '/app/');
$controllersDirectory = $basePath . 'Controllers';
// Register directories into the autoloader
Illuminate\Support\ClassLoader::register();
Illuminate\Support\ClassLoader::addDirectories($controllersDirectory);
// Instantiate the container
$app = new Illuminate\Container\Container();
$app['env'] = 'production';
$app->bind('app', $app); // optional
$app->bind('path.base', $basePath); // optional
// Register service providers
with (new Illuminate\Events\EventServiceProvider($app))->register();
with (new Illuminate\Routing\RoutingServiceProvider($app))->register();
require $basePath . 'routes.php';
$request = Illuminate\Http\Request::createFromGlobals();
$response = $app['router']->dispatch($request);
$response->send();
In Controllers/One.php
, create the class as a Controller, so we can use L5's method injection:
/**
* Controllers/One.php
*/
Class One extends Illuminate\Routing\Controller {
public $some = 'thingOne';
public $two;
public $three;
function __construct(Two $two) {
$this->two = $two;
echo('<pre>');
var_dump ($two);
echo ($two->doStuffWithTwo().'<br><br>');
}
public function doStuff(Three $three) {
var_dump ($three);
return ($three->doStuffWithThree());
}
}
In routes.php
, define our test route:
$app['router']->get('/', 'One@dostuff');
Finally, in index.php
, boot everything up and define our classes to test the dependency injection:
/**
* index.php
*/
// turn on error reporting
ini_set('display_errors',1);
error_reporting(E_ALL);
require 'vendor/autoload.php';
require 'routing.php';
// the classes we wish to inject
Class Two {
public $some = 'thing Two';
public function doStuffWithTwo() {
return ('Doing stuff with Two');
}
}
Class Three {
public $some = 'thing Three';
public function doStuffWithThree() {
return ('Doing stuff with Three');
}
}
Hit index.php and you should get this:
object(Two)#40 (1) {
["some"]=>
string(9) "thing Two"
}
Doing stuff with Two
object(Three)#41 (1) {
["some"]=>
string(11) "thing Three"
}
Doing stuff with Three
Some notes...
$one->doStuff();
, with the empty parameter that throws the exception (since doStuff
is expecting an instance). Instead, the router calls doStuff
and resolves the IoC container for us.If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With