Laravel / Eloquent: how to hook into the deletion process? - php

When a record gets deleted from my_items_table I want to insert the record into my_items_table_archive.
I could do this on each Controller, but would prefer to hook into the Eloquent model.
Is there anything like this?
Pseudocode:
class MyItem extends Model {
protected function beforeDelete($record) {
MyItemArchive::create($record); // add record the archive
return true; // continue deletion of $record
}
}
Any idea? Thanks!

Yes, there is something similar to your pseudocode.
You can utilise Eloquent Events
A good example of this can be seen below:
protected $dispatchesEvents = [
'deleted' => UserDeleted::class,
'deleting' => UserDeleting::class
];
The class in question just needs to adhere to / Follow: Listeners
You can also use Eloquent Observers / the observer pattern to achieve a similar result.
Let me know how you get on!

First of all create a new Observer using
php artisan make:observer MyItemObserver
Then
<?php
namespace App\Observers;
class MyItemObserver
{
public function deleting(MyItem $myItem)
{
/// insert new record here
}
}
Now you in your appServiceProvider
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
MyItem::observe(MyItemObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
Now your obverserver will be hooked to Model Events.
Hope this helps.

As described in the official documentation you have two choices using the events. The first one is creating an observer like this:
class MyModelObserver
{
/**
* Listen to the Model deleting event.
*
* #param User $user
* #return void
*/
public function deleting(User $user)
{
// HERE YOUR CODE TO TRANSFER THE MODEL
}
}
Than you have to register it on your AppServiceProvider
public function boot {
MyModel::observe(MyModelObserver::class)
}
Otherwise you can add these events in your model by generating the specific class:
protected $dispatchesEvents = [
'deleting' => MyModelDeletingEvent::class,
];
Anyway if you're using a version of laravel lower than 5.4 you should check the documentation for the specific implementation, since the $dispatchesEvents is not available as variable.

Related

Laravel container returning multiple Singleton instances

I've created a CustomProvider, added it to the app.php array of providers and registered a class as singleton:
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\ReserveCart;
class CustomProvider extends ServiceProvider
{
/**
* Bootstrap services.
*
* #return void
*/
public function boot()
{
//
}
/**
* Register services.
*
* #return void
*/
public function register()
{
$this->app->singleton('App\ReserveCart', function($app){
return new ReserveCart;
});
}
}
but everytime I request for the object with $rc = resolve('App\ReserveCart'); it keeps giving me different instances of the object instead of a single one (I've done some echo tracking).
Also tried passing the dependency to methods acording to Laravel Documentation. e.g
public function foo(App\ReserveCart $rc){
//
}
but the issue persists.
Is the output below same ?
$rc = resolve('App\ReserveCart');
$rc1 = resolve('App\ReserveCart');
dd(spl_object_hash($rc), spl_object_hash($rc1));

Automatically Change Model Value If Another Value Updates

With Laravel & Eloquent, if a column called status changes its value to "complete," for example, is it possible to automatically change the value of another column (issue_id) to NULL?
I was wondering about the set attribute or intercepting the save() method, but not sure which is best.
You could make use of Observers.
For example, to observe the Issue model, you could generate an Observer as such:
php artisan make:observer IssueObserver --model=Issue
This will produce an observer where you could listen to many model events.
<?php
namespace App\Observers;
use App\Issue;
class IssueObserver
{
/**
* Handle the Issue "updating" event.
*
* #param \App\Issue $Issue
* #return void
*/
public function updating(Issue $issue)
{
if($issue->status == 'complete') {
$issue->issue_id = null;
}
}
}
To register the Observer, you would need to add this to AppServiceProvider#boot()
<?php
namespace App\Providers;
use App\Issue;
use App\Observers\IssueObserver;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
Issue::observe(IssueObserver::class);
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
}
}
You could also just do this in your App/Issue model.
public static function boot()
{
parent::boot();
static::updating(function ($issue) {
if($issue->status == 'complete') {
$issue->issue_id = null;
}
})
}
Obviously, you would need to listen on the events that suit your needs. This is just an example. You could take a look at all the available model events here.

Laravel View Composer duplicating SQL queries for every view

I need to access some data (User details) in most views. What I have done:
I created ComposerServiceProvider
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class ComposerServiceProvider extends ServiceProvider
{
/**
* Bootstrap the application services.
*
* #return void
*/
public function boot()
{
view()->composer(
['includes.header','profile'],
'App\Http\ViewComposers\CustomerComposer'
);
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
//
}
}
Created CustomerComposer class
<?php
namespace App\Http\ViewComposers;
use Illuminate\Support\Facades\Auth;
use Illuminate\View\View;
use Modules\Customers\Entities\CustomerDetail;
class CustomerComposer
{
public $customer = [];
/**
* Bind data to the view.
*
* #param View $view
* #return void
*/
public function compose(View $view)
{
$user = Auth::guard('customer');
$this->customer = CustomerDetail::where('user_id',$user->id())->first();
$view->with( 'customer', $this->customer );
}
}
Everything works but when I look at Debug bar it shows me same queries excecuted per view, so for example if I define ['includes.header','profile'] Same SQL will be excecuted twice if ['includes.header','profile','something_else'] 3 times and so on...
In this case query's is
select * from `customer_details` where `user_id` = '1' limit 1
select * from `customer_details` where `user_id` = '1' limit 1
If I provide wildcard in
view()->composer(
['*'],
'App\Http\ViewComposers\CustomerComposer'
);
It will generate 23 queries! I missed something here?
Ok I think I found solution. In ComposerServiceProvider class:
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->singleton(\App\Http\ViewComposers\CustomerComposer::class);
}
That it.
In Laravel Docs
Registering A Singleton
Sometimes, you may wish to bind something into the container that
should only be resolved once, and the same instance should be returned
on subsequent calls into the container:
Per the manual at https://laravel.com/docs/5.5/views#view-composers:
"View composers are callbacks or class methods that are called when a view is rendered. If you have data that you want to be bound to a view each time that view is rendered, a view composer can help you organize that logic into a single location."
(emphasis mine)
In this case:
view()->composer(
['includes.header','profile'],
'App\Http\ViewComposers\CustomerComposer'
);
you're attaching the includes.header view and the profile view, which I guess includes the includes.header view. So, since the composer is executed when the view is rendered, it'll execute twice, one when rendering of the profile view and again another when rendering the includes.header view.
You can use config here to resolve multiple times query run issue for view compose. For example show below code.
public function compose(View $view)
{
if(!Config::has('composeVars'))
{
Config::set('composeVars') = [
'users' => User::all();
];
}
$view->with('*', Config::get('composeVars'));
}

Get current route inside service provider in Laravel

First of all, I have to tell that I am totally new on Laravel, so if you thing I am in the wrong way, please correct me.
Notice, that I have the Laravel 5.1 installed on my application.
I creating an application and I like to have my breadcrumbs inside an array, so I decide to create a Service Provider, and the code for the Service Provider is the following:
<?php
namespace App\Providers;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\ServiceProvider;
class BreadCrumbsServiceProvider extends ServiceProvider
{
// This will contain all the breadcrumb crumbs
protected $crumbs = [];
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
$this->createBreadcrumb();
}
/**
* Register the application services.
*
* #return void
*/
public function register()
{
$this->app->singleton(
'crumbs',
function() {
return $this->crumbs;
}
);
}
protected function createBreadcrumb( ) {
dd( Route::current() );
}
}
So ,when I run my site, the dd returns just null. Can someone help me with this situation ?
Inside your service provider class Route::Class won't work. Try these for
current request uri - $this->app->request->getRequestUri()
Current request method - $this->app->request->getMethod()

login event handling in laravel 5

i am trying to hook to the login even in my L5 app to set last login time and IP address. i can make it work with the following:
Event::listen('auth.login', function($event)
{
Auth::user()->last_login = new DateTime;
Auth::user()->last_login_ip = Request::getClientIp();
Auth::user()->save();
});
however, i am wondering what the best way to do this in L5 is with the event handler object. i tried creating an event handler and adding auth.login as an array key in the events service provider, however that didnt work. im not sure if that is possible or not with the auth.login event. if it isnt, where is the most appropriate place to put the above code. for testing, i put it in my routes.php file, but i know that isnt where it should be.
In laravel 5.2; auth.login won't work... the following will have to be used:
protected $listen = [
'Illuminate\Auth\Events\Attempting' => [
'App\Listeners\LogAuthenticationAttempt',
],
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LogSuccessfulLogin',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogSuccessfulLogout',
],
'Illuminate\Auth\Events\Lockout' => [
'App\Listeners\LogLockout',
],
];
As stated in the documentation here
EDIT: this only works in 5.0.* and 5.1.*.
For the 5.2.* solution see JuLiAnc response below.
after working with both proposed answers, and some more research i finally figured out how to do this the way i was trying at first.
i ran the following artisan command
$ php artisan handler:event AuthLoginEventHandler
Then i altered the generated class removing the import of the Event class and and imported the user model. I also passed User $user and $remember to the handle method since when the auth.login event is fired, thats what is passed.
<?php namespace App\Handlers\Events;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
use App\User;
class AuthLoginEventHandler {
/**
* Create the event handler.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param User $user
* #param $remember
* #return void
*/
public function handle(User $user, $remember)
{
dd("login fired and handled by class with User instance and remember variable");
}
}
now i opened EventServiceProvided.php and modified the $listen array as follows:
protected $listen = [
'auth.login' => [
'App\Handlers\Events\AuthLoginEventHandler',
],
];
i realized if this doesn't work at first, you may need to
$ php artisan clear-compiled
There we go! we can now respond to the user logging in via the auth.login event using an event handler class!
Be careful about asking what the best way to do X is, because Laravel, in particular, provides many ways of accomplishing the same task -- some are better than others in certain situations.
Taking a look at the Laravel documentation, personally I would go with the "Basic Usage" as it seems to match the use case you have stated.
If we run the following Artisan command we can generate a template for the UserLoggedIn event.
$ php artisan make:event UserLoggedIn
(note the past tense, because events happen, and then the subscribers are notified of the event having taken place)
(note 2: the app string in namespaces is what Laravel uses out of the box, it is likely different for you if you have executed the php artisan app:name command)
The following class is generated for us:
<?php namespace app\Events;
use app\Events\Event;
use Illuminate\Queue\SerializesModels;
class UserLoggedIn extends Event {
use SerializesModels;
/**
* Create a new event instance.
*
* #return void
*/
public function __construct()
{
//
}
}
If we add a userId parameter to the constructor, then the event doesn't need to know about the Auth Facade/Guard Contract. This means our UserLoggedIn event code is not tightly coupled to Eloquent or which ever authentication framework you decide to utilize in your app. Anyways, let's add that userId parameter.
<?php namespace app\Events;
use app\Events\Event;
use app\User;
use Illuminate\Queue\SerializesModels;
class UserLoggedIn extends Event {
use SerializesModels;
public $userId;
/**
* Create a new event instance.
*
* #param int userId the primary key of the user who was just authenticated.
*
* #return void
*/
public function __construct($userId)
{
$this->userId = $userId;
}
}
Now you're probably wondering, well that's great and all, but how to we act on this event? Great question! We need to create an event handler to handle when this event is fired. Let's do that now using Artisan:
$ php artisan handler:event UpdateUserMetaData --event=UserLoggedIn
We name our new event handler UpdateUserMetaData and tell Artisan that the event we want to handle is the UserLoggedIn event.
Now we have some code that looks like this inside of app/Handlers/Events/UpdateUserMetaData.php:
<?php namespace app\Handlers\Events;
use app\Events\UserLoggedIn;
use Illuminate\Queue\InteractsWithQueue;
use Illuminate\Contracts\Queue\ShouldBeQueued;
class UpdateUserMetaData {
/**
* Create the event handler.
*
* #return void
*/
public function __construct()
{
//
}
/**
* Handle the event.
*
* #param UserLoggedIn $event
* #return void
*/
public function handle(UserLoggedIn $event)
{
//
}
}
We can update the handle method to be able to handle this event like you specified above quite easily:
<?php namespace app\Handlers\Events;
use app\Events\UserLoggedIn;
use Illuminate\Http\Request;
class UpdateUserMetaData {
protected $request;
/**
* Create the event handler.
*
* #param Request $request
*/
public function __construct(Request $request)
{
$this->request = $request;
}
/**
* Handle the event.
*
* #param UserLoggedIn $event
*/
public function handle(UserLoggedIn $event)
{
$user = User::find($event->userId); // find the user associated with this event
$user->last_login = new DateTime;
$user->last_login_ip = $this->request->getClientIp();
$user->save();
}
}
As a side note, if you're not familiar with Carbon, you might want to look into using it so you can take advantage of its fantastic API like you can with Eloquent's created_at and updated_at timestamp fields on most models. Here's a link for how to tell Eloquent which fields should be used with Carbon: http://laravel.com/docs/master/eloquent#date-mutators.
There are two final steps we have to perform before this code will work in your Laravel app.
We need to map the event to the event handler in the EventServiceProvider class under the app/Providers directory.
We need to fire the event after login.
To complete the first step, we just need to add our event classes to the $listeners property in app/Providers/EventServiceProvder.php like so:
UserLoggedIn::class => [
UpdateUserMetaData::class
]
The above will work provided you import the classes inside the EventServiceProvider class, and you are using PHP 5.5. If you're using a lower PHP version, you'll need to provide the full path to each class as a string like this: 'app/Events/UserLoggedIn' and 'app/Handlers/Events/UpdateUserMetaData'.
The $listeners array maps events to their respective handlers.
Okay, now for the final step! In your code base, find the place where the user is authenticated and add the following:
event(new \app\Events\UserLoggedIn(Auth::user()->id));
And we're done! I tested this code as I wrote this answer, feel free to ask follow up questions if you have any.
For 5.2 something like this
in Listeners:
use Carbon\Carbon;
use Illuminate\Auth\Events\Login;
class UpdateLastLoginWithIp
{
public function handle(Login $event)
{
$event->user->last_login_at = Carbon::now();
$event->user->last_login_ip = Request::getClientIp()
$event->user->save();
}
}
In EventServiceProvider.php :
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'City\Listeners\UpdateLastLoginWithIp',
],
];
Usually you can achieve by doing like this step by step for User Login Logs
first, you should have Auth Scaffolding
use this as event,
'Illuminate\Auth\Events\Login' for Login Event
'Illuminate\Auth\Events\Logout' for Logout Event
located the login and logout event at :
vendor\laravel\framework\src\Illuminate\Auth\Events
EventServiceProvider.php
protected $listen = [
'Illuminate\Auth\Events\Login' => [
'App\Listeners\LoginLogs',
],
'Illuminate\Auth\Events\Logout' => [
'App\Listeners\LogoutLogs',
],
];
public function boot()
{
parent::boot();
}
then after you get done for EventServiceProvider, do this next step
type this artisan command php artisan event:generate
look for folder Listener inside App folder, check if contains php files both LoginLogs and LogoutLogs
create your migration and model
command: php artisan make:migration create_UserLoginHistory
Migration File
public function up()
{
Schema::create('tbl_user_login_history', function (Blueprint $table) {
$table->bigIncrements('id');
$table->integer('user_id');
$table->datetime('login_at')->nullable();
$table->datetime('logout_at')->nullable();
$table->string('login_ip')->nullable();
$table->string('role');
$table->string('session_id');
$table->timestamps();
});
}
public function down()
{
Schema::dropIfExists('tbl_user_login_history');
}
then your Model : UserLoginHistory
public $timestamps = false;
protected $table = 'tbl_user_login_history';
protected $fillable = ['user_id','login_at','logout_at','login_ip','role','session_id'];
public function setLogOutLog(){
$this->where('session_id',request()->session()->getId())->update([
'logout_at' =>Carbon::now(),
]);
}
public function setLogInLog(){
$this->insert(
['user_id' => Auth::user()->id,'login_at' =>Carbon::now(),
'login_ip'=>request()->getClientIp(),'role' =>Auth::user()->role,
'session_id'=>request()->session()->getId()
]);
}
4.after the migration and model creation procedure, let's assume that you have already in roles in users table
the listener part
Listener : LoginLogs Class
use App\UserLoginHistory;
private $UserLoginHistory;
public function __construct(UserLoginHistory $UserLoginHistory)
{
// the initialization of private $UserLoginHistory;
$this->UserLoginHistory = $UserLoginHistory;
}
public function handle(Login $event)
{
// from model UserLoginHistory
$this->UserLoginHistory->setLogInLog();
}
Listener : LogoutLogs Class
private $UserLogoutHistory;
public function __construct(UserLoginHistory $UserLoginHistory)
{
// the initialization of private $UserLogoutHistory;
$this->UserLogoutHistory = $UserLoginHistory;
}
public function handle(Logout $event)
{
// from model UserLoginHistory
$this->UserLogoutHistory->setLogOutLog();
}
after you do this all steps , try to login with Auth
here is my approach:
I have done for making an event handler when user logged in using:
Laravel 5.8
1) Run the following artian command
php artisan make:listener Auth/UserLoggedIn --event='Illuminate\Auth\Events\Login'*
It will make a Listener: UserLoggedIn in folder app\Listeners\Auth\
2) Then you need to add this listener into your EventServiceProvider:**
...
protected $listen = [
...
'Illuminate\Auth\Events\Login' => [
'App\Listeners\Auth\UserLoggedIn',
],
];
Finaly you can do log when user logged in in handle function located at UserLoggedIn Listener:
public function handle(Login $event)
{
//you have access to user object by using : $event->user
}
you can use all other Auth events, here is the possible events:
'Illuminate\Auth\Events\Registered',
'Illuminate\Auth\Events\Attempting',
'Illuminate\Auth\Events\Authenticated',
'Illuminate\Auth\Events\Login',
'Illuminate\Auth\Events\Failed',
'Illuminate\Auth\Events\Logout',
'Illuminate\Auth\Events\Lockout',
**You can use all these events in your EventServiceProvider:
https://laravel.com/docs/5.8/authentication#events
Open up EventServiceProvider.php and in boot method you can listen for 'auth.login' event via callback.
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('auth.login', function()
{
dd('logged in event');
});
}
You may want to create listener so you move callback function somewhere else. Do that following this http://laravel.com/docs/4.2/events#using-classes-as-listeners
just did it this way
<?php
namespace App\Providers;
use App\User;
use Auth;
use DB;
use Illuminate\Contracts\Events\Dispatcher as DispatcherContract;
use Illuminate\Foundation\Support\Providers\EventServiceProvider as ServiceProvider;
class EventServiceProvider extends ServiceProvider
{
/**
* The event listener mappings for the application.
*
* #var array
*/
protected $listen = [
];
/**
* Register any other events for your application.
*
* #param \Illuminate\Contracts\Events\Dispatcher $events
* #return void
*/
public function boot(DispatcherContract $events)
{
parent::boot($events);
$events->listen('auth.login', function()
{
DB::table('users')
-> where('id', Auth::id())
-> update(array(
'last_login' => date('Y-m-d H:i:s')
));
});
//
}
}

Categories