Laravel Singleton still creating new objects - php

I have a singleton setup in my AppServiceProvider.php, such as this:
public function boot()
{
$this->app->singleton(Breadcrumbs::class, function($app){
return new Breadcrumbs();
});
View::composer(['partials.*'], function($view){
$view->with('breadcrumbs', new Breadcrumbs());
});
}
Breadcrumbs is just a simple class that manages an array of breadcrumbs and I want there only to be 1 object across the whole app (so every time you call new Breadcrumbs() you actually get the existing object, not a new one. (i think this is what singletons are for?)
But now have added this to the JetStreamServiceProvider.php
public function boot()
{
$this->configurePermissions();
Fortify::loginView(function (){
$breadcrumbs = new Breadcrumbs();
$breadcrumbs->add('login','login.php');
return view('auth.login');
});
}
However instead of using the same object as what was created in the AppServiceProvider it is making a new object (so the breadcrumbs object in AppServiceProvider and the breadcrumbs object in the JetStreamServiceProvider are 2 different objects containing a different set of data)...which is no good.
What am I doing wrong?

If you want to resolve the binding you setup you need to use the IoC container that you set the binding on to resolve it. You calling new Class is you telling PHP directly to create a new instance of that class. Laravel does not change how PHP works.
public function register()
{
$this->app->singleton(Breadcrumbs::class, function () { ... });
}
public function boot()
{
View::composer(['partials.*'], function ($view) {
// resolve the instance from the IoC (Application) Container
$view->with('breadcrumbs', $this->app->make(Breadcrumbs::class));
});
// you can do a "view share" to share this with all views instead:
View::share('breadcrumbs', $this->app->make(Breadcrumbs::class));
}
Fortify::loginView(function () {
$this->app->make(Breadcrumbs::class)->add('login', 'login.php');
return view('auth.login');
});
Laravel 8.x Docs - Service Container - Resolving - The make method

This was the solution I used:
AppServiceProvider
public function register()
{
$this->app->singleton(Breadcrumbs::class, function($app){
return new Breadcrumbs();
});
}
public function boot(Breadcrumbs $breadcrumbs)
{
View::composer(['partials.*'], function($view) use ($breadcrumbs){
$view->with('breadcrumbs', $breadcrumbs);
});
}
JetStreamServiceProvider.php
public function boot(Breadcrumbs $breadcrumbs)
{
Fortify::loginView(function () use ($breadcrumbs){
$breadcrumbs->add('Login',Route('login'));
return view('auth.login');
});
}

Related

Laravel Target is not instantiable while building

I created an Artisan command which worked and where I injected a Kafka client service as the first parameter and a concrete class BudgetsTransformer, as the second parameter.
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, BudgetsTransformer $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
AppServiceProvider class looked like:
class AppServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('kafka.client', function ($app) {
return new \Weq\FKafka\FKafka();
});
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
}
public function boot()
{
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app['budget.transformer']);
});
$this->commands('consume:budgets');
}
}
So far all is working properly. Then I decided to create a TransformerInterface which BudgedTransformer implements (and other future Transformers will implement it):
class BudgetsTransformer implements TransformerInterface
{
// ...
}
and I changed the signature in the command to inject the interface instead of the concrete class:
class ConsumeBudgetsCommand extends Command {
public function __construct(FKafka $kafkaClient, TransformerInterface $transformer)
{
$this->kafkaClient = $kafkaClient;
$this->transformer = $transformer;
parent::__construct();
}
}
But I get the following issue when I try to run some artisan command
In Container.php line 933:
Target [App\Transformers\TransformerInterface] is not instantiable while building [App\Console\Commands\ConsumeBudgetsCommand].
I run previously the issue the following artisan command just in case. cache:clear, clear-compiled, optimize and so on but no luck.
What I'm doing wrong? Should I bind the BudgetTransformer in a different way I'm doing now for passing and Interface instead of a concrete class?
I added:
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
in AppServiceProvider::register() and I removed
$this->app->bind('budget.transformer', function ($app) {
return new BudgetsTransformer();
});
there, then I update in AppServiceProvider::boot() the command binding:
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app[TransformerInterface::class]);
});
But still not working, anyway this approach (even working) will not resolve the issue since when I want to add another different transformer implementation, let's say CostTransformer which implements TransformerInterface is gonna always inject BudgetTransformer. So reading the documentation in the link, I found that Contextual Binding could be the solution, so I substituted by:
$this->app
->when(ConsumeBudgetsCommand::class)
->needs(TransformerInterface::class)
->give(function ($app) {
return new BudgetsTransformer();
});
So in that way, I will be able to inject different implementations of transformers to different commands by injecting the interface. But still not working.
Can someone tell me how exactly declare the command binding
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], ???);
});
to use that Contextual binding?
For binding interfaces must be use this structure https://laravel.com/docs/5.5/container#binding-interfaces-to-implementations
$this->app->bind(TransformerInterface::class, BudgetsTransformer::class);
And
$this->app->bind('consume:budgets', function ($app) {
return new ConsumeBudgetsCommand($app['kafka.client'], $app->make(TransformerInterface::class));
});

Lumen - Registering Class in Service Provider

I am registering an Aspect class in the AopServiceProvider. However, it does not seem to work.
Here is my code:
public function register()
{
$this->app->singleton(LoggingAspect::class, function (Application $app) {
return new LoggingAspect($app->make(LoggerInterface::class));
});
$this->app->tag([LoggingAspect::class], 'goaop.aspect');
}
Now even if I have the code as this:
public function register()
{
$this->app->singleton(LogsasasagingAspect::class, function (Application $app) {
return new LoggingAspect($app->make(LoggesasarInterface::class));
});
$this->app->tag([LoggasaingAspect::class], 'goaop.aspect');
}
It still doesn't show any error.
This service provider has been registered as:
$app->register(App\Providers\AopServiceProvider::class);
Is this normal or is there any fix for this?

Laravel extend TestResponse class

I'm trying to add a custom assertion to the TestReponse class so I can make something like this:
$response = $this->json('POST', '/foo/bar');
$response->myCustomAssertion();
I tried creating an App\TestResponse class that extends the original one and then binding it in the App\Provider\AppServiceProvider class.
public function register()
{
$this->app->bind('Illuminate\Foundation\Testing\TestResponse', function ($app) {
return new App\TestResponse();
});
}
But $response->json() is still returning the original one and not my own implementation.
How can I extend the TestResponse class?
If you want a little more fine-grained control, you can also extend the Illuminate\Foundation\Testing\TestResponse, as you have done, and then override the createTestResponse method in your TestCase class to return an instance of your custom response class:
// Laravel 8 and above
protected function createTestResponse($response)
{
return tap(App\TestResponse::fromBaseResponse($response), function ($response) {
$response->withExceptions(
$this->app->bound(LoggedExceptionCollection::class)
? $this->app->make(LoggedExceptionCollection::class)
: new LoggedExceptionCollection
);
});
}
// Before Laravel 8
protected function createTestResponse($response)
{
return App\TestResponse::fromBaseResponse($response);
}
From Illuminate\Foundation\Testing\Concerns\MakesHttpRequests.
The TestResponse class uses the Macroable trait so you can add macro functions at runtime.
TestResponse::macro('nameOfFunction', function (...) {
...
});
You can add this to a Service Provider's boot method or somewhere before you need to make the call to that macro'ed method.

Laravel 5.4 : Best practice to parse variable to a view

For example, I want to add a variable in my default view (default.blade.php)
I can of course, define my variable directly into the view file, like this :
#php ($user = Sentinel::getUser())
But this is not recommended.
Should I add it on AppServiceProvider.php ? (https://laravel.com/docs/5.4/views#sharing-data-with-all-views)
But with which call ? Like this :
public function boot()
{ $user = Sentinel::getUser(); }
This get : Undefined variable: user
public function boot()
{ View::share('user', Sentinel::getUser()); }
This get Trying to get property of non-object, so Sentinel is not truly declare
Or in the a controller
public function __construct()
{
//user
$user = Sentinel::getUser();
view()->share('user',$user);
}
I also try this into my controller
public function boot()
{
return view('layouts/default')->with('user', Sentinel::getUser(););
}
or
public function boot() {
view()->composer('layouts.default', function($view) {
$view->with('user', Sentinel::getUser());
});
}
Still get "Undefined variable: user"
The idea is to call View::XXXXX within the boot method of a service provider...
The simplest is to call share on the view within your app service provider... this will make it available to all views... however take notice of when this value is resolved, it is resolved at boot time...
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
View::share('key', 'value');
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
You can also create a view composer that will be run prior to rendering a specific view or set of views... You can give it the view/class or view/closure... this will be evaluated before the view renders...
Bind a view composer class:
public function boot()
{
// Using class based composers...
View::composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
);
}
Or attach a closure... and do what you need to boot that vie within that closure...
public function boot()
{
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
}
You can bind to all views like the View::share... but push the evaluation to before the view render by using a simple closure based composer... in your case...
// Using Closure based composers...
View::composer('dashboard', function ($view) {
$view->with('user', Sentinel::getUser() );
});
Hope this helps...
In your controller, define a function like that:
public function index() {
$user = Sentinel::getUser();
//The parameter of the view is your blade file relative to your directory
//resources/views/default.blade.php
return view('default')->with('user', $user);
}
In your web.php (routes file), add this
//First parameter is the url, second is the controller/function
Route::get('/', 'YourControllerName#index');
Now test the view in localhost [Optional]
http://localhost:8000

Bind a primitive to Laravel IoC container and resolve in a controller method

I'm trying to resolve a primitive inside a controller method.
This is the register method of my Provider:
public function register()
{
$this->app->when('App\Http\Controllers\InvalidCustomerController')
->needs('$customers')
->give(function () {
return InvalidCustomer::latest()
->paginate(20);
});
}
And this is the controller method I'm trying to resolve $customers:
public function index($customers)
{
return view(
'customer.invalid.index',
compact('customers')
);
}
$customers is not filled.
Everything will work if I resolve that on constructor.
What am I doing wrong?
ps: I'm using Laravel 5.2
Not sure if you found a solution but to use a primitive in a controller, pass it through the __constructor of the controller class as so:
private $customers;
public function __construct($customers) {
parent::__construct();
$this->customers = $customers;
}
This $customers variable can then be used elsewhere inside of the class.

Categories