Is there a force deleting event? - Laravel 5.2 - php

When I throw records into the "trashbin" I use the normal delete method with softDelete enabled.
When I force delete a record I want to delete the belonging images as well. So I want to use Laravel's events. On forceDeleting I want some code to be executed.
What event can I call for that? When calling forceDeleting I get:
Call to undefined method Illuminate\Database\Query\Builder::forceDeleting()
What event should I use for this?
EDIT
For now I'm using:
Document::deleting(function ($document) {
if(!$document->deleted_at) {
// normal delete
}else{
// force delete
}
});
See also: https://codeneverlied.com/force-deleting-event-in-laravel/
But I still like to know if there is a event for this.

You can create your own event and fire it yourself. Would be easiest to extend the existing Delete Event and maybe call it Force Deleting.
The next step would be to extend the softDeleting trait to fire this event for you automatically.

According to the Eloquent documentation, these are the events:
retrieved, creating, created, updating, updated, saving, saved, deleting, deleted, restoring, and restored.
So to answer your question of if it exists, that answer would be no. Not according to the documentation of Laravel 5.8
https://laravel.com/docs/5.8/eloquent
Hope I could help.
ImJT

Better approach is this:
// GOOD approach
static::deleted(function ($document) {
if (Document::withTrashed()->where('id', $model->id)->exists()) {
// document is soft deleted
} else {
// document is force deleted
}
});

Related

Test if Eloquent Observer`s method fires an event

The app I am working on fires an event, when one of the Eloquent model attributes is updated.
The Eloquent model is called Job and it is supposed to fire JobCustomerTotalAmountDueChanged when the duration attribute is updated. I have the following code in the JobObserver:
public function saved(Job $job)
{
if ($job->isDirty('duration')) {
event(new JobCustomerTotalAmountDueChanged($job));
}
}
When I try to test it using Event::fake, Eloquent events are not being fired, which means that the code in saved method is never execured. From what I see the assertDispatched and assertNotDispatched methods are only available for faked events. Is there a way to assert that a particular event is/is not fired without Event::fake?
The solution turned out to be very easy after all:
Laravel lets you specify which events should be faked, as an argument to fake method. In my example:
Event::fake([JobCustomerTotalAmountDueChanged::class])
All of the other events are being triggered and handled. Also, you can make assertions only with reference to events passed as argument to fake method. If you don't pass any argument, Laravel will try to 'fake' every event in the app.

Laravel - Monitoring Record Change

I need an Eloquent model to represent a document uploaded to the server. One such field is the absolute path to the file. I want to be able to move the file whenever the database record is updated. I've seen that Laravel includes Observers to handle such events. The problem is:
Observers classes have method names which reflect the Eloquent events you wish to listen for. Each of these methods receives the model as their only argument.
This means that, unfortunately, with the given system I'm not able to actually move the file, since I will not have a way of retrieving both the current and new locations of the file in question. Is there another way of detecting when the value changes while having access to old and new values?
It sounds like you may be able to use the updating model event for this. You can register the event in the boot method of a service provider (such as app/Providers/AppServiceProvider.php) or by creating an observer class as you mentioned.
The isDirty and getOriginal methods should help you check if the file needs to be moved, and then get it's original path.
For example:
use Storage;
use App\Document;
public function boot()
{
Document::updating(function ($document) {
if ($document->isDirty('file_path')) {
// File needs to be moved
$current_path = $document->getOriginal('file_path');
$new_path = $document->file_path;
Storage::move($current_path, $new_path);
}
});
}
See also: Laravel updating eloquent event: getting the data

Laravel - last login date and time timestamp

I know there are fields automatically inserted (e.g. updated_at and created_at) but I was wondering if there is like an Eloquent method timestamp() or the Laravel way to produce timestamps?
For instance, I want to record a timestamp every time a user logs in to my system and store it into the database, so we can see each user's last login date and time details.
In Laravel 5.2+ (tested on 5.6 as well) the process is a little different. First, add your listener to the EventServiceProvider:
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
];
Then do:
php artisan event:generate
Which will create a new listener in app/Listeners. Now edit your Listener to touch the date in the last_login column in your users table:
public function handle(Login $event)
{
$event->user->last_login = date('Y-m-d H:i:s');
$event->user->save();
}
Make sure the Listener created by Artisan has the correct namespaces at the top. For this particular case the correct namespaces in your Listener should be:
use Illuminate\Auth\Events\Login;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldQueue;
Just add this code below into your App\Http\Controllers\Auth\LoginController.php
At the top of your controller:
use Carbon\Carbon;
use Illuminate\Http\Request;
And inside your controller put this function below:
public function authenticated(Request $request, $user) {
$user->last_login = Carbon::now()->toDateTimeString();
$user->save();
}
You may observe the auth.login event and update the user's last login time. See the first example in the documentation for events to accomplish exactly what you're trying to do.
Event::listen('auth.login', function($user) {
$user->last_login = new DateTime;
$user->save();
});
Note: In 4.0, the event name is user.login. In 4.1, the event name is auth.login
Update
It's really up to you where you put your Event listeners. If you read the linked to doc page, it states:
So, you know how to register events, but you may be wondering where to
register them. Don't worry, this is a common question. Unfortunately,
it's a hard question to answer because you can register an event
almost anywhere! But, here are some tips. Again, like most other
bootstrapping code, you may register events in one of your start files
such as app/start/global.php.
If your start files are getting too crowded, you could create a
separate app/events.php file that is included from a start file. This
is a simple solution that keeps your event registration cleanly
separated from the rest of your bootstrapping. If you prefer a class
based approach, you may register your events in a service provider.
Since none of these approaches is inherently "correct", choose an
approach you feel comfortable with based on the size of your
application.
My personal preference would be to register them within a Service Provider, since that would keep them segregated and to me is the more 'Laravel' way of doing things. If you need help with service providers, see this documentation entry.
However, if you really just want to get something up and running as quickly as possible, it would be just as easy for you to register that listener inside of app/start/global.php
I had a similar question. In case you don't feel the need the use events you can update the timestamp after you authenticate the user. I put the following code in my controller method that handles the user authentication.
if (Auth::attempt(array('email'=>$email, 'password'=>$password))) {
Auth::user()->last_login = new DateTime();
Auth::user()->save();
return Redirect::intended('/');
} else {
return Redirect::to('account/login');
}
This does the job for me.

Working with related model in CakePHP

This is a fairly basic question about CakePHP, but since my knowledge of this framework is rather rusty, it is making me lose a lot of time.
I have a ManyToMany relation between Guest and Present. Whenever a new Guest is created and associated with a present, I would like to mark the Present as taken. If the present is already taken, some error should arise. The reason why I am not just declaring that a Guest hasMany Presents is because in the future things may change and more than one guest could associate to a present, so I prefer to avoid a Db migration.
My Guest::add() action looks like follows. It is called with a POST with the data of a new Guest and the id of an existing Present.
public function add() {
if ($this->request->is('post')) {
$id = $this->request->data['Present']['id'];
$this->Guest->create();
$present = $this->Guest->Present->findById($id);
if ($present['Present']['taken']) {
throw new ForbiddenException();
}
if ($this->Guest->save($this->request->data)) {
if ($this->Guest->Present->saveField('taken', true)) {
// Give the guest a uuid and proceed with a welcome message
$this->Guest->read();
$this->set('uuid', $this->Guest->data['Guest']['uuid']);
}
}
}
else {
throw new ForbiddenException();
}
}
What happens is that a new Guest is created (correct) and associated with the given present (correct) but when I save the taken field a new present is created instead of modifying the given one.
What is the correct way to proceed to update the current Present?
If it is of any help, I am using CakePHP 2.0
For obtaining the model data by the primary key it's better to use theIn addition read method:
$present = $this->Guest->Present->read(null, $id);
The read method sets the model's id attribute so that further calls to other methods affect the same data record, rather than creating a new one. This should solve the problem you are having.
Model callbacks tend to be better suited for these situations. You could add a beforeSave callback to the Guest class to checks if the present is already taken, and not allow the creation if it is. This way the model logic is left in the model layer and you don't need to do any extra work e.g. if the constraint has to be enforced also when existing Guests are saved, or created from different controllers or actions.
It sounds like the ID of the model you are trying to save is losing scope. You should be able to resolve your issue by updating your code:
...
if ($this->Guest->save($this->request->data)) {
$this->Guest->Present->id = $id;
if ($this->Guest->Present->saveField('taken', true)) {
...

How do i detach a behavior in Symfony/Doctrine?

I have doctrine's softdelete behavior attached to all of my models. Is there a way I can hard delete a particular record?
In cakephp I remember detaching the behavior... deleting the record and then re attaching the behavior.
Is there something similar in symfony/doctrine ? If so then how do I detach a behavior?
Cheers
umm .. the SoftDelete behavior includes a much nicer way of doing this ... just call
$record->hardDelete();
Think I'd go for Zed's way, but for completeness:
The Event listener method for delete (and select) for the soft delete behaviour contains:
if ( ! $query->contains($field)) {
// do the magic stuff to covert the query to respect softdelete
}
This means that if you explicitly mention the field in the query, it won't apply the transformation to the query.
So, if you do:
$q = Doctrine_Query::create()
->delete('Table t')
->where('t.id = ? AND t.deleted != 2 ', 1);
it won't apply the soft delete stuff and will actually delete the record. Note that you can do anything with t.deleted, I've just done something that will always be true. The alias ('t.') is important too for it to work.
This trick works for selects too, which is where I've normally used it before.
As I say though, I think its nicer to do:
$old_dqlc = Doctrine_Manager::getInstance()->getAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS);
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, false);
$record->delete();
Doctrine_Manager::getInstance()->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, $old_dqlc);
In particular, you can still use the delete() method rather than having to manually create the query. The one plus for the query method is that if you have other behaviours attached to the record, they will still be respected.
$object->getListener()->setOption('disabled',true);
This will disable all record listeners for this object.
Try calling this, it should disable the behavior handling.
$manager->setAttribute(Doctrine::ATTR_USE_DQL_CALLBACKS, false);
As a dirty way you can generate an SQL query that deletes the entry from the table.
link text i would think that this function and setting the use dql callbacks to false just like on the manager should do the trick :).
Wanted to agree with Joshua Coady that the best way would be to use
$record->hardDelete()
However, I also wanted to add here since it's one of the first results on google for detaching the behavior in doctrine that the easiest way to detach the behavior for "selects" is simply to include "deleted_at" (or whatever you have named your field as in the query. The listener looks to see if it is included and if so does not filter deleted records out.
Doctrine_Core::getTable('Record')->createQuery()->select('id, etc1, etc2')->addSelect('deleted_at')->execute();
will return deleted records.

Categories