Override Singleton in Laravel Container - php

I'm wondering if there's a simple way to override a singleton service set in the core of the Laravel framework?
e.g. I'm trying to rewrite the app:name command service '' with the following provider:
use Hexavel\Console\AppNameCommand;
use Illuminate\Console\Events\ArtisanStarting;
use Illuminate\Contracts\Events\Dispatcher;
use Illuminate\Support\ServiceProvider;
class NameCommandProvider extends ServiceProvider
{
/**
* Register any other events for your application.
*
* #param \Illuminate\Contracts\Events\Dispatcher $events
* #return void
*/
public function boot(Dispatcher $events)
{
$events->listen(ArtisanStarting::class, function ($event) {
$event->artisan->resolve('command.app.name');
}, -1);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
$this->app->singleton('command.app.name', function ($app) {
return new AppNameCommand($app['composer'], $app['files']);
});
}
}
I'm 100% everything is working due to extensive checks put no matter what order I put my service provider (above or below ConsoleSupportServiceProvider) it still loads the original AppNameCommand over my custom one.
I've already got a work around BUT it would be nice to know about the behaviour of singleton services for the future if this is at all possible? (This is using Laravel 5.2 if that makes any difference.)

There's actually a cleaner way to do this. You basically want to extend a core binding, which can be achieved by using the extend method:
$this->app->extend('command.app.name', function ($command, $app) {
return new AppNameCommand($app['composer'], $app['files']);
});
Jason Lewis has a really nice article regarding Laravel's IoC on Tutsplus. Make sure to check it out ;)

I looked at this case and it seems it not the easy one. If you use singleton in your custom Provider it will be finally overridden by default provider (deferred one) so it seems it won't be the way.
After checking that simple approach doesn't work, what you need to do in such case is analysing what is happening when Laravel registers this command.
So in your case you search first for command.app.name - you see it's in Illuminate\Foundation\Providers\ArtisanServiceProvider and there is method registerAppNameCommand you would like to probably override.
So now you look for occurences of ArtisanServiceProvider to see where it's launched - you see it's in Illuminate\Foundation\Providers\ConsoleSupportServiceProvider in $providers property (which you would like probably to change).
So finally you should look for occurrences of ConsoleSupportServiceProvider and you see it's in config/app.php.
So what you need to do in this case:
Change in config/app.php - change Illuminate\Foundation\Providers\ConsoleSupportServiceProvider into your custom one ConsoleSupportServiceProvider
In your custom one you should extend from \Illuminate\Foundation\Providers\ConsoleSupportServiceProvider but change in $providers from Illuminate\Foundation\Providers\ArtisanServiceProvider into your custom ArtisanServiceProvider
finally create custom ArtisanServiceProvider which will extend from \Illuminate\Foundation\Providers\ArtisanServiceProvider where you override registerAppNameCommand using custom class in singleton
Using this way you will achieve your goal (I've verified it that custom class will be used running command php artisan app:name).
Alternatively you might want in your custom ArtisanServiceProvider remove 'AppName' => 'command.app.name', from $devCommands and use your custom service provider as you showed where you register your singleton but I haven't tried this approach.

Related

laravel7 override vendor package class

I'm new to this service container stuff in general.
Just looking for an easy way to override the getView() method of the Captcha class.
My idea was to create a new class extending the captcha class:
<?php
namespace App\Http\Helpers\Captcha;
use Igoshev\Captcha\Captcha\Storage\StorageInterface;
use Igoshev\Captcha\Captcha\Generator\GeneratorInterface;
use Igoshev\Captcha\Captcha\Code\CodeInterface;
class CaptchaNew extends Igoshev\Captcha\Captcha
{
/**
* Get html image tag.
*
* #return \Illuminate\Contracts\View\Factory|\Illuminate\View\View
*/
public function getView()
{
//new code...
}
}
Inside the AppServiceProvider under register method using:
$loader = AliasLoader::getInstance();
$loader->alias('App\Http\Helpers\Captcha\CaptchaNew', 'Igoshev\Captcha\Captcha');
I already tried the boot method too, doesn't work too. What's the best way to override the class? A `serviceProvider' is provided too, but I want to keep things simple and I have no idea about serviceProviders in general.
You could try this way below to register your service, use bind function instead.
Under your register function in AppServiceProvider, replace yours with the sample code below. But please be noticed that first parameter of bind function must be same as facade accessor of vendor library (in this case is \Igoshev\Captcha\Captcha\Captcha::class, you can take a look here). Register this facade in config/app.php after the service provider of package:
$this->app->bind(\Igoshev\Captcha\Captcha\Captcha::class, function(){
return new CaptchaNew($this->app['config']);
})

Understanding Laravel View Composers: Where does my view composer belong?

I am reading through the Laravel documentation to get a deeper understanding and this would be the second time I run into a bit of confusion.
Not long ago, I was working through a Laracasts video in which we implement a View composer in our AppServicesProvider register method.
Which looks like this:
public function register()
{
view()->composer('layouts.sidebar', function ($view) {
$archives = \App\Post::archives();
$tags = \App\Tag::has('posts')->pluck('name');
$view->with(compact('archives', 'tags'));
});
}
What this does, is it makes archives and tags available for our sidebar view, which is available in almost every page. (The Laracast videos involved building a blog from scratch)
So far so good.
Today, as I am reading through the documentation of a view composer, I encounter this example:
/**
* Register bindings in the container.
*
* #return void
*/
public function boot()
{
// Using class based composers...
View::composer(
'profile', 'App\Http\ViewComposers\ProfileComposer'
);
// Using Closure based composers...
View::composer('dashboard', function ($view) {
//
});
}
Now, I THINK I understand creating your own ComposerServiceProvider and class based composers vs closure based. The source of my confusion is that this is being done in the boot() method vs the register() method.
It's totally possible that I wrote this in the wrong place. If I think about it, it almost doesn't make sense for it to be in the register() method, since register should only be used to register services. The boot is called after ALL the register methods are called. If someone could clear this up for me I would appreciate it!
Does view composer belong in the register() method or boot() method? And
if it belongs in the register() method, is it just a coincidence that
it still works for me?
Citing the answer given by a user in this question:
Difference between boot and register method?
I actually just learned the difference last night from Taylor's book.
Here is an excerpt about it:
“After all providers have been registered, they are “booted”. This
will fire the boot method on each provider. A common mistake when
using service providers is attempting to use the services provided by
another provider in the register method. Since, within the register
method, we have no guarantee all other providers have been loaded, the
service you are trying to use may not be available yet. So, service
provider code that uses other services should always live in the boot
method. The register method should only be used for, you guessed it,
registering services with the container. Within the boot method, you
may do whatever you like: register event listeners, include a routes
file, register filters, or anything else you can imagine.”
So the register one is just for binding. The boot one is to actually
trigger something to happen.
They should be placed in the boot method. The register method does not guarantee dependencies will be resolved when you may need them. With the boot method you have that guarantee. And as you mentioned, ideally you should create a separate service provider for the view composers.

Codeception DataFactory interaction between helper and other factories

I'm looking into using DataFactory's in Codeception for seeding of data, and for use in our acceptance tests. In the documentation there's mention of 2 approaches, one using the helper file and one using factories files.
We load both options using this snippet from our acceptance.suite.yml
class_name: AcceptanceTester
modules:
enabled:
- Db
- WebDriver
- \Helper\Acceptance
- Doctrine2:
connection_callback: getEntityManager
- DataFactory:
factories: tests/_support/factories
depends: Doctrine2
- \Helper\Factory
Both of the options seem to load correctly. As per the documentation I can then define factories like this, which will allow interaction with Doctrine.
// tests/_support/Helper/Factory.php
class Factory extends Module
{
/**
* #param array $settings
* #throws \League\FactoryMuffin\Exceptions\DefinitionAlreadyDefinedException
* #throws \Codeception\Exception\ModuleException
*/
public function _beforeSuite($settings = [])
{
/** #var Module\DataFactory $factory */
$factory = $this->getModule('DataFactory');
/** #var EntityManager $em */
$em = $this->getModule('Doctrine2')->_getEntityManager();
$factory->_define(User::class,[
// generate random user name
'name' => Faker::name(),
]);
parent::_beforeSuite($settings);
}
}
As per the other option, I can also create factories by loading all files from within tests/_support/factories, such as below:
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
/** #var \League\FactoryMuffin\FactoryMuffin $fm */
$user = $fm->create(User::class);
dd($user);
However, the seed.php version cannot seem to share the Factory, and errors with:
The model definition 'User' is undefined.
I wondered if maybe this could be solved by moving the Factory.php logic into the initialize() method but this seems to be called before FactoryMuffin has been initiliazed.
The documentation for this with codeception seems a bit sparse, and the FactoryMuffin docs, while better, don't cover Codeception integration. Just trying to work out if i'm missing something, or I just need to repeat the code in each place if I want to use both files/methods.
This is an old question and technology moves fast so the documentation has likely changed since this was originally asked but I'll make an attempt in case anyone else stumbles across it like I did.
You're using the DataFactory module which is great as it comes with the integration for Codeception out of the box. The two methods you've described are actually ways of integrating DataFactory with your data. By creating factory files, you've given DataFactory a means of generating data. But what if you have some data already in the database that you'd like to use in your tests as well? That's where you would use the Helper class. According to the DataFactory Module docs:
In cases you want to use data from database inside your factory definitions you can define them in Helper. For instance, if you use Doctrine, this allows you to access EntityManager inside a definition.
As for your issue of seed.php not finding the User model, you need to specify it according to the definition given in your factory. For example, if your factory file looks similar to this
<?php
use League\FactoryMuffin\Faker\Facade as Faker;
$fm->define('app\models\User')->setDefinitions([
'name' => Faker::name(),
... // the rest of your properties here
]);
Then seed.php would look like
// tests/_support/factories/seed.php
use League\FactoryMuffin\Faker\Faker;
$user = $fm->create('app\models\User');
Once you have the DataFactory module installed and configured, you can simply call it within the appropriate testing suite via have, haveMultiple, or make. See the Codeception Docs

How do I add a 'validator' class to this code to get it to work?

This is the error that I keep receiving: Reflection Exception Class validator does not exist This is the code causing the problems:
use Illuminate\Support\ServiceProvider;
class DeskServiceProvider extends ServiceProvider
{
/**
* Register bindings
*
* #return void
*/
public function register()
{
$this->repositories();
$this->app->bind('Desk\Forms\MessageForm', function($app) {
$validator = $app->make('validator')->make([], []);
return new \Desk\Forms\MessageForm($validator);
});
}
}
I now know that I need to add a Validator class but I am not sure where or what to put in it. Thank you for all your help.
Your question is a little confusing, as is your code. If looks like you're trying to bind a service.
$this->app->bind('Desk\Forms\MessageForm'
However, instead of telling Laravel the service name you want to use to identify your service (like db, or message_form, etc.) you're passing it a class name (Desk\Forms\MessageForm).
Then, you're using the application's make factory to instantiate a validator object. It's not clear if you're trying to use make to instantiate an object from a class named Validator, or if you're trying to instantiate a service object from a service named validator. If the later, it doesn't look like a validator service exists in your application. If the former, it doesn't look like a class named Validator is defined anywhere Laravel can autoload from.
Regarding the next obvious question: Where can Laravel autoload from, you either want this Validator class in your composer package's src folder, named in a way that's PSR valid. If you're not using composer and this is a local application, the easiest thing to do is drop the file in
app/models/Validator.php
However, it's also not clear from your question if you're trying to use the Laravel built-in Validator service facade/object. A better question might yield a better answer. (possibly of interest, and a self link, I'm in the middle of writing a series of articles that explains the Laravel application container, which you may find useful.).

Extend Laravels Router class (4.1)

I would like to extend Laravels Router class (Illuminate\Routing\Router) to add a method I need a lot in my application.
But sadly I can't get this to work. I already extended other classes successfully so I really have no idea where my wrong thinking comes from.
Anyway, right into the code:
<?php
namespace MyApp\Extensions;
use Illuminate\Routing\Router as IlluminateRouter;
class Router extends IlluminateRouter
{
public function test()
{
$route = $this->getCurrentRoute();
return $route->getParameter('test');
}
}
So as you see I want to get the parameter set by {test} in routes.php with a simple call like:
Router::test();
Not sure how to go on now. Tried to bind it to the IOC-Container within my ServiceProvider in register() and boot() but I got no luck.
Whatever I try I get either a constructor error or something else.
All solutions I found are too old and the API has changed since then.
Please help me!
edit:
I already tried binding my own Router within register() and boot() (as said above) but it doesn't work.
Here is my code:
<?php
namespace MyApp;
use Illuminate\Support\ServiceProvider;
use MyApp\Extensions\Router;
class MyAppServiceProvider extends ServiceProvider {
public function register()
{
$this->app['router'] = $this->app->share(function($app)
{
return new Router(new Illuminate\Events\Dispatcher);
}
// Other bindings ...
}
}
When I try to use my Router now I have the problem that it needs an Dispatcher.
So I have to do:
$router = new Router(new Illuminate\Events\Dispatcher); // Else I get an exception :(
Also it simply does nothing, if I call:
$router->test();
:(
And if I call
dd($router->test());
I get NULL
Look at: app/config/app.php and in the aliases array. You will see Route is an alias for the illuminate router via a facade class.
If you look at the facade class in Support/Facades/Route.php of illuminate source, you will see that it uses $app['router'].
Unlike a lot of service providers in laravel, the router is hard coded and cannot be swapped out without a lot of work rewiring laravel or editing the vendor source (both are not a good idea). You can see its hardcoded by going to Illuminate / Foundation / Application.php and searching for RoutingServiceProvider.
However, there's no reason i can think of that would stop you overriding the router class in a service provider. So if you create a service provider for your custom router, which binds to $app['router'], that should replace the default router with your own router.
I wouldn't expect any issues to arise from this method, as the providers should be loaded before any routing is done. So overriding the router, should happen before laravel starts to use the router class, but i've not this before, so be prepared for a bit of debugging if it doesn't work straight away.
So I was asking in the official Laravel IRC and it seems like you simply can't extend Router in 4.1 anymore. At least that's all I got as a response in a pretty long dialogue.
It worked in Laravel 4.0, but now it doesn't. Oh well, maybe it will work in 4.2 again.
Other packages suffer from this as well: https://github.com/jasonlewis/enhanced-router/issues/16
Anyway, personally I'll stick with my extended Request then. It's not that much of a difference, just that Router would've been more dynamic and better fitting.
I'm using Laravel 4.2, and the router is really hard coded into the Application, but I extended it this way:
Edit bootstrap/start.php, change Illuminate\Foundation\Application for YourNamespace\Application.
Create a class named YourNamespace\Application and extend \Illuminate\Foundation\Application.
class Application extends \Illuminate\Foundation\Application {
/**
* Register the routing service provider.
*
* #return void
*/
protected function registerRoutingProvider()
{
$this->register(new RoutingServiceProvider($this));
}
}
Create a class named YourNamespace\RoutingServiceProvider and extend \Illuminate\Routing\RoutingServiceProvider.
class RoutingServiceProvider extends \Illuminate\Routing\RoutingServiceProvider {
protected function registerRouter()
{
$this->app['router'] = $this->app->share(function($app)
{
$router = new Router($app['events'], $app);
// If the current application environment is "testing", we will disable the
// routing filters, since they can be tested independently of the routes
// and just get in the way of our typical controller testing concerns.
if ($app['env'] == 'testing')
{
$router->disableFilters();
}
return $router;
});
}
}
Finally, create YourNamespace\Router extending \Illuminate\Routing\Router and you're done.
NOTE: Although you're not changing the name of the class, like Router and RoutingServiceProvider, it will work because of the namespace resolution that will point it to YourNamespace\Router and so on.

Categories