I'm not able to inject these variables through Laravel:
//...class AllowedUsername implements Rule...
public function __construct(Router $router, Filesystem $files, Repository $config)
{
$this->router = $router;
$this->files = $files;
$this->config = $config;
}
I get the error:
Type error: Too few arguments to function ... 0 passed in.
Why is Laravel not doing it automatically?
$request->validate([
'username' => ['required', new AllowedUsername],
]);
In order to leverage Laravel's injection magic you need to use Laravel's API which essentially is:
resolve($class) which is wrapper around app($class)
app($class, $params = []) which is wrapper around:
Note: I've changed $abstract for $class
if (is_null($class)) {
return Container::getInstance();
}
return Container::getInstance()->make($class, $parameters);
Classes that you want to resolve out of container (as seen in your code sample):
public function __construct(Router $router, Filesystem $files, Repository $config)
can be resolved only because Laravel maintainers already defined binding for Router::class, Filesystem:class (example: FilesystemServiceProvider).
Repository::class seems to be simple class that does not require parameters (or require parameters that container already knows how to resolve) while "newing up" - thus Laravel can resolve it without problem.
There is no need to bind classes into the container if they do not depend on any interfaces. The container does not need to be instructed on how to build these objects, since it can automatically resolve these objects using reflection.
Thats why resolve(AllowedUser::class) or resolve(Router::class)... work.
In order to let Laravel know what constructor's parameters should be sent during "newing up" you use bindings mentioned in documentation.
Related
In my controller I am using a class like so:
public function book($id, CalRequest $request)
{
$event = $this->repo->find($id);
(new CalAuthorisation())
->setEvent($event)
->canBook();
$this->booking->book($event, $request);
}
I'm mocking a class like below:
$auth = \Mockery::mock(CalAuthorisation::class)->makePartial();
$this->app->bind(CalAuthorisation::class, function() use ($auth) {
return $auth;
});
$auth->shouldReceive('canBook')->once()->andReturn(true);
However, if I dd within the canBook() function I still get into there. I have a similar mock in place for the CalRequest class which works fine. Any suggestions as to why this isn't working!
Dependency injection is about injecting different implementation into your logic. When using new keyword, PHP will fetch a certain class and only that class. Laravel solves dependency injection through the container, so you have to use the container for it to work.
There is two ways, either use app() or resolve(). Secondly you can inject your classes into methods that are on Laravel classes like jobs, commands, controllers etc. and this will be resolved through the container.
Using resolve().
resolve(new CalAuthorisation())
->setEvent($event)
->canBook();
Injecting it, through the controller method.
public function book($id, CalRequest $request, CalAuthorisation $calAuth)
{
$event = $this->repo->find($id);
$calAuth->setEvent($event)
->canBook();
whenever we call a Facade Method it involves Facade design pattern and it called for some hidden class by using Facade. for instance for File, if we call
File::get(public_path().'test.txt');
this will call the method in class
Illuminate\Filesystem\Filesystem
and in this class we will have get($path) method.
Now my question is how Facade Abstract Class is related to File and Filesystem and where Laravel is telling them to call get in Filesystem. is there some kind of register which i am missing ?? i want to find complete link.
If you go in your config/app.php, you will notice that there's an array called aliases which looks like this
'aliases' => [
//
//
//
//
'File' => Illuminate\Support\Facades\File::class,
];
So, basically whenever you call File, the Service Container will try to resolve an instance of Illuminate\Support\Facades\File::class which is just a Facade.
If you look into Illuminate\Support\Facades\File::class, you will see that it contains only one method:
class File extends Facade
{
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor()
{
return 'files';
}
}
As you can see, it extends the Facade class and whenever a Facade is being resolved, Laravel will try to find a key in the Service Container that is equal to whatever is returned by getFacadeAccessor().
If you check the source of Illuminate\Filesystem\FilesystemServiceProvider, you will see this:
$this->app->singleton('files', function () {
return new Filesystem;
});
As you can see, the key files is being bounded to a FileSystem implementation. So, that's how Laravel knows how to resolve the File facade.
I usually use parameters like this:
public function test($parameter)
{
echo 'Parameter value: ' . $parameter;
}
While looking at laravel service container I see this code.
public function __construct(UserRepository $users)
{
$this->users = $users;
}
According to the documentation it uses reflection.But i dont understand.
I dont know how the parameter UserRepository $users works. Is that an alias or something?
This is called type-hinting and is used to inject dependencies in a constructor or to validate the right type of argument is passed to a function. The injection simply means that if the class is called with the make method, Laravel will automatically provide an instance of the class required by your constructor.
For example if you have a function public function something(string $something) it would throw an error if any other type than a String is passed to this function, making sure the right data is used.
From the laravel documentation:
Alternatively, and importantly, you may "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 should be resolved by the container.
For example, you may type-hint a repository defined by your application in a controller's constructor. The repository will automatically be resolved and injected into the class:
Laravel has a great service container and it makes all dependency injections, so you don't need to pass a class a parameter, laravel do it for you.
without container you have to pass this parameter
class A {
public $foo;
public function __construct (Foo $foo){
$this->foo
}
$classA = new A((new Foo))
When laravel encounter with these classes, it resolves them.
Also you can define manually these classes using singleton() or bind() methods
$this->app->singleton('FooBar', function($app)
{
return new FooBar($app['SomethingElse']);
});
Or you may use interfaces. You can bind implemented class for to the interface and laravel when encounter with that interfance, it will resolve as you wish
$this->app->bind('App\ICacheManager', 'App\RedisManager');
public $redis;
public function __contruct(ICacheManager $redis){
$this->redis = $redis;
}
for more further check out laravel service container
I have an associative array which I use in approx all controllers and i was wondering if possible to define that array somewhere at one place and just use in in all controllers?
Kind of like we do in angular.
Like if i can define it in env file or something.
Please let me know if there is a way.
Creating an entry in the config/app.php
'myVar' => [
'key' => 'value'
],
and accesing it via config('app.myVar')
Put it in a helper file and access using that helper file
check out this answer https://stackoverflow.com/a/32772686/5808894
Using AppServiceProvider
In your app/providers/AppServiceProvider.php in the boot method add this, make sure to import App use App in AppServiceProvider
public function boot()
{
App::singleton('myVar', function(){
return [
'key' => 'value'
];
});
}
and access the variable in your controller using app('myVar');
Reference https://stackoverflow.com/a/25190686/5808894
Mentioning it here since no one else did:
public class SharedArrayContainer {
public static $data = [ 'key' => 'value' ];
}
and you can use it as:
SharedArrayContainer::$data
Not as good as adding it to the service container but this is what pre-framework me used to do.
I would recommend to create a provider class (service) for it and use laravel Service Container for injection. This way you can create helper methods like get, find, etc... and make use of laravels Dependency Injection (having single instance injected whenever & almost everywhere you want)
Laravel docs
class ExampelService
{
// associative array
private arr = []
public function get(item) { }
public function save(item) { }
public function has(item) { }
}
I am trying to do a hello world service provider with the new Laravel 5.4.
I have created the following service provider file:
//File: app/TestProvider/TestServiceProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
$this->app->bind('Test', function ($app) {
return new Test();
});
}
}
I have created a simple class under the same namespace:
//File: app/TestProvider/Test.php
namespace App\TestProvider;
class Test
{
/**
* Register bindings in the container.
*
* #return void
*/
public function helloWorld()
{
echo "hello world";
}
}
The problem is, this is not registering. The register method is executing as when I put a breaker before the 'bind' method, it executes:
public function register()
{
dd("BREAKER");
$this->app->bind('Test', function ($app) {
return new Test();
});
}
So this outputs "BREAKER" as expected. However if I put the breaker in the closure, nothing happens which suggests for some reason, that 'bind' method isn't being executed??
Any ideas?
EDIT:
Just some further info: I know that the Test class is registered and in the correct namespace as I can do:
dd(new Test());
in the registration method, and it outputs the resource id as expected.
Explanation
The closure provided only runs when the binding is being resolved. That's why it's a closure, it can be saved in the service container and resolved at any time while the program runs.
Solution
To see the resolved binding, create a controller and resolve the class in that controller:
// File: app/Http/Controllers/TestController.php
namespace App\Http\Controllers;
// This isn't the best way, but it works. See the best way below
class TestController extends Controller {
public function index()
{
return \App::make('Test')->helloWorld();
}
}
Of course, don't forget to register the route:
// File: routes/web.php
Route::get('/', 'TestController#index');
The binding will resolve when you hit the homepage.
However, as I said, it's not the best way, so here I prepared a better way. Change the way you register the binding:
// File: app/Providers/TestProvider.php
namespace App\TestProvider;
use Illuminate\Support\ServiceProvider;
use App\TestProvider\Test;
// Better way
class TestServiceProvider extends ServiceProvider
{
/**
* Register bindings in the container.
*
* #return void
*/
public function register()
{
// Note: we bind the exact complete class name!
$this->app->bind(Test::class, function ($app) {
return new Test();
});
}
}
After this change the controller so that it looks like this:
namespace App\Http\Controllers;
use App\TestProvider\Test;
class TestController extends Controller {
/**
* #var Test $test
*/
private $test;
// Let Laravel resolve the dependency on constructing the class
public function __construct(Test $test)
{
$this->test = $test;
}
public function index()
{
return $this->test->helloWorld();
}
}
You will see that the exact same thing happens, but it looks more elegant and avoids conflicts.
Details
Laravel gives only a high level overview of the service container, which doesn't help to learn how it works on the inside. The best way to see that is to go down the call stack.
When you do that, you find that Laravel registers every class in the project in the service container. That means that whether you create a service provider or not, the class will be in the container. How exactly?
When you run php artisan optimize, Laravel creates files that have array with all the classes of the project. When you run the app, after registering everything from the service providers, Laravel registers the rest of the classes from that file.
That means that in your case, if you don't specifically register the Test class, it will still be resolvable. Basically, you only need to register classes that need some specific instructions to be resolved.
So how does Laravel resolve the dependencies?
When you run \App::make(Test::class) or inject dependency via type hinting in the constructor (the "better way" from my solution), Laravel looks for that dependency among the bindings.
When it finds the dependency, it resolves either the closure associated to it or the constructor of the class directly.
When it resolves the constructor directly, it looks for type hints among the constructor parameters and recursively resolves all of them until there's nothing else to resolve.
After that it returns the resolved class.
Of course, bear in mind that for Laravel to analyze the contructor of a class, it needs to be resolved via the service container in the first place. You can't just call $test = new Test(); and expect Laravel to do all the magic :)
Conclusion
This is a rather quick overview of Laravel's service container. The best way for you to learn it is, of course, studying the sources for yourself. It's truly elegant and it uses PHP's functionality to the fullest.
I really hope this shed some light on the service container for you and can help you in the future :)
The closure passed to the bind() method is not executed until you actually attempt to resolve the alias you are binding.
So, if you dd('breaker') inside the closure, this won't actually get executed until Test is resolved (whatever your preferred resolution method is):
Service provider:
// bind the closure to the 'Test' alias
public function register()
{
$this->app->bind('Test', function ($app) {
dd("BREAKER");
return new Test();
});
}
Code that resolve Test alias:
// different ways of resolving the alias out of the container.
// any of these will execute the bound closure.
$test = resolve('Test');
$test = app('Test');
$test = app()->make('Test');
$test = \App::make('Test');
try:
$this->app->bind(Test::class, function ($app) {
return new Test();
});