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.
Related
I have a laravel app and have created a SocialMediaController to get the latest posts from twitter and instagram and store them in the database.
I need them to be universally accessible and know I can access it via the IOC.
public function doSomething(App\Http\Controllers\SocialMediaController
$SocialMedia) {
}
However if feels wrong to inject a controller like this. What is be best way to wrap up these methods for global use?
It seems like you want to share some logic you have in the SocialMediaController with another controller, right?
Instead of trying to pass a controller instance to the controller action using the service container, you may move the current logic to a service. Refer to Service Container and Service Providers in the Laravel docs on how to create your own services.
Another way to achieve that is using a trait. Check this answer on how you can do that https://stackoverflow.com/a/30365349/1128918. You would end up with something like this:
trait SocialFeeder {
public function getLatestFromTwitter() {
...
}
}
And, then, you can use that new trait with your controllers:
class SocialMediaController extends Controller {
use SocialFeeder;
public function doSomething() {
...
$tweets = $this->getLatestFromTwitter();
...
}
}
I'm in Laravel 5.1, for reference.
I'm looking to extend the Log feature in such a way that running it from any point in the code (including in the built in vendor libraries) will also trigger an additional method of my choosing. How would I go about doing this?
Laravel fires an event named illuminate.log when logging the data. So your can listen to that event and can call your extra methods.
For example:
Add following code in your boot() method of class App\Providers\AppServiceProvider:
public function boot()
{
\Event::listen('illuminate.log', function ($level, $message, $context) {
// call your extra functions
});
}
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.
I'm currently developing a package in/for Laravel 5.
My package contains a custom middleware and I would like to add it to the $routeMiddlewarearray of the Kernel class from in my package Service Provider.
But I can't seem to find a way to do this.
I tried to make a custom class that extends the Kernel class and then I can merge the array with my array.
But once out of the constructor it's not possible.
In L4 you had App::middleware, but that function is no longer available in L5.
Can anyone who has solved this problem help me solving this?
Please, tell me if my question is not clear enough, so that I can clarerify it a bit.
Since Laravel 5.4 (tested up to 5.8) you would call the following line from a service provider.
$this->app['router']->aliasMiddleware('my-package-middleware', \My\Package\Middleware::class);
Or you can use the app() helper as below.
app('router')->aliasMiddleware('my-package-middleware', \My\Package\Middleware::class);
It's about Laravel 5.6
/*In your package service provider*/
public function boot()
{
/** #var Router $router */
$router = $this->app['router'];
$router->pushMiddlewareToGroup('web', MyPackage\Middleware\WebOne::class);
}
In your package service provider you can access the router instance like this:
$this->app['router']
Next you can register middleware like this:
$this->app['router']->middleware('middlewareName', 'your\namespace\MiddlewareClass');
you place this code in the register method of your service provider.
You can do this with these two ways:
Register your middle-ware inside your general middle-ware provider
Register the middle-ware inside your package Service Provider
lets try first one by create your TestMiddleware.php file in your src package folder and place it in somewhere, in my case i placed it inside Middle-ware folder and then add it to your composer.json auto-loader like this:
"autoload": {
"psr-4": {
"Vendor\\Package\\Middleware": "packages/Vendor/Package/src/Middleware"
}
}
And write your general middle-ware:
namespace Vendor\Package\Middleware;
class TestMiddleware {
public function handle( $request, Closure $next ) {
echo 'hello, world!';
}
}
And then then add the Middle-ware to your main project middle-ware, in Lumen you should add it like this:
$app->middleware([
Vendor\Package\Middleware\TestMiddleware::class
]);
Add middle-ware to package service provider
And the second way, create middle-ware and load it in autoloader like last example and then create a service provider and register your middle-ware inside boot method:
public function boot()
{
$this->app->middleware([
\Vendor\Package\Middleware\TestMiddleware::class
]);
}
And finally you should register your service provider inside your main project (Lumen example)
$app->register(Vendor\Package\TestServiceProvider::class);
in laravel 9 in package service provider and in register function will use
app('router')->aliasMiddleware method for ex:
app('router')->aliasMiddleware('yourmiddleware',yourclass::class)
Solution for Lumen:
$this->app->middleware($middleware);
There are two ways to register your package middlewares.
The first way would be pushing middleware into existing middleware group with the following method:
$this->middlewareGroup($group_name, $middleware_list)
And the other way would be registering a custom middleware and assigning your middlewares to that group.
$this->pushMiddlewareToGroup($group_name, $middleware_list)
You can learn more in the links below
middlewareGroup:
https://laravel.com/api/5.5/Illuminate/Routing/Router.html#method_middlewareGroup
pushMiddlewareToGroup: https://laravel.com/api/5.5/Illuminate/Routing/Router.html#method_pushMiddlewareToGroup
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.