I have a service provider that I want to use to bind an instance of a class to the service container:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
$this->app->instance('iCal', function () {
return new Calendar(config('calendar.name'));
});
}
}
As I understand the documentation on binding an instance, this allows me to bind the key iCal to the service container so that later in my controller or service class I can type hint iCal and the instance created in the service provider will be used.
So I created a controller and tried to type hint my instance:
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
class CalendarInviteController extends Controller
{
public function download(iCal $ical, $sessionId)
{
dd($ical);
}
}
But when I do I get the error:
Class App\Http\Controllers\iCal does not exist
Makes sense, as it applies it's looking for a class named iCal in the controller namespace which doesn't exist. There's not a use statement for the instance since iCal is just a text key, so I tried telling it to look at the root namespace thinking that may fix it:
public function download(\iCal $ical, $sessionId)
and I get the error:
Class iCal does not exist
When I read the section of the documentation on resolving from the service container it looks like the only thing I need to do in the controller is type hint to get the instance.
Am I misunderstanding the docs?
Update
I should also mention that I did add my service provider to my config/app.php file.
Also, when I create an interface, bind it to the service container instead, edit the vendor code to implement said interface, and inject that interface instead it works, but that requires that I edit the vendor code which I don't want.
As you see in the docs the method instance takes a key and an object instance to register in the container. So, if you want to register a specific instance in the container, the registration should be:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
//register a specific instance of the Calendar class in the container
$this->app->instance('iCal', new Calendar(config('calendar.name') );
}
}
This way you could get back the instance with:
$cal = \App::make('iCal');
If your purpose is to type-hint the class in the controller method, and you want to resolve the previous registered instance from the service container, you could do like this:
namespace App\Providers;
use Eluceo\iCal\Component\Calendar;
use Illuminate\Support\ServiceProvider;
class IcalProvider extends ServiceProvider
{
public function register()
{
//the key will be 'Eluceo\iCal\Component\Calendar'
$this->app->instance( Calendar::class, new Calendar(config('calendar.name') );
}
}
Now, in your controller:
namespace App\Http\Controllers;
//important: specify the Calendar namespace
use Eluceo\iCal\Component\Calendar;
class CalendarInviteController extends Controller
{
public function download(Calendar $ical, $sessionId)
{
dd($ical);
}
}
This way Laravel will see that you want a Calendar object and it will try to get it from the service container looking if exists a binding for this key: (because this is the namespace of the class you have specified in the controller)
Eluceo\iCal\Component\Calendar
and the binding exists! As you have bound this key to your service container in your service provider, so Laravel will return your registered instance.
In the code you provided, you tipe-hinted the class iCal, but the class didn't exist anywhere so Laravel wasn't able to instantiate the class
If you’re wanting to inject dependencies into your controller (which is good, so kudos!) then you need an interface name to type-hint on.
Usually you would have a generic interface, and then bind that interface to a concrete implementation. So you may have a calendar service interface, that’s bound to your iCal implementation. Something like this:
use Eluceo\iCal\Component\Calendar;
class CalendarServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('App\Services\Calendar', function ($app) {
return new Calendar(config('calendar.name'));
});
}
public function provides()
{
return ['App\Services\Calendar'];
}
}
So long as you register your service provider in your config/app.php file, you can now type-hint your calendar dependency in classes:
use App\Services\Calendar;
class InvitationController extends Controller
{
protected $calendar;
public function __construct(Calendar $calendar)
{
$this->calendar = $calendar;
}
}
Related
I am using Laravel Lumen (v8.2.4) with PHP (v7.4)
I have the following controller class:
<?php
namespace App\Http\Controllers;
use App\Services\MyService;
class MyController extends Controller
{
protected $myService;
public function __construct(MyService $myService)
{
$this->myService = $myService;
}
}
And the following service class:
<?php
namespace App\Services;
class MyService
{
private $credentials;
function __construct($credentials)
{
$this->credentials = $credentials;
}
}
The myService class requires the credentials property to be set in order to function correctly. When myService is being injected into my controller class, I don't have the credentials at that time so therefore cannot initialise the service class to inject it.
How can I handle this? Should I remove the service class constructor and set the credentials property in a public setter method and invoke this when I need to? I am not sure if this is a good approach.
Appreciate any advice.
You need to register your service in service provider. You can do it in AppServiceProvider or in custom provider.
//retrieve credentials from wherever you need to get them, ex: config file
$credentials = config('app.credentialts');
$this->app->bind(MyService::class, function ($app) use ($credentials) {
return new MyService($credentials);
});
Check out Laravel Service Providers and Laravel Service Containers for more detailed information.
I am trying to inject a Manager class into toe Service Container of Lumen. My goal is to have a single instance of LogManager which is available in the whole application via app(LogManager::class).
Everytime i try to access this shortcut i get the following exeption:
[2017-03-23 16:42:51] lumen.ERROR: ReflectionException: Class LogManager does not
exist in /vendor/illuminate/container/Container.php:681
LogManager.php (i placed that class in the same location where my models are (app/LogManager.php))
<?php
namespace App;
use App\LogEntry;
class LogManager
{
...
}
AppServiceProvider.php
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\LogManager;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton(LogManager::class, function ($app) {
return new LogManager();
});
}
}
I uncommented the line $app->register(App\Providers\AppServiceProvider::class); in bootstrap/app.php
I think that i missed something with the correct namespacing or placement of the classes espaccially LogManager. Maybe some one is willing to give me a hint?
If you need some more informations just give me a hint!
Your class and your service provider look fine. However, wherever you're calling app(LogManager::class) also needs to know the fully qualified name of the class.
Either make sure you have use App\LogManager at the top of the file, or change your call to app(\App\LogManager::class).
I encountered this issue using the repository pattern. Currently I use an interface, and a custom class to achieve it, then type-hint it into the controller's construct and because of Laravel, it will solve the repositories' dependencies automatically and recursively.
I also do this in a service provider:
$this->app->bind(path/to/repoInterface,path/to/implementationClass)
However, because of the way I coded these repositories, in order to avoid code duplication, I created an abstract class that has a common method to all these repositories. This class is as follows:
abstract class CommonRepo{
public function __construct(SomeModelClass model){}
public function commonMethod(){//Code here}
And my repositories have the following structure:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI{
public function __construct(){
parent::__construct();
}
}
Laravel doesn't like this, so its giving this error:
Argument 1 passed to path/to/repo/CommonRepo::__construct() must be an instance of path/to/model/SomeModelClass, none given, called in...
So, obviously is not resolving the dependency of the class CommonRepo, but it does resolve the dependencies on the normal repositories.
I'd like, if it's possible, to use type-hinting (the Laravel way) without having to do anything related to the new operator
How can I, then, resolve that class's dependencies ?
PD: Using Laravel 5.2
Parent constructor is called like normal function without touching dependency resolver so you should do one of two possibilities:
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(SomeModelClass $model){
parent::__construct($model);
}
}
or
public class ExampleRepository extends CommonRepo implements ExampleRepositoryI
{
public function __construct(){
parent::__construct(App::make(SomeModelClass::class));
}
}
nice question. I did some tinkering, though I don't know if this is what you're looking for. But you can dynamically create an instance of Eloquent model required by your repository class.
Let's say you have your User model class stored in app\Models\User.php:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class User extends Model
{
//
}
You then create a base abstract class for all of your repository classes: app\Repositories\BaseRepository.php. This is where you place all common functionalities for your repository classes. But rather than injecting the Eloquent instance through the constructor, you may add a method named getModel() to dynamically create an instance of Eloquent model for your repository.
<?php
namespace App\Repositories;
use ReflectionClass;
use RuntimeException;
use Illuminate\Support\Str;
abstract class BaseRepository
{
protected $modelNamespace = 'App\\Models\\';
public function getById($id)
{
return $this->getModel()->find($id);
}
public function getModel()
{
$repositoryClassName = (new ReflectionClass($this))->getShortName();
$modelRepositoryClassName = $this->modelNamespace . Str::replaceLast('Repository', '', $repositoryClassName);
if (! class_exists($modelRepositoryClassName)) {
throw new RuntimeException("Class {$modelRepositoryClassName} does not exists.");
}
return new $modelRepositoryClassName;
}
}
Now let's say you want to create a repository for your User model, and this user's repository must implement the following interface: app\Repositories\UserRepositoryInterface.php
<?php
namespace App\Repositories;
interface UserRepositoryInterface
{
public function getByEmail($email);
}
You create app\Repositories\UserRepository.php class and simply extend it from the BaseRepository class. Also don't forget to implement all specific implementations defined on UserRepositoryInterface.
<?php
namespace App\Repositories;
use App\Repositories\BaseRepository;
use App\Repositories\UserRepositoryInterface;
class UserRepository extends BaseRepository implements UserRepositoryInterface
{
public function getByEmail($email)
{
return $this->getModel()->where('email', $email)->firstOrFail();
}
}
This way you can bind the UserRepositoryInterface to it's implementation like so:
$this->app->bind(\App\Repositories\UserRepositoryInterface::class, \App\Repositories\UserRepository::class);
Finally you can freely inject the UserRepositoryInterface to a controller's constructor or methods. You can also resolve it via service container like this:
$userRepository = App::make(App\Repositories\UserRepositoryInterface::class);
$userRepository->getByEmail('john#example.com');
Of course there's a catch to this approach. The repository class should be started with the associated model, so the InvoiceRepository.php is dedicated for Invoice.php model class.
Hope this help!
This might help. You can listen in for when an object resolves and set attributes.
$this->app->resolving(CommonRepo::class, function ($object, $app) {
// Called when container resolves object of any type...
$object->commonObject = app(CommonObject::class);
});
Docs: https://laravel.com/docs/5.4/container#container-events
I'm trying to deepen my knowlade in laravel architecture.
I have a search engine (elastic search for the sake of the example), but this search engine might change in the future. So im trying to write a container for this, so in case i'll change the engine in the future, i will have to change only the container. (I believe the termenology is factory design?)
I have created a provider app/providers/DataFromSearchEngine.php that looks like this:
use Illuminate\Support\ServiceProvider;
class DataFromSearchEngine extends ServiceProvider {
public function boot()
{
//
}
public function register()
{
$this->app->singleton('SearchEngine', function($app) {
return new elasticSearch;
});
}
}
Then i registered it in the providers array in config/app.php .
'providers' => [
// providers...
'App\Providers\DataFromSearchEngine'
],
The next step is to call SearchEngine from my controller:
namespace App\Http\Controllers;
use App\Http\Requests;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
class SearchController extends Controller {
protected $searchEngine;
public function __construct() {
$this->searchEngine = $this->app->make('SearchEngine');
}
}
But all these yields: Undefined property: App\Http\Controllers\SearchController::$app
Can someone explain what i'm missing?
Instead of using $this->app try using app().
This is because non of the inherited controller classes, i.e. App\Http\Controllers\Controller or Illuminate\Routing\Controllers\Controller have an app property on them.
As a note you can use app('SearchEngine') which is the equivalent of app()->make('SearchEngine') as a shortcut to making your object.
I had this issue when trying to create a service provider. I registered my service provider in AppServiceProvider.php but was still getting this same error. The issue was that in my ServiceProvider I needed to add extends ServiceProvider to my class. Seems simple but is often forgotten.
I have a custom class App/Http/Responder, which had a few methods to build a specific JSON response back in my application. I want to test my controller in isolation, so I'm trying to inject my dependencies via the constructor.
My plan was to simply create a service provider, attach bind it to the $app and then, as per the docs, let it be automatically resolved:
public function register()
{
$this->app->bind('responder', function()
{
return new App\Http\Responder($this->app['cache'], $this->app['app'], new JsonResponse, $this->app['config']);
});
}
I then add this to my config/app.php.
Okay, so now my Responder and it's dependancies are bound to the app, as responder.
Now I thought I'd be able to inject Responder into my controller constructor, and Laravel would be able to automatically resolve this from the IoC container:
class AreasController extends BaseController {
protected $responder;
public function __construct(Responder $responder)
{
$this->responder = $responder;
}
However I get Class Responser does not exist.
The only way I can get it working, without using the App::make() Facade, is to inject the app into my controller:
use Illuminate\Foundation\Application as App;
class AreasController extends BaseController {
protected $app;
public function __construct(App $app)
{
$this->app = $app;
}
I can then do $this->app['responder']->method().
Obviously I'm missing something, but I want to keep away from using Facades in my app so I can test.
If you want to type hint classes to be resolved in the IOC container, you should bind the actual class name with namespace:
$this->app->bind('App\Http\Responder', function()
{
return new App\Http\Responder($this->app['cache'], $this->app['app'], new JsonResponse, $this->app['config']);
});
Technically the container would still resolve this class, because it's a concrete class that can be found, but the way you're doing allows to inject other IOC-bound resources, which is a good practice.
Then, when you wish to have this class injected for you, type hint the full path to the class as you normally would:
use App\Http\Responder;
class AreasController extends BaseController {
protected $responder;
public function __construct(Responder $responder)
{
$this->responder = $responder;
}
}
Also, for what it's worth, your error indicates that you misspelled "Responder" as "Responser".