When I delete a row using this syntax:
$user->delete();
Is there a way to attach a callback of sorts, so that it would e.g. do this automatically:
$this->photo()->delete();
Preferably inside the model-class.
I believe this is a perfect use-case for Eloquent events (http://laravel.com/docs/eloquent#model-events). You can use the "deleting" event to do the cleanup:
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
// this is a recommended way to declare event handlers
public static function boot() {
parent::boot();
static::deleting(function($user) { // before delete() method call this
$user->photos()->delete();
// do the rest of the cleanup...
});
}
}
You should probably also put the whole thing inside a transaction, to ensure the referential integrity..
You can actually set this up in your migrations:
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
Source: http://laravel.com/docs/5.1/migrations#foreign-key-constraints
You may also specify the desired action for the "on delete" and "on
update" properties of the constraint:
$table->foreign('user_id')
->references('id')->on('users')
->onDelete('cascade');
Note: This answer was written for Laravel 3. Thus might or might not works well in more recent version of Laravel.
You can delete all related photos before actually deleting the user.
<?php
class User extends Eloquent
{
public function photos()
{
return $this->has_many('Photo');
}
public function delete()
{
// delete all related photos
$this->photos()->delete();
// as suggested by Dirk in comment,
// it's an uglier alternative, but faster
// Photo::where("user_id", $this->id)->delete()
// delete the user
return parent::delete();
}
}
Hope it helps.
Relation in User model:
public function photos()
{
return $this->hasMany('Photo');
}
Delete record and related:
$user = User::find($id);
// delete related
$user->photos()->delete();
$user->delete();
There are 3 approaches to solving this:
1. Using Eloquent Events On Model Boot (ref: https://laravel.com/docs/5.7/eloquent#events)
class User extends Eloquent
{
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->delete();
});
}
}
2. Using Eloquent Event Observers (ref: https://laravel.com/docs/5.7/eloquent#observers)
In your AppServiceProvider, register the observer like so:
public function boot()
{
User::observe(UserObserver::class);
}
Next, add an Observer class like so:
class UserObserver
{
public function deleting(User $user)
{
$user->photos()->delete();
}
}
3. Using Foreign Key Constraints (ref: https://laravel.com/docs/5.7/migrations#foreign-key-constraints)
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
As of Laravel 5.2, the documentation states that these kinds of event handlers should be registered in the AppServiceProvider:
<?php
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*
* #return void
*/
public function boot()
{
User::deleting(function ($user) {
$user->photos()->delete();
});
}
I even suppose to move them to separate classes instead of closures for better application structure.
It is better if you override the delete method for this. That way, you can incorporate DB transactions within the delete method itself. If you use the event way, you will have to cover your call of delete method with a DB transaction every time you call it.
In your User model.
public function delete()
{
\DB::beginTransaction();
$this
->photo()
->delete()
;
$result = parent::delete();
\DB::commit();
return $result;
}
To elaborate on the selected answer, if your relationships also have child relationships that must be deleted, you have to retrieve all child relationship records first, then call the delete() method so their delete events are fired properly as well.
You can do this easily with higher order messages.
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* #return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get()->each->delete();
});
}
}
You can also improve performance by querying only the relationships ID column:
class User extends Eloquent
{
/**
* The "booting" method of the model.
*
* #return void
*/
public static function boot() {
parent::boot();
static::deleting(function($user) {
$user->photos()->get(['id'])->each->delete();
});
}
}
Using Constrained()
After Laravel 7, new foreignId() and constrained() methods are available for defining relationship constraint in database. OnDelete() method can be used on these methods to automatically delete related records.
Old style
$table->unsignedBigInterer('user_id');
$table->foreign('user_id')
->references('id')
->on('users')
->onDelete('cascade');
New style
$table->foreignId('user_id')
->constrained()
->onDelete('cascade');
I would iterate through the collection detaching everything before deleting the object itself.
here's an example:
try {
$user = User::findOrFail($id);
if ($user->has('photos')) {
foreach ($user->photos as $photo) {
$user->photos()->detach($photo);
}
}
$user->delete();
return 'User deleted';
} catch (Exception $e) {
dd($e);
}
I know it is not automatic but it is very simple.
Another simple approach would be to provide the model with a method. Like this:
public function detach(){
try {
if ($this->has('photos')) {
foreach ($this->photos as $photo) {
$this->photos()->detach($photo);
}
}
} catch (Exception $e) {
dd($e);
}
}
Then you can simply call this where you need:
$user->detach();
$user->delete();
Add delete function on model that you want to delete
Define relations of models
for example in this instance:
/**
* #return bool|null
*/
public function delete(): ?bool
{
$this->profile()->delete();
$this->userInterests()->delete();
$this->userActivities()->delete();
$this->lastLocation()->delete();
return parent::delete();
}
And relations in user model are:
public function profile()
{
return $this->hasOne(Profile::class, 'user_id', 'id');
}
public function userInterests()
{
return $this->hasMany(userInterest::class, 'user_id', 'id');
}
public function userActivities()
{
return $this->hasMany(userActivity::class, 'user_id', 'id');
}
public function lastLocation()
{
return $this->hasOne(LastLocation::class, 'user_id', 'id');
}
This way worked for me on Laravel 8:
public static function boot() {
parent::boot();
static::deleted(function($item){
$item->deleted_by = \Auth::id(); // to know who delete item, you can delete this row
$item->save(); // to know who delete item, you can delete this row
foreach ($item->photos as $photo){
$photo->delete();
}
});
}
public function photos()
{
return $this->hasMany('App\Models\Photos');
}
Note: deleting in this syntax $user->photos()->delete(); not worked for me...
In my case it was pretty simple because my database tables are InnoDB with foreign keys with Cascade on Delete.
So in this case if your photos table contains a foreign key reference for the user than all you have to do is to delete the hotel and the cleanup will be done by the Data Base, the data base will delete all the photos records from the data base.
Here are the perfect solutions.
# model
public function order_item_properties()
{
return $this->hasMany(OrderItemProperty::class, 'order_id', 'id');
}
public function order_variations()
{
return $this->hasMany(OrderItemVariation::class, 'order_id', 'id');
}
# controller
$order_item = OrderItem::find($request->order_id);
$order_item->order_item_properties()->delete();
$order_item->order_variations()->delete();
$order_item->delete();
return response()->json([
'message' => 'Deleted',
]);
Or you can do this if you wanted, just another option:
try {
DB::connection()->pdo->beginTransaction();
$photos = Photo::where('user_id', '=', $user_id)->delete(); // Delete all photos for user
$user = Geofence::where('id', '=', $user_id)->delete(); // Delete users
DB::connection()->pdo->commit();
}catch(\Laravel\Database\Exception $e) {
DB::connection()->pdo->rollBack();
Log::exception($e);
}
Note if you are not using the default laravel db connection then you need to do the following:
DB::connection('connection_name')->pdo->beginTransaction();
DB::connection('connection_name')->pdo->commit();
DB::connection('connection_name')->pdo->rollBack();
It’s better to use onDelete cascade when defining your model’s migration. This takes care of deleting the model’s relations for you:
e.g.
$table->foreign(’user_id’)
->references(’id’)->on(’users’)
->onDelete(’cascade’);
If you happen to find yourself thinking about how to delete a model and its relations to a level greater than 3 or 4 nested relations, then you should consider redefining your model's relationships.
$table->foreignId('user_id')->constrained('user')->cascadeOnDelete();
or
$table->foreignId('user_id')->constrained()->cascadeOnDelete();
yeah, but as #supersan stated upper in a comment, if you delete() on a QueryBuilder, the model event will not be fired, because we are not loading the model itself, then calling delete() on that model.
The events are fired only if we use the delete function on a Model Instance.
So, this beeing said:
if user->hasMany(post)
and if post->hasMany(tags)
in order to delete the post tags when deleting the user, we would have to iterate over $user->posts and calling $post->delete()
foreach($user->posts as $post) { $post->delete(); } -> this will fire the deleting event on Post
VS
$user->posts()->delete() -> this will not fire the deleting event on post because we do not actually load the Post Model (we only run a SQL like: DELETE * from posts where user_id = $user->id and thus, the Post model is not even loaded)
You can use this method as an alternative.
What will happen is that we take all the tables associated with the users table and delete the related data using looping
$tables = DB::select("
SELECT
TABLE_NAME,
COLUMN_NAME,
CONSTRAINT_NAME,
REFERENCED_TABLE_NAME,
REFERENCED_COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE
WHERE REFERENCED_TABLE_NAME = 'users'
");
foreach($tables as $table){
$table_name = $table->TABLE_NAME;
$column_name = $table->COLUMN_NAME;
DB::delete("delete from $table_name where $column_name = ?", [$id]);
}
Related
I have a Post table with the following fields (id, name, state_id, city_id, category_id, subcategory_id)
and PostPhoto table with the following fields (id, photo_url, post_id)
The Post Model has
public function user(){
return $this->belongsTo(User::class);
}
public function category(){
return $this->belongsTo(Category::class);
}
public function subCategory(){
return $this->hasOne(SubCategory::class);
}
public function photos(){
return $this->hasMany(PostPhoto::class);
}
public function city(){
return $this->belongsTo(City::class);
}
and PostPhoto Model has
public function post()
{
return $this->belongsTo(Post::class);
}
Now if I want to delete a particular post, it'll fail due to foreign key constrains. How do I delete the Post together with postphotos associated with it completely from database. And each post can have multiple photos also. Please any suggestion will be appreciated. Thanks
Either you can make post_id as a foreign key and make cascade relation on update and delete which will automatically delete post photos on post deletion.
otherwise, you can try below code.
try {
\DB::beginTrasaction();
PostPhotos::where('post_id', 'your-post-id')->delete();
Post::where('id', 'your-post-id')->delete();
\DB::commit();
} catch(\Exception $exception) {
\DB::rollback();
}
In your Post model add booted method .boot method will call whenever model executes.if you are calling post delete then static deleting closure will exicutes
if you are using latest larvel 8
protected static function booted()
{
static::deleting(function($post) {
$post->photos()->delete();
});
}
if you are using older laravel version then
protected static function boot()
{
parent::boot();
static::deleting(function($post) {
$post->photos()->delete();
});
}
Update
$post=Post::find($id);
$post->photos()->delete();
$post->delete();
I am working on a project in which there are events, which each relate to two single forms on two separate relations – booking and survey. These forms are identically constructed, making it seem unnecessary to use two entirely distinct form models – I instead wanted to use a polymorphic relation, but it appears that isn't possible.
What is the appropriate way to structure this relationship?
Events have one or no booking form
Events have one or no survey form
Forms are a separate, single table
What I have tried:
Polymorphic relationship: Not compatible with two relations to the same model.
Has one relationship: This used a booking_id and survey_id but refused to set either of these fields.
Has many relationship with a type field: Made it difficult to easily save the forms, as it wasn't possible to save to the single relationship. There was also no restriction on the number of forms.
class Event extends Model
{
public function booking()
{
return $this->hasOne(Form::class, 'id', 'booking_form_id');
}
public function survey()
{
return $this->hasOne(Form::class, 'id', 'survey_form_id');
}
}
...
class Form extends Model
{
public function event()
{
return $this->belongsTo(Event::class);
}
}
...
$event = new Event;
$event->name = 'Event';
$event->save();
$booking = new Form;
$booking->name = 'booking';
$event->booking()->save($booking);
$survey = new Form;
$survey->name = 'survey';
$event->survey()->save($survey);
...
Schema::create('events', function (Blueprint $table) {
$table->bigIncrements('id');
$table->string('name');
$table->unsignedInteger('booking_form_id')->nullable()->index();
$table->unsignedInteger('survey_form_id')->nullable()->index();
$table->timestamps();
});
Schema::create('forms', function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
});
What would be preferable:
Using a polymorphic relationship which would allow forms to be used in other parts of the application.
Using multiple hasOne relationships to limit the number of forms to one for each type.
I think you got your param order wrong. It's hasOne($related, $foreignKey, $localKey)
class Event extends Model
{
/* if you haven't changed the default primary keys, $localKey should be equal to 'id' */
public function booking()
{
return $this->belongsTo(Form::class, 'booking_form_id');
}
public function survey()
{
return $this->belongsTo(Form::class, 'survey_form_id');
}
}
class Form extends Model
{
public function booking_event()
{
return $this->hasOne(Event::class, 'booking_form_id');
}
public function survey_event()
{
return $this->hasOne(Event::class, 'survey_form_id');
}
}
Now there's 2 ways you can go about this.
If a Form can belong to both kind of events, you need to return a collection when accessing $form->event.
If a Form can belong to only one kind of event, you need to guess which kind and return the model when accessing $form->event.
# Form model
# 1. can be achieved using an accessor. Cannot be eager loaded but can be appended with the $appends Model property
public function getEventsAttribute()
{
return collect([$this->booking_event, $this->survey_event]);
}
# Form model
# 2. can be achieved using a relationship that guesses which relation it should return. Since it returns a relationship, it can be eager loaded.
public function event()
{
return ($this->booking_event()->count() != 0) ? $this->booking_event() : $this->survey_event();
}
I am using Laravel 5.7 and now I am trying setup a relationship between three tables named:
Tickets (PK - TicketID, FK - CampusID)
Campus (PK - CampusID, FK - TechID)
User (PK - TechID)
I don't think I set up my models correctly as I am showing a ticket where the CampusID doesn't belong to the TechID. I am looking for a best practice on setting up Eloquent to keep the data integrity in place so I can prevent any abnormalities. As mentioned above the foreign key for Tickets should reference the Campus primary key, and Campus foreign key should reference the User primary key.
Here are my Models:
Ticket
protected $table='tickets';
public function user() {
return $this->belongsTo(User::class);
}
Campus
protected $table='campus';
public function user() {
return $this->belongsTo(User::class);
}
User
public function campus()
{
return $this->hasMany(Campus::class, 'TechID');
}
public function ticket()
{
return $this->hasMany(Ticket::class, 'AssignedTo');
}
Here is my controller:
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Auth;
use App\Campus;
use App\Ticket;
class PagesController extends Controller
{
/**
* Create a new controller instance.
*
* #return void
*/
public function __construct()
{
$this->middleware('auth');
}
// Dashboard Page
public function index()
{
$user = Auth::user();
$campuses = Campus::where('TechID',$user->id)->pluck('CampusName');
$tickets = Ticket::all()->where('AssignedTo', $user->id);
return view('home')->with(['user' => $user,'campuses'=>$campuses,'tickets'=>$tickets]);
}
// Queue Page
public function Queue() {
return view('Pages.Queue');
}
// Reports Page
public function Reports() {
return view('Pages.Reports');
}
// Search Page
public function Search() {
return view('Pages.Search');
}
}
I think my models are fine, but my controller is probably where I made some mistakes. I've tried reading questions on here already, watching videos, and reading the Laravel docs, but nothing has really clicked with me yet. I really appreciate any and all help. Ideally it should cascade changes. So if I have a situation where I want to change what location a tech belongs to I could just make the change in the Campus table probably in the TechID column.
I would use Eager Loading.
public function index()
{
$user = User::with([
'campuses' => function($query) {
$query->select(['id', 'CampusName']);
},
'tickets'
])->where('id', Auth::id())->first();
$campuses = $user->campuses->pluck('CampusName');
$tickets = Ticket::all()->where('AssignedTo', $user->id);
return view('home')->with([
'user' => $user,
'campuses'=>$user->campuses->pluck('CampusName'),
'tickets'=>$user->tickets]);
}
EDIT
You need to update your User model.
public function campuses()
{
return $this->hasMany(Campus::class, 'TechID');
}
public function tickets()
{
return $this->hasMany(Ticket::class, 'AssignedTo');
}
I want to correctly save both polymorphic relationships at the same time. The code below works, but I feel it could be a lot cleaner as I presume the two update() methods are calling the database twice.
A NewsModule::class can have different module items; VideoModule, TextModule, ImageModule, and a few more. Each containing their own content to be attached to the parent NewsModule.
As mentioned, the code works so the relationships are set up correctly, but I'm convinced there's a cleaner way of saving both at the same time.
I'm also open to suggestions about cleaning up the if statements too. But maybe that's another post.
public function update(Request $request, $id)
{
$module = NewsModule::find($id);
if ($module->type === 'text') {
$content = TextModule::find($module->content_id);
} elseif ($module->type === 'image') {
$content = ImageModule::find($module->content_id);
};
$module->update($request->all());
$content->update($request->all());
return fractal()
->item($module, new NewsModuleTransformer)
->parseIncludes(['content'])
->toArray();
}
Updated (more code by request)...
Structure:
news_modules
- id
- content_id
- content_type
- etc
text_modules
- id
- content
- etc
image_modules
- id
- image_id
- etc
NewsModule:
class NewsModule extends Model
{
public function content()
{
return $this->morphTo();
}
}
All item modules:
class TextModule extends Model
{
public function newsmodules()
{
return $this->morphMany(NewsModule::class, 'content');
}
}
public function update(Request $request, $id)
{
$modele = NewsModule::find($id);
$module->update($request->all());
$module->content->update($request->all());
return fractal()
->item($module, new NewsModuleTransformer)
->parseIncludes(['content'])
->toArray();
}
That will run 4 queries total. 1 for each module to retrieve and another to update. That can be cut down to 3 like:
public function update(Request $request, $id)
{
$modele = NewsModule::find($id);
$module->update($request->all());
$module->content()->update($request->all());
return fractal()
->item($module, new NewsModuleTransformer)
->parseIncludes(['content'])
->toArray();
}
The downside to $module->content()->update($request->all()); is it will throw an error if there is anything in $request->all() that isn't a column in that content model or there is an array as a value. You can avoid that by just calling update() on the $fillable properties (if you have them defined) of the related model like:
$fillable = $module->content()->getRelated()->getFillable();
$module->content()->update($request->only($fillable));
This way will also not fire any model event listeners you have since you are never retrieving the model from the database.
To take everything one step further, look into Route Model Binding. In your app\Providers\RouteServiceProvider's boot() method:
public function boot()
{
parent::boot();
Route::model('news', App\NewsModule::class);
}
This way 'news' will always resolve to an instance of NewsModule when using it as a route parameter. So your route would be something like:
Route::match(['patch', 'put'], '/news/{news}', 'NewsController#update');
So in your update method you could resolve the model by just type hinting it in the method allowing you to do:
public function update(Request $request, NewsModule $news)
{
$news->update($request->all());
$news->content->update($request->all());
return fractal()
->item($news, new NewsModuleTransformer)
->parseIncludes(['content'])
->toArray();
}
I have four Models:
User
Client
Store
Opportunity
The relationships are defined as such:
User hasMany Client
Client hasMany Store
Store hasMany Opportunity
User hasManyThrough Store, Client (this works)
The problem is that I'm attempting to access the User->Opportunity relationship via built-in Laravel relationships, but it doesn't seem as if I can do it without a custom Query or an additional user_id column on the opportunities table to allow direct access (even though one can be inferred from the Store->Client relationship). I'm also not a fan of nested foreach loops if they can be avoided.
My question:
Is there a way to go one level deeper and directly access a User's Opportunities in this scenario? The actual Model code and all relevant relationships are as follows:
User
class User extends Eloquent{
public function clients(){
return $this->hasMany('Client');
}
public function stores(){
return $this->hasManyThrough('Store', 'Client');
}
public function proposals(){
return $this->hasMany('Proposal');
}
public function opportunities(){ //This does the job, but I feel like it could be better
return Opportunity::join('stores', 'stores.id', '=', 'opportunities.store_id')->
join('clients', 'clients.id', '=', 'stores.client_id')->
join('users', 'users.id', '=', 'clients.user_id')->
select('opportunities.*')->
where('users.id', $this->id);
}
public function getOpportunitiesAttribute(){ //This just helps mimic the hasManyThrough shorthand
return $this->opportunities()->get();
}
}
Client
class Client extends Eloquent{
public function stores(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunities(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Store
class Store extends Eloquent {
public function client(){
return $this->belongsTo('Client');
}
public function opportunities(){
return $this->hasMany('Opportunity');
}
}
Opportunity
class Opportunity extends Eloquent {
public function store(){
return $this->belongsTo('Store');
}
}
I don't think there is such method in Laravel. You have to create your custom query. This custom query can be very expensive since multiple queries will be performed. Thus, the optimum solution for this, according to me, is to relate User and Opportunity with a foreign key.
However, if you don't desire to link User and Opportunity with a foreign key, then you can create a custom query to handle this. Simply add a "hasManyThrough" relation between Opportunity and Client model like,
<?php
class Client extends Eloquent{
public function store(){
return $this->hasMany('Store');
}
public function user(){
return $this->belongsTo('User');
}
public function opportunity(){
return $this->hasManyThrough('Opportunity', 'Store');
}
}
Then create a static function in User model.
<?php
class User extends Eloquent implements UserInterface, RemindableInterface {
use UserTrait, RemindableTrait;
public function client(){
return $this->hasMany('Client');
}
public function store(){
return $this->hasManyThrough('Store', 'Client');
}
public static function getOpportunityOfUser($userId)
{
$clients = User::find($userId)->client;
foreach ($clients as $client) {
$opportunities[] = Client::find($client->id)->opportunity;
}
return $opportunities;
}
}
Now you can access Opportunity realted to a User in one go like,
Route::get('/', function()
{
return $usersOpportunities = User::getOpportunityOfUser(1);
});
This will return all opportunity of all clients related to User with id '1'.
I created a HasManyThrough relationship with unlimited levels: Repository on GitHub
After the installation, you can use it like this:
class User extends Model {
use \Staudenmeir\EloquentHasManyDeep\HasRelationships;
public function opportunities() {
return $this->hasManyDeep(Opportunity::class, [Client::class, Store::class]);
}
}