Why does laravel IoC does not provisioning my class with my method? - php

I can't get why laravel tries to create my class itself, without using my method. I can see that IoC binding is executed (POINT 1 is shown). But singleton method is being never executed. Why?
In my service provider (not deferred):
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
}
I try to use typehinting to resolve dependencies when creating a class:
class Test
{
public function __construct(FooBar $fooBar)
{
}
}
I see that laravel tries to create FooBar to inject it, but can't resolve FooBar's dependencies. They could be resolved, if laravel would call service provider callback, but it does not. Why? How to make laravel use that callback for that class?

Instead of closure (that will not work), use boot() method to initiate your service.
/**
* #param \App\Services\FooBar $foobar
*/
public function boot(\App\Services\FooBar $foobar)
{
$foobar->setOptions(['option' => 'value']);
}
It will launch right after service will be instantiated.

It is because when you are binding a class to IoC container you are not immediately calling the closure. Instead when you need to actually do some action on your class from container you call App::make('class') which would fire the closure and give you the value that was returned from it. So for example
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
echo "POINT 1"; // I can see this one
$this->app->singleton(\App\Services\FooBar::class, function($app)
{
echo "POINT 2\n"; // Does not comes here
return new FooBar($params);
});
$this->app->make(\App\Services\FooBar::class); //here POINT 2 will be called first.
}

Related

Mocking interface dependency of controllers in Laravel testing

I'm just trying to test my Laravel app. When coding the project, I was trying to take care about "fat services, skinny controllers" principle, so every logic (including DB logic) is extracted to service classes with interfaces, and injected into the controllers. Then, the dependencies are resolved by IoC container provided by Laravel.
My question is regarding to mocking out these interface dependencies when testing the controllers. To me, it seems like the dependency injection is never properly resolved by the test, it is always using the implementation which is injected by the IoC Container, and not the fake one.
Example Controller Method
public function index(ICrudService $crudService)
{
if (!\Auth::check()) {
return redirect()->route('login');
}
$comps = $crudService->GetAllCompetitions();
return view('competitions.index', ['competitions' => $comps]);
}
setUp method
protected function setUp()
{
parent::setUp();
$this->faker = Faker\Factory::create();
// Create the mock objects
$this->user = \Mockery::mock(User::class);
$this->allComps = new Collection();
for ($i=0; $i<10; $i++)
{
$this->allComps->add(new Competition(['comp_id' => ++$i,
'comp_name' => $this->faker->unique()->company,
'comp_date' => "2018-11-07 17:25:41"]));
}
$this->user->shouldReceive('getAuthIdentifier')
->andReturn(1);
$this->competitionFake = \Mockery::mock(Competition::class);
// Resolve every Competition params with this fake
$this->app->instance(Competition::class, $this->competitionFake);
}
The test
public function test_competition_index()
{
// Mock the Crud Service.
$fakeCrud = \Mockery::mock(ICrudService::class);
// Register the fake Crud Service to DI container.
$this->app->instance(ICrudService::class, $fakeCrud);
// Mock GetALllCompetitions method to return the fake collection.
$fakeCrud->shouldReceive('GetAllCompetitions')
->once()
->andReturn
($this->allComps);
// Authenticate the mock user.
$this->be($this->user);
// Send the request to the route, and assert if the view has competitions array.
$this->actingAs($this->user)->get('competitions')->assertStatus(200);
}
CrudServiceProvider
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('App\Services\Interfaces\ICrudService', function () {
return new CommonCrudService();
});
}
/**
* Get the services provided by the provider.
*
* #return array
*/
public function provides()
{
return ['App\Services\Interfaces\ICrudService'];
}
The behaviour
The test fails because of HTTP response 500 instead of 200. When debugging, I could see that the controller is still using the CommonCrudService class provided by the ServiceProvider, and not the fake one. If I comment out the CrudServiceProvider, the fake service is being passed to the controller, and returns the collection that I specified. Of course, I want to keep the container for my application.
Had anyone experienced things like this?
Thanks a lot in advance!

Laravel caching authenticated user's relationships

In my application I use Laravel's authentication system and I use dependency injection (or the Facade) to access the logged in user. I tend to make the logged in user accessible through my base controller so I can access it easily in my child classes:
class Controller extends BaseController
{
protected $user;
public function __construct()
{
$this->user = \Auth::user();
}
}
My user has a number of different relationships, that I tend to eager load like this:
$this->user->load(['relationshipOne', 'relationshipTwo']);
As in this project I'm expecting to receive consistently high volumes of traffic, I want to make the application run as smoothly and efficiently as possible so I am looking to implement some caching.
I ideally, need to be able to avoid repeatedly querying the database, particularly for the user's related records. As such I need to look into caching the user object, after loading relationships.
I had the idea to do something like this:
public function __construct()
{
$userId = \Auth::id();
if (!is_null($userId)) {
$this->user = \Cache::remember("user-{$userId}", 60, function() use($userId) {
return User::with(['relationshipOne', 'relationshipTwo'])->find($userId);
});
}
}
However, I'm unsure whether or not it's safe to rely on whether or not \Auth::id() returning a non-null value to pass authentication. Has anyone faced any similar issues?
I would suggest you used a package like the following one. https://github.com/spatie/laravel-responsecache
It caches the response and you can use it for more than just the user object.
Well, after some messing about I've come up with kind of a solution for myself which I thought I would share.
I thought I would give up on caching the actual User object, and just let the authentication happen as normal and just focus on trying to cache the user's relations. This feels like quite a dirty way to do it, since my logic is in the model:
class User extends Model
{
// ..
/**
* This is the relationship I want to cache
*/
public function related()
{
return $this->hasMany(Related::class);
}
/**
* This method can be used when we want to utilise a cache
*/
public function getRelated()
{
return \Cache::remember("relatedByUser({$this->id})", 60, function() {
return $this->related;
});
}
/**
* Do something with the cached relationship
*/
public function totalRelated()
{
return $this->getRelated()->count();
}
}
In my case, I needed to be able to cache the related items inside the User model because I had some methods inside the user that would use that relationship. Like in the pretty trivial example of the totalRelated method above (My project is a bit more complex).
Of course, if I didn't have internal methods like that on my User model it would have been just as easy to call the relationship from outside my model and cache that (In a controller for example)
class MyController extends Controller
{
public function index()
{
$related = \Cache::remember("relatedByUser({$this->user->id})", 60, function() {
return $this->user->related;
});
// Do something with the $related items...
}
}
Again, this doesn't feel like the best solution to me and I am open to try other suggestions.
Cheers
Edit: I've went a step further and implemented a couple of methods on my parent Model class to help with caching relationships and implemented getter methods for all my relatonships that accept a $useCache parameter, to make things a bit more flexible:
Parent Model class:
class Model extends BaseModel
{
/**
* Helper method to get a value from the cache if it exists, or using the provided closure, caching the result for
* the default cache time.
*
* #param $key
* #param Closure|null $callback
* #return mixed
*/
protected function cacheRemember($key, Closure $callback = null)
{
return Cache::remember($key, Cache::getDefaultCacheTime(), $callback);
}
/**
* Another helper method to either run a closure to get a value, or if useCache is true, attempt to get the value
* from the cache, using the provided key and the closure as a means of getting the value if it doesn't exist.
*
* #param $useCache
* #param $key
* #param Closure $callback
* #return mixed
*/
protected function getOrCacheRemember($useCache, $key, Closure $callback)
{
return !$useCache ? $callback() : $this->cacheRemember($key, $callback);
}
}
My User class:
class User extends Model
{
public function related()
{
return $this->hasMany(Related::class);
}
public function getRelated($useCache = false)
{
return $this->getOrCacheRemember($useCache, "relatedByUser({$this->id})", function() {
return $this->related;
});
}
}
Usage:
$related = $user->getRelated(); // Gets related from the database
$relatedTwo = $user->getRelated(true); // Gets related from the cache if present (Or from database and caches result)

Laravel ioc automatic resolution - works from controller but not from custom class

Namespaces omitted for brevity...
I have written the following service provider and registered in config/app.php:
class OfferServiceProvider extends ServiceProvider
{
public function register()
{
$this->registerLossControlManager();
}
protected function registerLossControlManager()
{
$this->app->bind('LossControlInterface', 'LossControl');
}
}
Here is my LossControlInterface
interface LossControlInterface
{
/**
* #param int $demandId
* #param float $offerTotal
* #param float $productTotal
* #param null|int $partnerId
* #return mixed
*/
public function make($demandId, $offerTotal, $productTotal, $partnerId = null);
/**
* #return float
*/
public function getAcceptableLoss();
/**
* #return bool
*/
public function isAcceptable();
/**
* #return bool
*/
public function isUnacceptable();
/**
* #return null
*/
public function reject();
}
Now within the controller, I can inject the LossController as follows:
use LossControlInterface as LossControl;
class HomeController extends BaseController {
public function __construct(LossControl $lossControl)
{
$this->lossControl = $lossControl;
}
public function getLossThresholds()
{
$lossControl = $this->lossControl->make(985, 1000, null);
var_dump('Acceptable Loss: ' . $lossControl->getAcceptableLoss());
var_dump('Actual Loss: ' . $lossControl->calculateLoss());
var_dump('Acceptable? ' . $lossControl->isAcceptable());
}
}
However if I try to dependency inject the LossControlInterface from within a custom class called by a command:
[2014-09-02 13:09:52] development.ERROR: exception 'ErrorException' with message 'Argument 11 passed to Offer::__construct() must be an instance of LossControlInterface, none given, called in /home/vagrant/Code/.../ProcessOffer.php on line 44 and defined' in /home/vagrant/Code/.../Offer.php:79
It appears as though I am unable to dependency inject the interface into a custom class, but I can when dependency injecting into a controller.
Any thoughts on what Im doing wrong or have omitted to get the automatic resolution working?
The IoC is automatic within controllers, and you don't see the injection because Laravel handles the construction of controllers for you. When creating any other custom class by using the new keyword, you will still need to send in all of the parameters needed to it's constructor:
$myClass = new ClassWithDependency( app()->make('Dependency') );
You can hide this, to a degree, by funneling creation of your custom class through a service provider:
// Your service provider
public function register()
{
$this->app->bind('ClassWithDependency', function($app) {
return new ClassWithDependency( $app->make('Dependency') );
});
}
Then just have the IoC make it whenever you need it:
$myClass = app()->make('ClassWithDepenency');
In your case, you can change your code to look like this:
private function setOffer(Offer $offer = null) {
$this->processOffer = $offer ?:
new Offer( app()->make('LossControlInterface') );
}
A perhaps cleaner approach could be to create a service provider and an OfferFactory which gets injected into your controller. The controller can then request the factory to create the offer whenever it needs one:
// Controller
public function __construct(OfferFactory $offerFactory)
{
$this->offerFactory = $offerFactory;
}
public function setOffer(Offer $offer = null)
{
$this->processOffer = $offer ?: $this->offerFactory->createOffer();
}
// OfferFactory
class OfferFactory
{
public function createOffer()
{
return app()->make('Offer');
}
}
This has the benefit of completely decoupling your controller from the logic behind the creation of the offer, yet allowing you to have a spot to add any amount of complexity necessary to the process of creating offers.
In Laravel 5.2 the simplest solution for your particular problem would be to replace
new Offer();
with
App::make('Offer');
or even shorter
app('Offer');
which will use Laravel Container to take care of dependencies.
If however you want to pass additional parameters to the Offer constructor it is necessary to bind it in your service provider
App::bind('Offer', function($app, $args) {
return new Offer($app->make('LossControl'), $args);
});
And voila, now you can write
app('Offer', [123, 456]);
In laravel 5.4 (https://github.com/laravel/framework/pull/18271) you need to use the new makeWith method of the IoC container.
App::makeWith( 'App\MyNameSpace\MyClass', [ $id ] );
if you still use 5.3 or below, the above answers will work.

Laravel can't instantiate interface via __construct (using App::bind)

I am trying to resolve class via __construct using Laravel's bind() method.
Here what I do:
routes.php (of course I will move it away from here)
// Bindings
App::bind(
'License\Services\ModuleSelector\SelectorInterface',
'License\Services\ModuleSelector\ModuleSelector'
);
SelectorInterface.php - interface that I will expect in __construct method.
<?php namespace License\Services\ModuleSelector;
interface SelectorInterface {
/**
* Simply return query that will select needle module fields
*
* #return mixed
*/
public function make();
}
ModuleSelector.php - this is class that I want to resolve via Laravel's DI (see example below).
<?php namespace License\Services\ModuleSelector;
use License\Services\ModuleSelector\Selector;
class ModuleSelector extends Selector
{
/**
* Get module by it's code
*
* #return mixed
*/
public function find()
{
return $this->make()
->where('code', $module_code)
->first();
}
}
Module.php
<?php namespace License\Services\ModuleType;
use License\Services\ModuleType\TypeInterface;
use License\Services\ModuleSelector\SelectorInterface;
class Module
{
...
function __construct(SelectorInterface $selector)
{
$this->selector = $selector;
}
...
}
And the place when error occurs:
In my repo I have use License\Services\ModuleType\Module as ModuleService;.
Than there is method called find():
/**
* Find module by its code with all data (types, selected type)
* #return mixed
*/
public function find($module_code)
{
$module = new ModuleService;
// Get module id in order to use build in relations in framework
$module = $this->module->find($module_code);
...
}
So, in other words, I have 2 classes and one interface. What I am trying to do is:
1) Create Class1.php / Class2.php / Class2Interface.php.
2) In Class1.php in the __construct I specify __construct(Class2Interface $class2).
3) Instantiate Class2.
What I am doing wrong? Examples found here.
In this line:
$module = new ModuleService;
You are directly invoking the Module class and not passing in an instance of SelectorInterface.
For the IoC to work you bind and make classes using it. Try that line again with :
$module = App::make('License\Services\ModuleSelector\SelectorInterface');
An alernative is to inject it directly into your repos constructor, as long as the repo is created by the IoC container, your concrete will be automatically injected.
Nowhere do you have a class marked to actually "implement SelectorInterface".

PHPSpec and Laravel - how to handle double method not found issues

I appear to be having issues with my spec tests when it comes to stubs that are calling other methods.
I've been following Laracasts 'hexagonal' approach for my controller to ensure it is only responsible for the HTTP layer.
Controller
<?php
use Apes\Utilities\Connect;
use \OAuth;
class FacebookConnectController extends \BaseController {
/**
* #var $connect
*/
protected $connect;
/**
* Instantiates $connect
*
* #param $connect
*/
function __construct()
{
$this->connect = new Connect($this, OAuth::consumer('Facebook'));
}
/**
* Login user with facebook
*
* #return void
*/
public function initialise() {
// TODO: Actually probably not needed as we'll control
// whether this controller is called via a filter or similar
if(Auth::user()) return Redirect::to('/');
return $this->connect->loginOrCreate(Input::all());
}
/**
* User authenticated, return to main game view
* #return Response
*/
public function facebookConnectSucceeds()
{
return Redirect::to('/');
}
}
So when the route is initialised I construct a new Connect instance and I pass an instance of $this class to my Connect class (to act as a listener) and call the loginOrCreate method.
Apes\Utilities\Connect
<?php
namespace Apes\Utilities;
use Apes\Creators\Account;
use Illuminate\Database\Eloquent\Model;
use \User;
use \Auth;
use \Carbon\Carbon as Carbon;
class Connect
{
/**
* #var $facebookConnect
*/
protected $facebookConnect;
/**
* #var $account
*/
protected $account;
/**
* #var $facebookAuthorizationUri
*/
// protected $facebookAuthorizationUri;
/**
* #var $listener
*/
protected $listener;
public function __construct($listener, $facebookConnect)
{
$this->listener = $listener;
$this->facebookConnect = $facebookConnect;
$this->account = new Account();
}
public function loginOrCreate($input)
{
// Not the focus of this test
if(!isset($input['code'])){
return $this->handleOtherRequests($input);
}
// Trying to stub this method is my main issue
$facebookUserData = $this->getFacebookUserData($input['code']);
$user = User::where('email', '=', $facebookUserData->email)->first();
if(!$user){
// Not the focus of this test
$user = $this->createAccount($facebookUserData);
}
Auth::login($user, true);
// I want to test that this method is called
return $this->listener->facebookConnectSucceeds();
}
public function getFacebookUserData($code)
{
// I can't seem to stub this method because it's making another method call
$token = $this->facebookConnect->requestAccessToken($code);
return (object) json_decode($this->facebookConnect->request( '/me' ), true);
}
// Various other methods not relevant to this question
I've tried to trim this down to focus on the methods under test and my understanding thus far as to what is going wrong.
Connect Spec
<?php
namespace spec\Apes\Utilities;
use PhpSpec\ObjectBehavior;
use Prophecy\Argument;
use \Illuminate\Routing\Controllers\Controller;
use \OAuth;
use \Apes\Creators\Account;
class ConnectSpec extends ObjectBehavior
{
function let(\FacebookConnectController $listener, \OAuth $facebookConnect, \Apes\Creators\Account $account)
{
$this->beConstructedWith($listener, $facebookConnect, $account);
}
function it_should_login_the_user($listener)
{
$input = ['code' => 'afacebooktoken'];
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
So here's the spec that I'm having issues with. First I pretend that I've got a facebook token already. Then, where things are failing, is that I need to fudge that the getFacebookUserData method will return a sample user that exists in my users table.
However when I run the test I get:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` not found.
I had hoped that 'willReturn' would just ignore whatever was happening in the getFacebookUserData method as I'm testing that separately, but it seems not.
Any recommendations on what I should be doing?
Do I need to pull all of the OAuth class methods into their own class or something? It seems strange to me that I might need to do that considering OAuth is already its own class. Is there some way to stub the method in getFacebookUserData?
Update 1
So I tried stubbing the method that's being called inside getFacebookUserData and my updated spec looks like this:
function it_should_login_the_user($listener, $facebookConnect)
{
$returnCurrentUser = (object) [
'email' => 'existinguser#domain.tld',
];
$input = ['code' => 'afacebooktoken'];
// Try stubbing any methods that are called in getFacebookUserData
$facebookConnect->requestAccessToken($input)->willReturn('alongstring');
$facebookConnect->request($input)->willReturn($returnCurrentUser);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
$listener->facebookConnectSucceeds()->shouldBeCalled();
$this->loginOrCreate($input);
}
The spec still fails but the error has changed:
Apes/Utilities/Connect
37 ! it should login the user
method `Double\Artdarek\OAuth\Facade\OAuth\P13::requestAccessToken()` is not defined.
Interestingly if I place these new stubs after the $this->getFacebookUserData stub then the error is 'not found' instead of 'not defined'. Clearly I don't fully understand the inner workings at hand :D
Not everything, called methods in your dependencies have to be mocked, because they will in fact be called while testing your classes:
...
$facebookConnect->requestAccessToken($input)->willReturn(<whatever it should return>);
$this->getFacebookUserData($input)->willReturn($returnCurrentUser);
...
If you don't mock them, phpspec will raise a not found.
I'm not familiar with the classes involved but that error implies there is not method Oauth:: requestAccessToken().
Prophecy will not let you stub non-existent methods.

Categories