I'm building a Laravel project and in one of the controllers I'm injecting two dependencies in a method:
public function pusherAuth(Request $request, ChannelAuth $channelAuth) { ... }
My question is really simple: How do I pass parameters to the $channelAuth dependency?
At the moment I'm using some setters to pass the needed dependencies:
public function pusherAuth(Request $request, ChannelAuth $channelAuth)
{
$channelAuth
->setChannel($request->input('channel'))
->setUser(Auth::user());
What are the alternatives to this approach?
P.S. The code needs to be testable.
Thanks to the help I received on this Laracast discussion I was able to answer this question. Using a service provider it's possible to initialize the dependency by passing the right parameters to the constructor. This is the service provider I created:
<?php namespace App\Providers;
use Security\ChannelAuth;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class ChannelAuthServiceProvider extends ServiceProvider {
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('Bloom\Security\ChannelAuthInterface', function()
{
$request = $this->app->make(Request::class);
$guard = $this->app->make(Guard::class);
return new ChannelAuth($request->input('channel_name'), $guard->user());
});
}
}
You can pass parameters (as a string indexed array) when resolving a dependence like this:
<?php namespace App\Providers;
use Security\ChannelAuth;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Support\ServiceProvider;
class ChannelAuthServiceProvider extends ServiceProvider {
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->bind('Bloom\Security\ChannelAuthInterface', function($params)
{
$channelName = $params['channelName'];
$guard = $this->app->make(Guard::class);
return new ChannelAuth($channelName, $guard->user());
});
}
}
Then when resolving eg in a controller:
public function pusherAuth()
{
$channelAuth = app()->makeWith('Bloom\Security\ChannelAuthInterface', [
'channelName' => $request->input('channel_name')
]);
// ... use $channelAuth ...
}
You can create and register your own service provider and create object with constructor's requests parameters.
I don't know how to do this in Laravel, but in Symfony2 you can inject in your own service something like RequestStack. It's the best way, because you have small service providers that are fully testable.
Related
There is a posting from 2016 that describes how to implement Yasumi:
https://www.yasumi.dev/
into Laravel. But this looks like it completely outdated now. What is the correct way to implement it into Laravel 9?
Post I am referencing from 2016:
https://stackoverflow.com/a/41266340/8207054
I am using this code (AppServiceProvider):
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use Carbon\Carbon;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('yasumi', \Yasumi\Yasumi::create('USA', Carbon::now()->format('Y')));
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
//
}
}
but it causes this error: Illuminate\Container\Container::bind(): Argument #2 ($concrete) must be of type Closure|string|null
You can send function as the second parameter to singleton method like this:
$this->app->singleton('yasumi', function () {
return \Yasumi\Yasumi::create('USA', Carbon::now()->format('Y'));
});
We're using a microservice architecture. There are 2 sets of data for the laravel service:
The database that houses the admins.
And all the other data that admins can access, which comes via GRPC
calls to other services.
I want something like eloquent (maybe API Resource?) for structuring data/relationships, but instead of making Database-queries to load data, it needs to make GRPC calls to other services. I was thinking of making a custom class that extends off of eloquent and overloading the protected functions that make calls to the database, but that sounds like a recipe for a bad time. If anybody has experience doing what I'm describing, what direction did you go? What worked? what didn't?
So, I ended up not using eloquent at all. I continued using the protoc set up as the documentation explains. But I used Route-binding to enable type-hinting in controllers:
<?php
namespace App\Providers;
use OurNamespace\GrpcClient;
use Illuminate\Support\Facades\Route;
use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider;
use OurNamespace\Customer;
use OurNamespace\CustomerIdInput;
class RouteServiceProvider extends ServiceProvider
{
/**
* This namespace is applied to your controller routes.
*
* In addition, it is set as the URL generator's root namespace.
*
* #var string
*/
protected $namespace = 'App\Http\Controllers';
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
//
parent::boot();
Route::bind('customer', function ($customerId) {
$grpcClient = app(GrpcClient::class);
$customerIdInput = new CustomerIdInput();
$customerIdInput->setCustomerId($customerId);
list($customer, $status) = $grpcClient->GetCustomerDetails($customerIdInput)->wait();
if ($status->code != 0) {
error_log('ERROR - ' . print_r($status, 1));
return redirect()->back()->withErrors(['There was an error retrieving that customer', $status->details]);
}
return $customer;
});
The GrpcClient is coming from the AppServiceProvider. This way if we want to make a query we don't have to manually instantiate it.
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use OurNamespace\GrpcClient;
use Grpc\ChannelCredentials;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->singleton('OurNamespace\GrpcClient', function ($app) {
return new GrpcClient(env('GRPC_HOST'), [
'credentials' => ChannelCredentials::createInsecure(),
]);
});
I've created a CustomProvider, added it to the app.php array of providers and registered a class as singleton:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\ReserveCart;
class CustomProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->singleton('App\ReserveCart', function($app){
return new ReserveCart;
});
}
}
but everytime I request for the object with $rc = resolve('App\ReserveCart'); it keeps giving me different instances of the object instead of a single one (I've done some echo tracking).
Also tried passing the dependency to methods acording to Laravel Documentation. e.g
public function foo(App\ReserveCart $rc){
//
}
but the issue persists.
Is the output below same ?
$rc = resolve('App\ReserveCart');
$rc1 = resolve('App\ReserveCart');
dd(spl_object_hash($rc), spl_object_hash($rc1));
I have a part of site that starts with specific prefix /manage.
Can I somehow like with AppServiceProvider view-composers inject a variable in all routes from that prefix?
I tried to do it by passing this variable to layout of all that routes. But then I met a problem. I use this variable in blade view of specific page, and it returns me variable not defined.
Then, I inspect laravel debugger and saw the order of loading of blade files. And it was :
1. Current page view
2. Layout view
3. Sidebars and other stuff
So, the fact that current page is loaded before layout, cause error of undefined variable.
So, how can I solve that ? Thanks.
Code from my Service provider :
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\CT;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
view()->composer(['website.implicare.ct.show', 'website.implicare.ct.petition.index', 'layouts.ct'], function($view) {
$ct = request()->ct;
$permissions = [];
foreach($ct->userPermissions(auth()->id()) as $userPermission) {
if($userPermission->pivot->ct_id == $ct->id) {
array_push($permissions, $userPermission->name);
}
}
$view->with('permissions', $permissions);
});
}
/**
* Register any application services.
*
* #return void
*/
public function register()
{
//
}
}
create ComposerServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public $theme = 'mytheme';
public function boot()
{
view()->composer($this->theme.'.includes.navbar', 'App\Http\ViewComposers\MenuComposer');
view()->composer($this->theme.'.includes.header', 'App\Http\ViewComposers\MenuComposer');
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
As the last comment of this post it states the following:
You can easily do it in any service provider (boot would be a nice place since we can use method based DI).
public function boot(\Illuminate\Contracts\Http\Kernel $kernel) {
$kernel->appendMiddleware('Sheepy85\L5Localization\Middleware\Localization'); // prependMiddleware works too.
}
This is Laravel Code for injecting a middleware from a Service Provider.
I am trying to achieve the same thing from Lumen Framework, here's the code:
<?php namespace Acme\Slz\Providers;
use Illuminate\Contracts\Http\Kernel;
use Illuminate\Support\ServiceProvider;
class SlzServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
* #param Kernel $kernel
* #return void
*/
public function boot(Kernel $kernel)
{
// push the serializer middleware to the stack
$kernel->pushMiddleware(Acme\Slz\Middleware\Serializer::class);
}
/**
* Register any application services.
* This service provider is a great spot to register your various container
* bindings with the application. As you can see, we are registering our
* #return void
*/
public function register()
{
}
}
But this raise the following error:
lumen.ERROR: exception 'ErrorException' with message 'Argument 1 passed to Acme\Slz\Providers\SlzServiceProvider::boot() must be an instance of Illuminate\Contracts\Http\Kernel, none given
Are there some more stuffs to do to enable the Dependency Container with Lumen ?
How about this:
<?php namespace Acme\Slz\Providers;
use Illuminate\Support\ServiceProvider;
class SlzServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
// push the serializer middleware to the stack
$this->app->middleware([
'serializer' => 'Acme\Slz\Middleware\Serializer',
]);
}
}