Relationship not being passed to notification? - laravel

I created a notification that I am passing a model to:
class NewMessage extends Notification implements ShouldQueue
{
use Queueable;
protected $message;
public function __construct(Message $message)
{
$this->message = $message;
}
public function via()
{
return ['database'];
}
public function toArray()
{
Log::info($this->message);
return [
'message_id' => $this->message->id,
];
}
}
And this is how I call the notification:
$message = Message::where('user_id', Auth::user()->id)
->where('message_id', $message_id)
->with('topic')
->get();
$user->notify(new NewMessage($message));
The problem is that when the notification prints the log (Log::info($this->message);), the topic relationship doesn't show up.
However, I found that if I change the toArray() function in the nofitication class to this, it prints out fine:
public function toArray()
{
$this->message->topic;
Log::info($this->message);
return [
'message_id' => $this->message->id,
];
}
Why? How do I fix this?

Note: this question/answer is only relevant for Laravel < 5.6. Starting in Laravel 5.6, loaded relationships are also serialized, so the issue in this question is no longer an issue.
Your notification is set to queue, and the Notification class you're extending uses the SerializesModels trait. When an object with the SerializesModels trait is serialized to be put on the queue, any Models contained on that object (e.g. your message) are replaced with just the id of that model (the message id). When the queue worker unserializes your notification to process it, it will use that message id to re-retrieve the message from the database. Unfortunately, when this happens, no relationships are included.
So, even though your message had the topic relationship loaded when it was serialized, it will not have the topic relationship loaded when the queue worker processes the notification. If you need the topic inside of your notification, you will need to reload it, as you have seen.
You can read more about this in the documentation here. The relevant part is quoted below:
In this example, note that we were able to pass an Eloquent model directly into the queued job's constructor. Because of the SerializesModels trait that the job is using, Eloquent models will be gracefully serialized and unserialized when the job is processing. If your queued job accepts an Eloquent model in its constructor, only the identifier for the model will be serialized onto the queue. When the job is actually handled, the queue system will automatically re-retrieve the full model instance from the database. It's all totally transparent to your application and prevents issues that can arise from serializing full Eloquent model instances.

Related

How to avoid user take long time to wait while controller is processing

let's me explain example
User click button
run function trigger in Controller
User wait 30sec because MyModel::doSomeThing take long time to process
MyModel::doSomeThing do many thing. I don't want user to wait for it.
Is it possible to run MyModel::doSomeThing by don't care about result and return to user immediately?
function trigger(Request $request){
$id= $request->get('id');
MyModel::doSomeThing($id); // this one take 30 sec.
return response()->json([], 200);
}
If the result of doSomeThing() method isn't necessary for your response & can be done in background, I suggest using Events and Listeners, which will use queues to run in the background, and the user won't need to wait for this procces to finish. The process is fairly simple. Create event and it's listened with these two commands:
php artisan make:event YourEvent
php artisan make:listener YourListener --event=YourEvent
After that, register your event and listener in the App\Providers\EventServiceProvider, under the $listen array:
protected $listen = [
YourEvent::class => [
YourListener::class,
],
];
Now, when you have that sorted out, you need to build your event instance. Inside your newly created method, in the construct method, add this:
public $yourModel;
public function __construct(YourModel $yourModel)
{
$this->yourModel = $yourModel;
}
After you created your model, time to edit your listener, which will hanlde all the logic ghat you need. Inside this handle method, you will have the access to $yourModel instance that we defined in our event:
public function handle(YourEvent $event)
{
// Access your model using $event->yourModel...
YourModel::doSomeThing($event->yourModel);
}
The only thing left do to is to make your listener queueable. You can do this by adding implements ShouldQueue your listened definition:
class YourListener implements ShouldQueue
{
//
}
Now when we have everything setup, you can change your controller code to call this newly created event, and let the queue handle all the logic:
function trigger(Request $request){
$id= $request->get('id');
YourEvent::dispatch($id); //Calling event which will handle all the logic
return response()->json([], 200);
}
And that should be it. I haven't tested this code, so if you encounter any problems, let me know.

Attaching many to many relations while still binding to created event

So I've run into this issue a few times and now I've decided that I want to find a better solution.
For examples sake, I have two models, Order & Product. There is a many to many relation so that an order can have multiple products and a product can of course have multiple orders. Table structure looks like the below -
orders
id
more fields...
products
id
more fields...
product_orders
order_id
product_id
So when an order is created I run the following -
$order = Order::create($request->validated())
$order->products()->attach([1,2,3,4...]);
So this creates an order and attaches the relevant products to it.
However, I want to use an observer, to determine when the order is created and send out and perform related tasks off the back (send an order confirmation email, etc.) The problem being, at the time the order created observer is triggered, the products aren't yet attached.
Is there any way to do the above, establishing all the many to many relationships and creating the order at the same time so I can access linked products within the Order created observer?
Use case 1
AJAX call hits PUT /api/order which in turn calls Order::place() method. Once an order is created, an email is sent to the customer who placed the order. Now I could just put an event dispatch within this method that in turn triggers the email send but this just feels a bit hacky.
public static function place (SubmitOrderRequest $request)
{
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
return $order;
}
Use case 2
I'm feature testing to make sure that an email is sent when an order is created. Now, this test passes (and email sends work), but it's unable to output the linked products at this point in execution.
/**
* #test
**/
public function an_email_is_sent_on_order_creation()
{
Mail::fake();
factory(Order::class)->create();
Mail::assertSent(OrderCreatedMailable::class);
}
Thanks,
Chris.
I think the solution to your problem could be transaction events as provided by this package from fntneves.
Personally, I stumbled upon the idea of transactional events for another reason. I had the issue that my business logic required the execution of some queued jobs after a specific entity had been created. Because my entities got created in batches within a transaction, it was possible that an event was fired (and the corresponding event listener was queued), although the transaction was rolled back because of an error shortly after. The result were queued listeners that always failed.
Your scenario seems comparable to me as you don't want to execute your event listeners immediately due to missing data which is only attached after the model was actually created. For this reason, I suggest wrapping your order creation and all other tasks that manipulate the order within a transaction. Combined with the usage of said package, you can then fire the model created event as the actual event listener will only be called after the transaction has been committed. The code for all this basically comes down to what you already described:
DB::transaction(function() {
$order = Order::create($request->validated());
$order->products()->attach($request->input('products'));
});
In your model, you'd simply define an OrderCreated event or use an observer as suggested in the other answer:
class Order
{
protected $dispatchesEvents = [
'created' => OrderCreated::class,
];
}
class OrderCreated implements TransactionalEvent
{
public $order;
/**
* Create a new event instance.
*
* #param \App\Order $order
* #return void
*/
public function __construct(Order $order)
{
$this->order = $order;
}
}
You can redefine boot method in your model, if product ids is static
class Order extends Eloquent {
protected static function boot() {
parent::boot();
static::saving(function ($user) {
$this->products()->attach([1,2,3,4...]);
});
}
}
Or use observers
class OrderObserver
{
public function created($model)
{
//
}
}
And register this
class EventServiceProvider extends ServiceProvider
{
public function boot(DispatcherContract $events)
{
parent::boot($events);
Order::observe(new OrderObserver());
}
}

Laravel Cancel Creation of Object

I have a Laravel app which has an object, Position, which is created via a form.
class Position extends Model
{
protected $dispatchesEvents = [
'creating' => PositionCreating::class,
];
And this calls an event of the PositionCreating class, which I've tested, and is correctly firing. The underlying code also works to give me success or fail criteria.
class PositionCreating
{
use Dispatchable, InteractsWithSockets, SerializesModels;
public function __construct(Position $position)
{
if (some_good_stuff())
{
//keep creating the object
} else {
//stop creating the object
}
}
If it works, that's fine, I just let the __construct() function finish executing and everything, including the pre-execution code I want, works perfectly.
But I don't know how to actually stop the creation of the object. I can, of course use the dd() function or something (which works and stops creation of the object as expected), but I want to present a readable error to the user in a friendly manner. What function or commands should I be using to cancel the creation of the object to return back to my position.create method?
A bit late answer but this is a way to do it. Models fire several events. The one you're looking for is probably the "created" event. Each model event receive an instance of the model so you could just attach an event on your model, just like this:
protected $dispatchesEvents = [
'created' => PositionCreated::class,
];
Inside your "PositionCreated" event, add a public property to get the model instance,like this:
public $position;
public function __construct(Position $position)
{
$this->position=$position;
}
Finally just add the logic on your "handle" method inside your event listener.
public function handle($event)
{
if($something)
{
$event->position->delete();
}
}
This should do the work.You can check for the other events and see wich one suits you the most.

Broadcasting eloquent events using observers

Currently I am using observers to handle some stuff after creation and updating of my models.
I want to update my app by making it real-time using laravel-echo but I am not able to find documentation regarding the use of laravel-echo in combination with observers (instead of events).
You can use events and their broadcast functionality in combination with their respective listeners to get this functionality but I like the more clean code of observers (less "magic").
Looking at the code of the laravel framework I can see that the observable still uses eloquent events so I do suspect that there is a way to broadcast these.
So my question: is there a way to broadcast eloquent events using laravel-echo without creating individual events or manually adding broadcast statements on every event?
Interesting question! We can create a reusable, general-purpose observer that broadcasts events fired from the models that it observes. This removes the need to create individual events for each scenario, and we can continue to use existing observers:
class BroadcastingModelObserver
{
public function created(Model $model)
{
event(new BroadcastingModelEvent($model, 'created'));
}
public function updated(Model $model) { ... }
public function saved(Model $model) { ... }
public function deleted(Model $model) { ... }
}
class BroadcastingModelEvent implements ShouldBroadcast
{
public $model;
public $eventType;
public function __construct(Model $model, $eventType)
{
$this->model = $model;
$this->eventType = $eventType;
}
public function broadcastOn() { ... }
}
Then, simply instruct the observer to observe any models that you need to broadcast events to Echo for:
User::observe(BroadcastingModelObserver::class);
Post::observe(BroadcastingModelObserver::class);
...
As you know, multiple observers can observe the same model. This is a very simple example. We can do a lot of neat things with this pattern. For instance, we could declare which attributes we want to broadcast on each model and configure the event to filter out any that the model doesn't explicitly allow. Each model might also declare the channel that the event publishes to or the type of events that it should broadcast.
Alternatively, we could broadcast the event from your existing observers, but it sounds like you want to avoid adding these statements to each one.

Queueable entity App\Setting not found for ID error in Laravel

I am trying to send emails in laravel 5.1 by using queues. When running queue listen command on terminal,
php artisan queue:listen
Displays below error on terminal,
[Illuminate\Contracts\Queue\EntityNotFoundException]
Queueable entity [App\Setting] not found for ID [].
Values of jobs table is not process. Any idea ?
How can I process my queue ?
I know this question is a few months old, but I'd like to add an observation of mine while encountering this very same error message. It is due to the EventListener (interface of ShouldQueue in this example for asynchronous) not being able to resolve a dependant variable correctly (out of scope or not included in scope of Event object passed through the handle(Event $event) method of EventListener).
For me, this error was fired when I put my code within the __construct block within the EventListener:
public function __construct(Event $event)
{
$localProperty = $event->property
Mail::queue(etc...);
}
public function handle()
{
// Yeah I left this blank... whoops
}
Instead, the handle() method of the EventListener takes an Event interface and when called processes the job in the queue:
In the Event:
public function __construct(Object $ticket, AnotherObject $user)
{
$this->ticket = $ticket;
$this->user = $user;
}
And in Event Listener
class SomeEventListener implements ShouldQueue
{
use InteractsWithQueue;
use SerializesModels;
public function __construct()
{
// Leave me blank!
}
public function handle(Event $event)
{
$someArray = [
'ticket' = $event->ticket,
'user' = $event->user,
];
Mail::queue('some.view', $someArray, function($email) use ($someArray) {
// Do your code here
});
}
}
Although a tad late, I hope this helps someone. Queues are similar to Events (with the exception of Jobs being the main driving force behind Queues), so most of this should be relevant.
Turned out that it was because a model was added to the queue, that has since been deleted.

Resources