laravel - dependency injection and the IoC Container - php

I'm trying to wrap my head around dependency injection and the IoC container and i'm using my UserController as an example. I'm defining what the UserController depends on in its constructor and then am binding those objects to it using App::bind(). If i'm using the Input::get() facade/method/thing am i not taking advantage of the Request object i just injected into it? Should i be using the following code instead, now that the Request object is injected or doesInput::get() resolve to the same Request instance? I'd like to use the static facades but not if they resolve to un-injected objects.
$this->request->get('email');
Dependency injection
<?php
App::bind('UserController', function() {
$controller = new UserController(
new Response,
App::make('request'),
App::make('view'),
App::make('validator'),
App::make('hash'),
new User
);
return $controller;
});
UserController
<?php
class UserController extends BaseController {
protected $response;
protected $request;
protected $validator;
protected $hasher;
protected $user;
protected $view;
public function __construct(
Response $response,
\Illuminate\Http\Request $request,
\Illuminate\View\Environment $view,
\Illuminate\Validation\Factory $validator,
\Illuminate\Hashing\BcryptHasher $hasher,
User $user
){
$this->response = $response;
$this->request = $request;
$this->view = $view;
$this->validator = $validator;
$this->hasher = $hasher;
$this->user = $user;
}
public function index()
{
//should i use this?
$email = Input::get('email');
//or this?
$email = $this->request->get('email');
//should i use this?
return $this->view->make('users.login');
//or this?
return View::make('users.login');
}

If you're worried about a testability thing then you should really only be injecting instances that aren't being routed through a facade, as the facades themselves are testable already (meaning you can mock these in L4). You shouldn't need to inject the response, hasher, view environment, request, etc. By the looks of it you only need to be injecting the $user.
For everything else I'd just stick to using the static facade. Check out the swap and shouldReceive methods on the base Facade class. You can easily swap out the underlying instance with your own mocked object or simply start mocking with, for example, View::shouldReceive().
Hope this helps.

Related

Slim Framework 3 - How to inject $logger to route controller

I am using Slim Framework 3. I want to inject $logger defined in dependencies.php into a Router Controller class. Below is what I do, is there a better way?
routes.php
$app->get('/test', function($request, $response, $args){
$controller = new AccountController($this->get('logger'));
return $controller->test($request, $response, $args);
});
AccountController
class AccountController{
private $logger;
function __construct($logger){
$this->logger = $logger;
}
public function test($request, $response, $args){
$this->logger->info('i am inside controller');
return $response->withHeader('Content-Type', 'application/json')->write('test');
}
}
In Slim Framework 3 documentation, the proper way of using a Route Controller should be:
$app->get('/test', 'AccountController:test');
But how do I inject $logger into AccountController when I choose to code my Route Controller in this more "elegant" way?
In terms of making your controller easier to test, you should inject the logger into the controller via the constructor.
AccountController looks like this:
class AccountController
{
protected $logger;
public function __construct($logger) {
$this->logger = $logger;
}
public function test($request, $response, $args){
$this->logger->info('i am inside controller');
return $response->withJson(['foo' => 'bar']);
}
}
Set up in index.php is something like:
$container = $app->getContainer();
$container[Logger::class] = function ($c) {
$logger = new \Monolog\Logger('logger');
return $logger;
};
$container[AccountController::class] = function ($c) {
$logger = $c->get(Logger::class);
return new AccountController($logger);
};
$app->get('/test', 'AccountController:test');
Note that if you make the format route callable be a string of 'class name' colon 'method name', then Slim 3 will call the method for you after extracting the controller class from the DI container. If the class name is not a registered key with the container, then it will instantiate it and pass the container to the constructor.
According to the container resolution docs, you should be able to access your logger through the container, inside your controller:
AccountController
class AccountController
{
protected $ci;
//Constructor
public function __construct(ContainerInterface $ci)
{
$this->ci = $ci;
}
public function test($request, $response, $args)
{
$this->ci->get('logger')->info('i am inside controller');
return $response->withHeader('Content-Type', 'application/json')->write('test');
}
}
When you call $app->get('/test', 'AccountController:test');, Slim should automatically pass the container into AccountController's constructor.
That being said, this is more of a convenience feature than an example of great design. As Rob Allen explains in his answer, you can achieve better modularity, and thus more easily tested code (if you're using unit tests), by injecting the controllers into the application container, rather than injecting the container into each controller.
Take a look at his example Slim application. If you look at, for example AuthorController, you can see how with this design controller classes no longer depend on the magical container providing all the services. Instead, you explicitly state which services each controller will need in the constructor. This means you can more easily mock the individual dependencies in testing scenarios.

Silex Controller Dependency Injection

I'm trying to inject a dependency into my Silex Controller, because I need an object of type user in my controller and handle some stuff with it.
$app->mount("/users", new \MyApp\Controller\Provider\User($user));
And I implemented the controller by implementing the ControllerProviderInterface:
class User implements ControllerProviderInterface{
protected $user;
public function __construct($user){
//...
}
public function connect(Application $app)
{
//...
}
}
The routes and the methods are all set up in the controller. Without the dependency injection everything works fine. But as long as I edit the code and add the injection I get the following error:
Missing argument 1 for ...::__construct()
When I create the object, I send that parameter to it, but somehow Silex creates an instance before with a constructor without passing any argument.
Another approach is to use a ServiceController instead of a ControllerProvider. You can achieve a more familiar dependency injection feel this way.
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app["user"] = function () {
return new User();
};
$app["user.controller"] = function () use ($app) {
return new UserController($app["user"]);
};
$app->get("/users/{id}", "user.controller:get");
...
class User implements UserInterface
{
// ...
}
...
class UserController
{
protected $user;
public function __construct(UserInterface $user)
{
$this->user = $user;
}
public function get(Request $request, $id)
{
$this->user;
// Do stuff
}
}
Reference: http://silex.sensiolabs.org/doc/providers/service_controller.html
Silex takes a different approach to dependency injection than you might be used to or might expect. Silex\Application is the dependency injection container that is available in almost any context in a Silex application. You register your dependencies with the $app and those dependencies are injected via the $app in any context you might need it. Here is an example of something you might do in your situation.
$app["user"] = function () {
return new \MyApp\Service\User();
};
$app->mount("/users", new \MyApp\Controller\Provider\User());
...
class User implements ControllerProviderInterface
{
public function connect(Application $app)
{
$controller = $app["controller_factory"];
$controller->get("/{id}", array($this, "get"));
return $controller;
}
public function get(Application $app, Request $request, $id)
{
$user = $app["user"];
// Do stuff
}
}
Dependency injection in Silex is different and it takes a little getting used to, but once you are comfortable with it, it's a pleasure to work with and it's very efficient.
This accepted answer is not the answer to the question. And the thing described in the answer is not dependency injection either, or a realy crude form of it. Its more like Hardcoding $app into your controllers.
The code in the question should work without changes. The code in the connect function and the constructor might be helpful to figure out, where your problem sits. Its not the call to mount, im sure.

Custom BaseController in Silex

I have created a simple aplication in Silex 1.3.4 and I want to have a base controller that will have a __construct method accepting $app and $request. All inheriting controllers then should have their respective constructors and calling the parent controller construct method.
//Use statements here....
class AppController
{
public function __construct(Application $app, Request $request){
$this->app = $app;
$this->request = $request;
}
}
Inheriting controllers would be written as below:
//Use statements here....
class ItemsController extends AppController
{
public function __construct(Application $app, Request $request){
parent::__construct($app, $request);
}
public function listAction()
{
//code here without having to pass the application and request objects
}
}
The approach I have decided on routing is as shown below:
$app->post(
'/items/list', 'MySilexTestDrive\Controller\ItemsController::listAction'
)->bind('list');
I was thinking of using the dispatcher and override some processes there and create my controller instances my own way but I do not have any idea how and if this is a great idea at all.
Anyone who has done something similar to this? Please help.
You can use ServiceControllerServiceProvider to define your controller as a service in the application. But you can't inject a Request in that way. BTW you can have more than one request and the request instance can change if you do sub-request. You can inject RequestStack instead, then call $requestStack->getCurrentRequest() when you need to get the current request.
$app = new Silex\Application();
abstract class AppController
{
protected $app;
protected $requestStack;
public function __construct(Silex\Application $app, Symfony\Component\HttpFoundation\RequestStack $requestStack)
{
$this->app = $app;
$this->requestStack = $requestStack;
}
public function getRequest()
{
return $this->requestStack->getCurrentRequest();
}
}
class ItemsController extends AppController
{
public function listAction()
{
$request = $this->getRequest();
// ...
}
}
$app->register(new Silex\Provider\ServiceControllerServiceProvider());
$app['items.controller'] = $app->share(function() use ($app) {
return new ItemsController($app, $app['request_stack']);
});
$app->get('/items/list', "items.controller:listAction");
It makes sense to do such a thing? I do not think so. Especially if the framework gives you a request instance thanks to the type hinting. Just do
public function listAction(Application $app, Request $request)
{
// ...
}
and work with that.
You can try this too :
class BaseController
{
protected $app;
protected $request;
public function __call($name, $arguments)
{
$this->app = $arguments[0];
$this->request = $arguments[1];
return call_user_func_array(array($this,$name), [$arguments[0], $arguments[1]]);
}
protected function getSystemStatus(Application $app, Request $request)
{
[...]
}
[...]
}
#Rabbis and #Federico I have come up with a more elegant solution for this where I have created a BeforeControllerExecuteListener that I dispatch with my application instance. This listener accepts the FilterControllerEvent object and then from my base controller I call a method where I inject both the Silex Application and the request from the event.
public function onKernelController(FilterControllerEvent $event)
{
$collection = $event->getController();
$controller = $collection[0];
if($controller instanceof BaseControllerAwareInterface){
$controller->initialize($this->app, $event->getRequest());
}
}
The I simple dispatch this in my bootstrap file as shown below:
$app['dispatcher']->addSubscriber(new BeforeControllerExecuteListener($app));
This allows me to have access to this object without having to add them as parameters on my actions. Below is how one of my actions in the making looks:
public function listAction($customer)
{
$connection = $this->getApplication()['dbs']['db_orders'];
$orders= $connection->fetchAll($sqlQuery);
$results = array();
foreach($orders as $order){
$results[$order['id']] = $order['number'] . ' (' . $order['customer'] . ')';
}
return new JsonResponse($results);
}
If the currently running controller being called honors the BaseControllerAwareInterface interface as I have defined it then it means I should inject that controller with the Application and Request instances. I leave the controllers to deal with how they manage the Response of each action as with my example above I may need the Response object itself of JsonResponse even any other type of response so it entirely depends on the controller to take care of that.
Then the routing remains as simply as:
$app->match('/orders/list/{cusstomer}', 'Luyanda\Controller\OrdersController::listAction')
->bind('list-orders');

Laravel mock with Mockery Eloquent models

I'm developing a PHP (5.4.25) application with laravel(4.2) framework. I'd like test my UserController with Mockery, so I've fit my UserController in this way:
class UsersController extends \BaseController {
protected $user;
public function __construct(User $user) {
$this->user = $user;
$this->beforeFilter('csrf', array('on'=>'post'));
}
public function store() {
$validator = Validator::make(Input::all(), User::$rules);
if ( $validator->passes() ) {
$this->user->username = Input::get('username');
$this->user->password = Hash::make(Input::get('password'));
$this->user->first_name = Input::get('first_name');
$this->user->last_name = Input::get('last_name');
$this->user->email = Input::get('email');
$this->user->save();
return true;
} else {
return false;
}
}
I want mock Eloquent User model so i develop my UsersControllerTest so:
class UsersControllerTest extends TestCase {
private $mock;
public function __construct() {}
public function setUp() {
parent::setUp();
$this->createApplication();
}
public function tearDown() {
parent::tearDown();
Mockery::close();
}
public function testStore() {
$this->mock = Mockery::mock('Eloquent','User[save]');
$this->mock
->shouldReceive('save')
->once()
->andReturn('true');
$this->app->instance('User', $this->mock);
$data['username'] = 'qwerety';
$data['first_name'] = 'asd';
$data['last_name'] = 'asd123';
$data['email'] = 'test#gmail.com';
$data['password'] = 'password';
$data['password_confirmation'] = 'password';
$response = $this->call('POST', 'users', $data);
var_dump($response->getContent());
}
}
When I run my test it returns me this error:
Mockery\Exception\InvalidCountException : Method save() from Mockery_0_User should be called
exactly 1 times but called 0 times.
Why? What's wrong?
EDIT: I found the problem - If I don't use mock object all works fine and the controller create a new user in the DB, but when I use mock the Input:all() method returns empty array.
--
Thanks
i had this same issue when i started testing.....
the thing there is that, in your userscontroller store method you are actually saving the user to the database and base on your code it might work just fine but you are surprise that it is actually failing the test.
Now think of it this way, you mock the user, and you told your test that when ever i call User model, please give me the mock version of my user, like you did in your code line below
$this->app->instance('User', $this->mock);
Now the problem is that you need to also call the mock version of save() method from the through the mock version of User like so:
$this->mock->save();
Consider the following Example:
public function testStore()
{
$input = ['name', 'Canaan'];
$this->mock
->shouldReceive('create')
->once()->with($input);
$this->app->instance('User', $this->mock);
$this->mock->create($input);
$this->call('POST', 'users', $input);
}
i hope it helps you.
The way you are testing this, in the controller constructor is passed an instance of the real Eloquent model, not the mock. If you want to test the facades (as clearly stated in the documentation) you should call the shouldReceive() method directly on the facade to have it mocked.
But, since you are redefining the $this->user variable in the store() method of the controller, it will not be called, unless you remove the hardcoded instantiation and use the injected $user.
Edit: i overlooke the $this->app->instance('User', $this->mock); line. So, the problem may be due the fact that in the store method you are getting a new class instance directly, and not via the Laravel IoC container. instead of $this->user = new User; in your store method, you should get it via App::make('User');
The problem was in $this->createApplication();.
I have commented that line and Input::all() works fine with all input parameters!

Is it possible to pass a route parameter to controller constructor in Laravel?

Is it possible to inject a route-paramter (or an route segment) to the controller-constructor?
You find some code to clarify my question.
class TestController{
protected $_param;
public function __construct($paramFromRoute)
{
$this->param = $paramFromRoute;
}
public function testAction()
{
return "Hello ".$this->_param;
}
}
----------------------------------------------------
App::bind('TestController', function($app, $paramFromRoute){
$controller = new TestController($paramFromRoute);
return $controller;
});
----------------------------------------------------
// here should be some magic
Route::get('foo/{bar}', 'TestController');
It's not possible to inject them, but you have access to all of them via:
class TestController{
protected $_param;
public function __construct()
{
$id = Route::current()->getParameter('id');
}
}
Laravel 5.3.28
You can't inject the parameter...
But, you can inject the request and get it from the router instance, like this:
//route: url_to_controller/{param}
public function __construct(Request $request)
{
$this->param = $request->route()->parameter('param');
}
In Laravel 5.4, you can use this to request the parameter:
public function __construct(Request $request) {
$id = $request->get("id");
}
If you want a more testable solution, you can use Service Provider power.
$this->app->bind(TestController::class, function ($app) {
return new TestController(request()->testParam);
});
UPDATE FOR LARAVEL 8
you can use the route() method to get the value from the route url parameter from laravel 8:
$id = request()->route('id')
Lastly, but most importantly, you may simply "type-hint" the dependency in the constructor of a class that is resolved by the container, including controllers, event listeners, queue jobs, middleware, and more. In practice, this is how most of your objects are resolved by the container.
http://www.golaravel.com/laravel/docs/5.1/container/

Categories