Understand Laravel 5.3 Events - laravel

I create event using command php artisan event:make EventTest and that generates this class:
class EventTest
{
use InteractsWithSockets, SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Get the channels the event should broadcast on.
*
* #return Channel|array
*/
public function broadcastOn()
{
return new PrivateChannel('channel-name');
}
}
Official documentation for Laravel 5.3 Events however looks different https://laravel.com/docs/5.3/events#defining-events, event that it uses as example looks like this:
class OrderShipped extends Event
{
use SerializesModels;
public $order;
/**
* Create a new event instance.
*
* #param Order $order
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
As you can see it extends Event, my Event created by artisan does not extend Event, also my class uses InteractsWithSockets while class on laravel documentation does not. Why this difference, what does it mean? Is Laravel's 5.3 documentation already out of date and not updated to reflect latest changes?

It appears you're correct. It looks like this particular part of the documentation was not updated with the 5.3 release.
To answer your question more completely, Laravel 5.3 no longer uses the abstract Event class you were seeing referenced in the 5.2 documentation.
The abstraction itself was unnecessary, and so it was removed.
As for the InteractsWithSockets trait you're seeing, that was added for a more seamless integration with Websockets. If your application isn't using Websockets (see: Socket.io), then you don't have to leverage these traits and the use statement may be removed safely.
Anyone may edit the documentation, so you may want to consider putting in a pull request with the docs here.

Related

How to perform action globally when record get updated in laravel

Is that possible to run this job UpdateRateZone globally wheneven Consignment Model gets updated anywhere in the system?
One method is to use Observer but observer doesn't work when update multiple reccord at once like
Consignment::where('status',1)->update(['address'=>'This']);
Is there anything else we can do?
As per laravel docs :
When issuing a mass update via Eloquent, the saving, saved, updating,
and updated model events will not be fired for the updated models.
This is because the models are never actually retrieved when issuing a
mass update.
Laravel does not fire updated event in case of mass update, so its not possible as per my knowledge. Other way is to do manually.
Other than observers there is methods such using closures for events and Registering events manually but all these methods would work if only laravel trigger an event on mass updation .
Yes you can create a Event Listener for your model. You can read up on more info here
In short first you need to create an Event for the needed model, so if its a Updated event create something like this.
php artisan make:event Consignment/Updated
In that file add
class Updated
{
use Dispatchable, InteractsWithSockets, SerializesModels;
/**
* #var Consignment
*/
public $consignment;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Consignment $consignment)
{
$this->consignment= $consignment;
}
/**
* Get the event consignment property
*
* #return Consignment
*/
public function getConsignment()
{
return $this->consignment;
}
}
Now you must create a Listener for this event
php artisan make:listener Consignment/UpdatedEvent
And in the handle function add youre logic
/**
* Handle the event.
*
* #param Updated $event
* #return void
*/
public function handle(Updated $event)
{
//
}
And all that is left after that is to register the events for you Model and you do that in your Consignment.php class where you add
/**
* The event map for the category.
*
* #var array
*/
protected $dispatchesEvents = [
'updated' => Updated::class,
];
When you do this:
Model::where('status',1)->update([some stuff]);
Query Builder's update() method is executed instead of Eloquent's update() method.
If you want to trigger Eloquent events, you need to update rows one by one.
You can fire these events manually, but it's tricky and it's a bad idea.
You could just run the UpdateRateZone job manually for all the Consignments that were updated

Laravel - extending Illuminate\Http\Request and using session

I've extended the Illuminate\Http\Request class and am passing it along to my controller.
use Illuminate\Http\Request;
class MyRequest extends Request
{
...
}
Controller
class MyController
{
// Doesnt work
public function something(MyRequest $request) {
var_dump($request->session())
}
// Does work
public function something(Illuminate\Http\Request $request) {
var_dump($request->session())
}
}
So when I'm trying to get session $request->session() I get RuntimeException - Session store not set on request.
I feel it has something to do with not running middlewares on my custom request but I dont know how to make it work. Helping or pionting to the right direction would be much apreciated.
To give a little bit more info. I'm trying to make a wizard. Several pages where content of one page depends on choices on previous pages. I'm storing the data in session and on the final page I do "stuff" with it and clear the session storage of current user.
Because it a lot of lines of code and since session instace lives on request I though it would be elegant to hide all those line it in custom request and in controler simply call $myRequest->storeInputs()
This is what seemed to me as "most elegant" in this particular case so I would prefer to finish it this way but I'm also open to a different solution if there is a better aproach.
Summary: basically where should I hide all those lines which are storing and retriving data from sesison?
Solution: I actually solved it by extending FormRequest since it was solution which was the best fit for what I was trying to do. However I accepted the one offered answer since I believe it is generally better solution and I would use it if not for this very particullar case.
The classic Laravel request already got a bunch of settings you didn't catch on your custom request. To achieve that, you should setup a middleware (maybe global in your use-case) which replaces old request in Laravel's container by yours.
<?php
namespace App\Http\Middleware;
use App\Http\MyRequest;
use Closure;
use Illuminate\Contracts\Foundation\Application;
use Illuminate\Http\Request;
class CustomizeRequest
{
/**
* #var \Illuminate\Contracts\Foundation\Application
*/
protected $app;
/**
* #var \App\Http\MyRequest
*/
protected $myRequest;
/**
* #param \Illuminate\Contracts\Foundation\Application $app
* #param \App\Http\MyRequest $myRequest
*/
public function __construct(Application $app, MyRequest $myRequest)
{
$this->app = $app;
$this->myRequest = $myRequest;
}
/**
* Handle an incoming request.
*
* #param \Illuminate\Http\Request $request
* #param \Closure $next
* #return mixed
*/
public function handle(Request $request, Closure $next)
{
$this->app->instance(
'request', Request::createFrom($request, $this->myRequest)
);
return $next($this->myRequest);
}
}

Middleware before Model injection

I have a design doubt I would like to share.
I have a model in Laravel with an Observer at retrieved:
class MailingObserver
{
public function retrieved($mailing)
{
// we retrieve HTML content from disk file
$mailing->setAttribute('content', \Illuminate\Support\Facades\Storage::disk('mailings')->get("{$mailing->id}-{$mailing->slug}.html"));
$mailing->syncOriginal();
}
}
which retrieve an attribute stored in a plain text instead of database.
The site is a multibrand platform so disk('mailings') is different per each logged user. This configuration is loaded in a middleware according to the the current logged user.
Up to here all is fine.
Now the "problem". I have a Controller which injects the entity Mailing:
class MailingCrudController extends CrudController
{
/**
* Sends the mailing
* #param Request $request
* #param \App\Mailing $mailing
*/
public function send(Request $request, \App\Mailing $mailing)
{
// WHATEVER
}
}
When the model is injected the retrieved Observer method is fired but the Middleware wasn't still executed so mailings disk is still not set up.
I don't know how to change this order: first execute middleare, then the model injection.
One approach
I tried in AppServiceProvider to add:
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$middleware = new \App\Http\Middleware\CheckBrandHost();
$middleware->setBrandInformation(request());
$middleware->loadBrandConfig(request()->get('brand.code_name'));
}
Would you approve this solution? What problems can cause it to me? Is it the proper way to do it?
Thanks all!

Custom laravel migration command "[Illuminate\Database\Migrations\MigrationRepositoryInterface] is not instantiable"

I'm trying to create a custom laravel (5.2) migration command that basically works the same as migrate:status except it just lists the pending migrations instead of all the migrations.
To do this i've very simply copied the migrate:status into another class within my app/console directory and adjusted the code to suit my needs. However whenever I try to run it I get an error:
[Illuminate\Contracts\Container\BindingResolutionException]
Target [Illuminate\Database\Migrations\MigrationRepositoryInterface] is not instantiable while building [App\Console\Commands\PendingMigrations, Illuminate\Database\Migrations\Migrator].
The contents of the class itself and the fire() method doesn't seem to matter as it doesn't get that far, it fails within the __construct() method.
<?php namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Database\Migrations\Migrator;
class PendingMigrations extends Command
{
/**
* The console command name.
*
* #var string
*/
protected $name = 'migrate:pending';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Shows a list of pending migrations';
/**
* The migrator instance.
*
* #var \Illuminate\Database\Migrations\Migrator
*/
protected $migrator;
/**
* Create a new migration rollback command instance.
*
* #param \Illuminate\Database\Migrations\Migrator $migrator
* #return \Illuminate\Database\Console\Migrations\StatusCommand
*/
public function __construct(Migrator $migrator)
{
parent::__construct();
$this->migrator = $migrator;
}
/**
* Execute the console command.
*
* #return void
*/
public function fire()
{
}
}
The reason for it is likely to be something to do with the IoC container and the order with which things are loaded, but I don't know enough about the inner workings of Laravel to figure out any more than that.
It surely must be possible?
I am currently stuck on 5.2, so i'm not sure if this problem exists in more recent versions.
The only thing i've attempted so far is added the migration service provider to the top of the list in config/app.php however it didn't seem to have an affect and it was just a random guess anyway.
providers' => [
Illuminate\Database\MigrationServiceProvider::class,`
]
I got around this using:
$this->migrator = app('migrator');
but it is not necessarily the best way to do this
The Migrator instance is not bound to the class name in the IoC container, it is bound to the migrator alias.
From Illuminate\Database\MigrationServiceProvider:
/**
* Register the migrator service.
*
* #return void
*/
protected function registerMigrator()
{
// The migrator is responsible for actually running and rollback the migration
// files in the application. We'll pass in our database connection resolver
// so the migrator can resolve any of these connections when it needs to.
$this->app->singleton('migrator', function ($app) {
$repository = $app['migration.repository'];
return new Migrator($repository, $app['db'], $app['files']);
});
}
Since the class name is not bound in the IoC container, when Laravel resolves your command and attempts to resolve the Migrator dependency, it attempts to build a new one from scratch and fails because the Illuminate\Database\Migrations\MigrationRepositoryInterface is also not bound in the IoC container (hence the error you're receiving).
Since Laravel can't figure this out itself, you need to either register the binding for the Migrator class name, or you need to register the binding for your command. Laravel itself registers all the bindings for the commands in the Illuminate\Foundation\Providers\ArtisanServiceProvider. An example of the command.migrate binding:
/**
* Register the command.
*
* #return void
*/
protected function registerMigrateCommand()
{
$this->app->singleton('command.migrate', function ($app) {
return new MigrateCommand($app['migrator']);
});
}
So, in your AppServiceProvider, or another service provider you setup, you can add one of the following:
Register the command in the IoC:
$this->app->singleton(\App\Console\Commands\PendingMigrations::class, function ($app) {
return new \App\Console\Commands\PendingMigrations($app['migrator']);
});
Or, register the Migrator class name in the IoC:
$this->app->singleton(\Illuminate\Database\Migrations\Migrator::class, function ($app) {
return $app['migrator'];
});
As I don't want to register the migrator everywhere in the app, but I still want to extend the MigrateCommand itself, I came up with this approach to maintain my app as it is:
public function __construct()
{
app()->singleton(\App\Console\Commands\PendingMigrations::class, function ($app) {
return new \App\Console\Commands\PendingMigrations($app['migrator']);
});
parent::__construct(app('migrator'));
}

Laravel Architecture - Event vs. Controller

My question is related more about the architecture of an Laravel application. I'm developing an application and I have some problems about the positioning of my codes. Let's assume I have a controller to control the comments of my post, but now, after a while, I need to add an action each time a comment is registered, in this case, I create an event or simply add this new action to my controller action?
Thank you.
As Bogdan's says, you should read Model Events.
A sample approach could be the next.
Create a service provider:
php artisan make:provider CommentServiceProvider
Then a sample CommentServiceProvider class:
<?php
namespace App\Providers;
use App\Comment;
use Illuminate\Support\ServiceProvider;
class CommentServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Comment::creating(function ($comment) {
//... do stuff here
});
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}

Resources