Laravel mass update , still fire event - laravel

As stated in the doc, laravel will not fire an event on mass update/insert/delete.
https://laravel.com/docs/5.8/eloquent#events
It uses the Builder for this and will not fire an event.
Is there a way that I can still fire an event after a mass update for example? I would only need the query Builder to extract the needed info myself ( log purposes).

It is actually possible , but you have to extend the Eloquent builder ,overwrite the update/insert methods and send the event there.
Just been playing around with it... Needs work, but the basic idea is the following :
class Test extends Model
{
protected $guarded = [];
public $dispatchesEvents = [
'saved' => SavedTest::class
];
/**
* INCLUDE this as a trait in your model.
* Overwrite the eloquentBuilder.
*
* #param \Illuminate\Database\Query\Builder $query
* #return \Illuminate\Database\Eloquent\Builder|static
*/
public function newEloquentBuilder($query)
{
return new TestBuilder($query);
}
}
Extend the eloquent builder...
class TestBuilder extends Builder
{
/**
* Update a record in the database and fire event.
*
* #param array $values
* #return int
*/
public function update(array $values)
{
// normal eloquent behavior.
$result =$this->toBase()->update($this->addUpdatedAtColumn($values));
/*
* Fire event.
*/
if($result){
if( $event = Arr::get($this->model->dispatchesEvents,'saved')){
// at the attributes.
$this->model->fill($this->addUpdatedAtColumn($values));
$queryBuilder =$this->toBase();
event(new $event($this->model,$queryBuilder));
}
}
}
public function insert(array $values)
{
// same idea..
}
}
The event class :
class SavedTest
{
use SerializesModels;
public $model;
public $query;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct($model,$query =null)
{
$this->model = $model;
$this->query = $query;
}
}
The listener.
class SavedTestEvent
{
/**
* Create the event listener.
*
*
*/
public function __construct()
{
}
/**
* Handle the event.
*
* #param object $event
* #return void
*/
public function handle($event)
{
// The model , with the attributes.
dump($event->model);
// the query builder , you could extract the wheres or whatever to build your own log for it.
dump($event->query);
}
}

#Paolo on batch request it would not be file the event you must have to perform operation on single record.. like
Analytic::where('id', '>', 100)->get()->each(function($analytic) {
$analytic->delete();
});

Related

Laravel: resolveRouteBinding() Issue

I having a difficulty displaying my edit view. Displaying all event and creating an event seems to work fine. I even added my own custom resolveRouteBinding method to troubleshoot where the problem is, but it doesn't seem to work every time I hit the url on the browser.
This is the a part of the Event model
class Event extends Model
{
use HasAddress, HasTestData, SoftDeletes;
/**
* Retrieve the model for a bound value.
*
* #param mixed $value
* #param string|null $field
* #return \Illuminate\Database\Eloquent\Model|null
*/
public function resolveRouteBinding($value, $field = null)
{
return $this->find(1)->firstOrFail();
//abort(404);
}
}
This is the event part of the routes:
Route::resource('events', 'EventController')->only(['store', 'update','destroy']);
Route::prefix("events")->name("events.")->middleware("can:viewAny,App\Models\Event")->group(function() {
Route::get("/", function(){
return redirect("admin/events/overview");
});
Route::view("overview", 'admin.events.overview')->name('overview');
Route::view("manage", "admin.events.manage")->name("manage");
Route::view("create", "admin.events.create")->middleware("can:create,App\Models\Event")->name("create");
Route::get("{event}", function (App\Models\Event $event) {
return view("admin.events.view", ["event" => $event]);
})->middleware("can:view, event")->name("view");
Route::get("{event}/edit", function (App\Models\Event $event) {
return view("admin.events.edit", ["event" => $event]);
})->middleware("can:update, event")->name("edit");
});
While this is a part of the EventController
<?php
namespace App\Http\Controllers;
use App\Models\Event;
class EventController extends Controller {
/**
* Show the form for editing the specified resource.
*
* #param \App\Models\Event $event
* #return \Illuminate\Http\Response
*/
public function edit(Event $event){
$this->authorize('update', $event);
return view('admin.events.edit', ['event' => $event]);
}
}

Date and Time localization not works in Laravel-mix

I have Laravel mix installed on my server. there is a chat part on website and I use some kind of class :
class ActivityCell extends Component {
getTimestamp() {
const {message} = this.props;
return (
<span className="font-weight-semi-bold">
{utcDateCalendarTime(message.created_at)}
</span>
);
}
And here is my AppServiceProvider.php file :
<?php
namespace App\Providers;
use Illuminate\Http\Resources\Json\Resource;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function boot()
{
setlocale(LC_ALL, Config::get('app.lc_all'));
Carbon::setLocale(Config::get('app.locale'));
}
public function register()
{
$this->registerPlugins();
}
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->bootDatabase();
$this->bootResource();
}
/**
* Boot database schema
*
* #return void
*/
private function bootDatabase()
{
Schema::defaultStringLength(191);
}
/**
* Boot resource
*
* #return void
*/
private function bootResource()
{
Resource::withoutWrapping();
}
/**
* Register plugins
*
* #return void
*/
private function registerPlugins()
{
$pluginDirs = File::directories(base_path('app/Plugins'));
foreach ($pluginDirs as $pluginDir) {
$class = "App\\Plugins\\" . basename($pluginDir) . "\\PluginServiceProvider";
if (class_exists($class) && is_subclass_of($class, ServiceProvider::class)) {
$this->app->register($class);
}
}
}
}
I tried to put setlocale(LC_TIME, 'tr'); on top of the class file but there is no success. Then tried to use carbon in order to make the date is viewed in different languages when I change the website language.
I added the following codes in app/config.php :
'locale' => env('APP_LOCALE', 'az'),
'lc_all' => env('APP_LC_ALL', 'az_AZ.UTF-8'),
and added following to the env file :
APP_LOCALE = az
APP_LC_ALL = az_AZ.UTF-8
in both methods, I was not successful. I am pretty sure that I am doing a mistake somewhere but can not find where exactly. Maybe I am missing to add something else to add. Any help would be highly appreciated.
EDIT : Adding Chat.php :
<?php
namespace App\Models;
use App\Events\ChatParticipationChanged;
use App\Events\ChatUpdated;
use App\Http\Resources\ChatMessage as ChatMessageResource;
use App\Http\Resources\MarketplaceTrade as MarketplaceTradeResource;
use ArrayObject;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Str;
use JSsVPSDioNXpfRC;
use DateTimeInterface;
class Chat extends Model
{
protected $lastMessageAttribute;
protected $lastMarketplaceTradeAttribute;
/**
* The attributes that aren't mass assignable.
*
* #var array
*/
protected $guarded = [];
/**
* The event map for the model.
*
* #var array
*/
protected $dispatchesEvents = [
'updated' => ChatUpdated::class
];
/**
* Indicates if the IDs are auto-incrementing.
*
* #var bool
*/
public $incrementing = false;
/**
* Get the route key for the model.
*
* #return string
*/
protected function serializeDate(DateTimeInterface $date)
{
return $date->translatedFormat('A B M');
}
public function getRouteKeyName()
{
return 'id';
}
/**
* #return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function creator()
{
return $this->belongsTo(User::class, 'creator_id', 'id');
}
/**
* Participants for this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function participants()
{
return $this->hasMany(ChatParticipant::class, 'chat_id', 'id');
}
/**
* Messages for this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function messages()
{
return $this->hasMany(ChatMessage::class, 'chat_id', 'id');
}
/**
* Update user's participation record
*
* #param User $user
*/
public function updateParticipation($user)
{
$this->participants()->where('user_id', $user->id)
->update(['last_read_at' => now()]);
broadcast(new ChatParticipationChanged($this, $user));
}
/**
* All marketplace trades hosted by this chat
*
* #return \Illuminate\Database\Eloquent\Relations\HasMany
*/
public function marketplaceTrades()
{
return $this->hasMany(MarketplaceTrade::class, 'chat_id', 'id')
->has('buyer')->has('seller');
}
/**
* #return Model|\Illuminate\Database\Eloquent\Relations\HasMany|mixed|object|null
*/
public function getLatestMarketplaceTrade()
{
if (!isset($this->lastMarketplaceTradeAttribute)) {
$trade = $this->marketplaceTrades()->latest()->first();
$this->lastMarketplaceTradeAttribute = new MarketplaceTradeResource($trade);
}
return $this->lastMarketplaceTradeAttribute;
}
/**
* Last chat message
*
* #return ChatMessageResource|ArrayObject|mixed
*/
public function getLatestMessage()
{
if (!isset($this->lastMessageAttribute)) {
$message = $this->messages()->latest()->first();
if ($message) {
$this->lastMessageAttribute = new ChatMessageResource($message);
} else {
$this->lastMessageAttribute = new ArrayObject();
}
}
return $this->lastMessageAttribute;
}
/**
* #param User $user
* #return array
*/
public function getParticipation($user)
{
$participant = $this->participants()
->where('user_id', $user->id)->without('user')
->first();
$unreadMessagesCount = ($participant && $participant->last_read_at) ?
$this->messages()->where('user_id', '!=', $user->id)
->where('created_at', '>', $participant->last_read_at)
->count() :
$this->messages()->where('user_id', '!=', $user->id)
->count();
return [
'user_id' => $user->id,
'unread_messages_count' => $unreadMessagesCount
];
}
/**
* If user should be allowed in this chat
*
* #param User $user
* #return bool
*/
public function shouldAllowUser($user)
{
$isParticipant = $this->participants()
->where('user_id', $user->id)->exists();
return (
$isParticipant ||
$user->can('moderate_chats')
);
}
/**
* #return string
*/
public function attachmentsDir()
{
return "chats/{$this->id}/message-attachments";
}
}
The problem is on your namespace :
// Using PHP callable syntax
use Carbon\Carbon;
Or,
// Using string syntax
\Carbon\Carbon::setLocale('ru');
You also need to use translatedFormat() method on your blade for use the translate format, like :
{{ Carbon\Carbon::now()->translatedFormat('A B M') }} // утра 428 фев
You can use serializeDate() method on your model, to change timestamp column as a translated dataTime format :
use DateTimeInterface;
protected function serializeDate(DateTimeInterface $date)
{
return $date->translatedFormat('A B M');
}

Laravel 7.x Observer not saving, created_by updated_by user

I have a Model named "Resource".
by using this command
php artisan make:observer ResourceObserver --model=Resource
this command create a new file, i update created, updated functions and update constructor
<?php
namespace App\Observers;
use App\Resource;
class ResourceObserver
{
protected $userID;
public function __construct()
{
$this->userID = auth()->user()->id;
}
/**
* Handle the resource "created" event.
*
* #param \App\Resource $resource
* #return void
*/
public function created(Resource $resource)
{
$resource->created_by = $this->userID;
}
/**
* Handle the resource "updated" event.
*
* #param \App\Resource $resource
* #return void
*/
public function updated(Resource $resource)
{
$resource->updated_by = $this->userID;
}
/**
* Handle the resource "deleted" event.
*
* #param \App\Resource $resource
* #return void
*/
public function deleted(Resource $resource)
{
//
}
/**
* Handle the resource "restored" event.
*
* #param \App\Resource $resource
* #return void
*/
public function restored(Resource $resource)
{
//
}
/**
* Handle the resource "force deleted" event.
*
* #param \App\Resource $resource
* #return void
*/
public function forceDeleted(Resource $resource)
{
//
}
}
this is my migration:
public function up()
{
Schema::create('resources', function (Blueprint $table) {
$table->id();
// some fields here
$table->foreignId('created_by')->nullable()->default(null)->constrained('users')->onDelete('set null');
$table->foreignId('updated_by')->nullable()->default(null)->constrained('users')->onDelete('set null');
$table->timestamps();
});
}
then you should register the observer in AppServiceProvider like this:
use App\Observers\ResourceObserver;
use App\Resource;
public function boot()
{
Schema::defaultStringLength(191);
Resource::observe(ResourceObserver::class);
}
Now the problem appears when update any record it is not save the user_id
to update i use update function in ResourceController
public function update(Request $request, Resource $resource)
{
$validations = [
// some validations
];
$request->validate($validations);
try {
if (!empty($resource)) {
$resource->field_a = $request->field_a;
$resource->field_b = $request->field_b;
$resource->field_c = $request->field_c;
$resource->save();
return 'done messge';
} else {
return 'error message';
}
} catch (\Exception $e) {
return 'bug message';
}
}
Any help please?!
When issuing a mass update or delete via Eloquent, the saved, updated, deleting, and deleted model events will not be fired for the affected models. This is because the models are never actually retrieved when issuing a mass update or delete.
So, In ResourceObserver i just changed from method from updated to updating,
and created to creating

Eloquent event deleting listener return false will stop delete function? [laravel]

I have Team eloquent and TeamObserver. TeamObserver has deleting event and in the event i call TeamDeletingEvent. TeamDeletingEvent dispatch TeamDeletingListener. If TeamDeletingLister will return false will stop the team delete function?
TeamObserver
class TeamObserver
{
/**
* Handle the team "deleting" event.
*
* #param Team $team
* #return void
*/
public function deleting(Team $team)
{
event(new TeamDeletingEvent($team));
}
}
TeamDeletingEvent
class TeamDeletingEvent
{
use SerializesModels;
/**
* #var Team
*/
public $team;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct(Team $team)
{
$this->team = $team;
}
}
TeamDeletingListener
class TeamDeletingListener
{
/**
* Handle the event.
*
* #param TeamDeletingEvent $event
* #return bool
*/
public function handle(TeamDeletingEvent $event)
{
$teamUser = Team::where('id', $event->team->id)->users()->first();
if(is_null($teamUser)){
return true;
}
return false;
}
}
More than this TeamObserver registered in AppServiceProvider and Event and Listener registered in EventServiceProvider
listener return false will not stop the delete function. It will stop the continuous listener call of the event. But Eloquent event returning false will stop the delete function. (model instance values gets updated but these are not updated in database). More info Detail Answer

Doctrine Event Listener for adding/removing Many to Many relations

I make heavy use of Entity Listeners for logging purposes, generally works really well and keeps all the code out of the controllers/services.
One thing I haven't been able to achieve is logging of items added to a ManyToMany relation. In this instance I want to log when a size is added/removed from a product
/**
* #ORM\Entity
* #ORM\EntityListeners({"EventListener\ProductListener"})
* #ORM\Table(name="products")
*/
class Product
{
// ...
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="Size")
* #ORM\JoinTable(name="productSizes",
* joinColumns={#ORM\JoinColumn(name="productId", referencedColumnName="productId")},
* inverseJoinColumns={#ORM\JoinColumn(name="sizeId", referencedColumnName="sizeId")}
* )
*/
protected $sizes;
/**
* #param Size $size
* #return Product
*/
public function addSize(Size $size)
{
$this->sizes[] = $size;
return $this;
}
/**
* #param Size $size
*/
public function removeSize(Size $size)
{
$this->sizes->removeElement($size);
}
/**
* #return ArrayCollection
*/
public function getSizes()
{
return $this->sizes;
}
// ...
}
Then inside the entity listener
class ProductListener
{
// ...
/**
* #ORM\PostPersist
*/
public function postPersistHandler(Product $product, LifecycleEventArgs $args)
{
$this->getLogger()->info("Created product {$product->getSku()}", [
'productId' => $product->getId()
]);
}
/**
* #ORM\PostUpdate
*/
public function postUpdateHandler(Product $product, LifecycleEventArgs $args)
{
$context = $args->getEntityManager()->getUnitOfWork()->getEntityChangeSet($product);
$context['productId'] = $product->getId();
$this->getLogger()->info("Updated product", $context);
}
// ...
}
So how can I get the colours added/removed from the unit of work? I'm assuming this is available somewhere but I can't find it.
In your ProductListener
$product->getSizes() returns instance of Doctrine\ORMPersistentCollection. Then you can call 2 methods:
- getDeleteDiff - returns removed items
- getInsertDiff - returns added items

Resources