Laravel Eloquent Model Observer deleted method throws an exception. What is wrong? - events

Can you see what I'm doing wrong with the following code?
/**
* Listen to the AdministerRole deleted event.
* #param AdministerRole $role
* #return void
*/
public function deleted(AdministerRole $role)
{
//This works
$arg_list = func_get_args();
event(new BroadcastingModelEvent($arg_list, 'deleted'));
//This throws an exception
//event(new BroadcastingModelEvent($role, 'deleted'));
//No query results for model [App\Models\AdministerRole].
}
When I delete an AdministerRole model and pass the parameter $role to my BroadcastingModelEvent, an exception is thrown suggesting that perhaps the model can't be found because it's already deleted:
No query results for model [App\Models\AdministerRole]].
However, if I use func_get_args to get the parameters which were passed to the function, and pass the resulting array to BroadcastingModelEvent, I can see a JSON representation of the model collected by Pusher on the client side. What on earth is the difference that's causing this exception?

I got the clue to this problem from this SerializesModels with recently deleted models discussion on the Laravel GitHub page.
My BroadcastingModelEvent's constructor is expecting an Eloquent Model. If I use the SerializesModels trait, it throws an No query results for model exception as discussed in the GitHub page if the event is deleted. The argument seems to be that if the model is already deleted it shouldn't be serializable - or something. The logic escapes me because the deleted event receives the model which has just been deleted and that model is accessible within the deleted event handler, and furthermore, you can call toArray() or toJson() to serialise it! That's my solution. The observer deleted method calls toArray() on the model passed to it and sends that array to the BroadcastingModelEvent constructor. I simply commented out SerializesModels and pre-serialised my model in the observer. If you can explain and justify that seeming inconsistency I'll bake you some shortbread.
public function deleted(AdministerRole $role)
{
event(new BroadcastingModelEvent($role->toArray(), 'deleted'));
}
And BroadcastingModelEvent handles it
namespace App\Events;
use Illuminate\Broadcasting\Channel;
use Illuminate\Queue\SerializesModels;
use Illuminate\Broadcasting\PrivateChannel;
use Illuminate\Broadcasting\PresenceChannel;
use Illuminate\Foundation\Events\Dispatchable;
use Illuminate\Broadcasting\InteractsWithSockets;
use Illuminate\Contracts\Broadcasting\ShouldBroadcast;
use Illuminate\Database\Eloquent\Model as Model;
/**
* https://github.com/laravel/framework/issues/10733
*/
class BroadcastingModelEvent implements ShouldBroadcast {
use Dispatchable,
InteractsWithSockets;
//Throws an exception after the deleted event.
//SerializesModels;
public $modelArray;
public $eventType;
public function __construct($modelArray, $eventType) {
$this->modelArray = $modelArray;
$this->eventType = $eventType;
}
public function broadcastOn() {
return new Channel('MyObservers');
}
public function broadcastAs() {
return 'MyBroadcastEvent';
}
}

Related

How to mock laravel model relathipship?

I have a model that has a relationship with a View, that is complicate to popolate for make the feature test, but in the same time this is called from some component that are inside the controller called.
The following code is an example:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use App\Models\TemperatureView;
class Town extends Model
{
function temperature()
{
return $this->hasOne(TemperatureView::class);
}
}
This is an example of the controller:
<?php
namespace App\Http\Controllers;
use App\Models\Town;
class TownController extends Controller
{
public function update($id)
{
// Here is the validation and update of Town model
$UpdatedTown = Town::where('id',$id);
$UpdatedTown->update($data);
$this->someOperation($UpdatedTown);
}
private function someOperation($Town)
{
//Here there is some operation that use the temperature Relationship
/*
Example:
$Town->temperature->value;
*/
}
}
The test is like is like this:
<?php
namespace Tests\Feature;
use Illuminate\Foundation\Testing\RefreshDatabase;
use Illuminate\Foundation\Testing\WithoutMiddleware;
use Tests\TestCase;
use App\Models\TownModel;
use Mockery;
use Mockery\MockInterface;
class TownTest extends TestCase
{
/**
* A basic test example.
*
* #return void
*/
public function test_get_town_temperature()
{
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test failed
}
public function test_get_town_temperature_with_mocking()
{
$this->instance(
TownModel::class,
Mockery::mock(TownModel::class, function (MockInterface $mock) {
$MockDataTemperature = (object) array('value'=>2);
$mock->shouldReceive('temperature')->andReturn($MockDataTemperature);
})
);
$payload = ['someTownInformation' => 'Value'];
$response = $this->post('/Town/'.$idTown,$payload);
$response->assertStatus(200);
//This test also failed
}
}
The first test failed because the Controller has some check on the relationship temperature, that is empty because the view on database is empty.
The second test failed also for the same reason. I tried to follow some others questions with the official guide of Laravel Mocking. I know this is mocking object and not specially Eloquent.
Is something I'm not setting well?
If it's not possible to mock only the function, is possible to mock all the relationship of view, bypassing the DB access to that?
Edit
I undestand that the mocking work only when the class is injected from laravel, so what I wrote above it's not pratical.
I don't know if it's possible to mock only it, I saw a different option, that to create the interface of the model and change for the test, but I didn't want to make it.

Telegram notifications in Octobercms plugin

I would like to send a telegram message to a specific user at 17:00 using laravel's Telegram notification channel, I however can't seem to get it going. I currently use a cmd command for testing, but keep getting errors and don't know what to do.
Here are my files for the command and notification:
SendNotification.php
<?php
namespace Rogier\Lab\Console;
use Illuminate\Console\Command;
use Rogier\Lab\Notifications\DailyTelegram;
class SendNotifications extends Command
{
protected $name = 'lab:notifications:send';
protected $description = 'Send notifications';
protected $userid = 919871501;
/**
* Execute the console command.
* #return void
*/
public function handle()
{
$this->output->writeln('Sending notifications');
$notification = new DailyTelegram($this->userid);
$notification->via()->toTelegram();
$this->output->writeln('Done');
}
}
and DailyTelegram.php
<?php
namespace Rogier\Lab\Notifications;
use NotificationChannels\Telegram\TelegramChannel;
use NotificationChannels\Telegram\TelegramMessage;
use Illuminate\Notifications\Notification;
use Illuminate\Notifications\Notifiable;
class DailyTelegram extends Notification
{
protected $userid = 919871501;
public function via()
{
return [TelegramChannel::class];
}
public function toTelegram()
{
return TelegramMessage::create()
// Optional recipient user id.
->to($this->userid)
// Markdown supported.
->content("Hello there!\nYour invoice has been *PAID*");
}
}
I currently get the error "Call to a member function toTelegram() on array", but I feel like I tried everything, maybe I'm doing it completely wrong. Does anyone know how I should do it?
thanks in advance
Yes, you are doing it wrong. Notifiables have notify() method, you should use it:
$user->notify(new DailyTelegram);
In this example $user is App\User instance (which is notifiable out of the box).
You should check out both Laravel's sending notifications and laravel-notification-channels/telegram docs.

How to always use withTrashed() for model Binding

In my app, I use soft delete on a lot of object, but I still want to access them in my app, just showing a special message that this item has been deleted and give the opportunity to restore it.
Currently I have to do this for all my route parametters in my RouteServiceProvider:
/**
* Define your route model bindings, pattern filters, etc.
*
* #return void
*/
public function boot()
{
parent::boot();
Route::bind('user', function ($value) {
return User::withTrashed()->find($value);
});
Route::bind('post', function ($value) {
return Post::withTrashed()->find($value);
});
[...]
}
Is there a quicker and better way to add the trashed Object to the model binding ?
Jerodev's answer didn't work for me. The SoftDeletingScope continued to filter out the deleted items. So I just overrode that scope and the SoftDeletes trait:
SoftDeletingWithDeletesScope.php:
namespace App\Models\Scopes;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletingScope;
class SoftDeletingWithDeletesScope extends SoftDeletingScope
{
public function apply(Builder $builder, Model $model)
{
}
}
SoftDeletesWithDeleted.php:
namespace App\Models\Traits;
use Illuminate\Database\Eloquent\SoftDeletes;
use App\Models\Scopes\SoftDeletingWithDeletesScope;
trait SoftDeletesWithDeleted
{
use SoftDeletes;
public static function bootSoftDeletes()
{
static::addGlobalScope(new SoftDeletingWithDeletesScope);
}
}
This effectively just removes the filter while still allowing me to use all the rest of the SoftDeletingScope extensions.
Then in my model I replaced the SoftDeletes trait with my new SoftDeletesWithDeleted trait:
use App\Models\Traits\SoftDeletesWithDeleted;
class MyModel extends Model
{
use SoftDeletesWithDeleted;
For Laravel 5.6 to 7
You can follow this doc https://laravel.com/docs/5.6/scout#soft-deleting. And set the soft_delete option of the config/scout.php configuration file to true.
'soft_delete' => true,
For Laravel 8+
You can follow this doc https://laravel.com/docs/8.x/routing#implicit-soft-deleted-models. And append ->withTrashed() to the route that should accept trashed models:
Ex:
Route::get('/users/{user}', function (User $user) {
return $user->email;
})->withTrashed();
You can add a Global Scope to the models that have to be visible even when trashed.
For example:
class WithTrashedScope implements Scope
{
public function apply(Builder $builder, Model $model)
{
$builder->withTrashed();
}
}
class User extends Model
{
protected static function boot()
{
parent::boot();
static::addGlobalScope(new WithTrashedScope);
}
}
Update:
If you don't want to show the deleted objects you can still manually add ->whereNull('deleted_at') to your query.

withTrashed on hasManyThrough relation

How can withTrashed be applied on a hasManyThrough relation ?
$this->hasManyThrough('App\Message', 'App\Deal')->withTrashed();
returns
Call to undefined method Illuminate\Database\Query\Builder::withTrashed()
when i'm doing:
$messages = Auth::user()->messages()->with('deal')->orderBy('created_at', 'DESC')->get();`
Here is my Deal model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Deal extends Model
{
use SoftDeletes;
/* ... */
protected $dates = ['deleted_at'];
public function user() {
return $this->belongsTo('App\User');
}
public function messages() {
return $this->hasMany('App\Message'); // I've tried to put withTrashed() here, there is no error but it doesn't include soft deleting items.
}
}
To all those coming to this late, there is now a native way of doing this with Laravel.
$this->hasManyThrough('App\Message', 'App\Deal')->withTrashedParents();
This is not well documented but can be found in Illuminate\Database\Eloquent\Relations\HasManyThrough
The error is thrown because you are requesting a messages with deleted ones without using SoftDelete trait in Message model.
After I check the hasManyThrough relation code I found that there is no way to do it in this way, you should play around.
Ex:
get the deals of user with messages instead
$deals = Auth::user()->deals()->withTrashed()->with('messages')->get();
foreach($deals as $deal) {
//Do your logic here and you can access messages of deal with $deal->messages
}

How to access my model in laravel?

I can't seem to figure out what's wrong with this code. I'm running laravel 5.4.
The error:
https://www.dropbox.com/s/64ioxdf4mv6ps1w/Screenshot%202017-02-28%2020.11.33.png?dl=0
The Controller function:
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use App\Thread;
class ThreadController extends Controller
{
public function show($id) {
return Thread::where('id', '=', $id)->messages();
}
}
The Model:
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
class Thread extends Model
{
public function messages()
{
return $this->hasMany(Message::class)->get();
}
}
I suggest adding your error code instead of linking to image (right now AWS is down, thus I'm unable to view the image).
Regardless, the messages method is defining a relationship and as such, is an instance of the query builder. Rather than chaining the get method there, I suggest a slightly different approach:
In your controller
public function show($id)
{
// Use first() instead of get() since you're returning a
// specific model by its primary key (I assume)
return Thread::where('id', $id)->with('messages')->first();
}
And in your model
public function messages()
{
// This returns a query builder instance, which is what you want.
// It defines the relationship between Thread and Message
return $this->hasMany(Message::class);
}
This will eager load your messages along with your Thread model.
Hope it helps!
Regarding Joel's question in the comments...
User::find(1)->messages->create($request->only('body'));
Is not calling the 'messages function' you mentioned and not returning the relationship. Instead, it is calling the messages property, which is something different.

Resources