GithubHelp home page GithubHelp logo

michaeldyrynda / laravel-cascade-soft-deletes Goto Github PK

View Code? Open in Web Editor NEW
902.0 14.0 68.0 97 KB

Cascading deletes for Eloquent models that implement soft deletes

Home Page: https://dyrynda.com.au

License: MIT License

PHP 100.00%
laravel eloquent eloquent-models package

laravel-cascade-soft-deletes's Introduction

Cascading soft deletes for the Laravel PHP Framework

Build Status Latest Stable Version Total Downloads License Buy us a tree

Introduction

In scenarios when you delete a parent record - say for example a blog post - you may want to also delete any comments associated with it as a form of self-maintenance of your data.

Normally, you would use your database's foreign key constraints, adding an ON DELETE CASCADE rule to the foreign key constraint in your comments table.

It may be useful to be able to restore a parent record after it was deleted. In those instances, you may reach for Laravel's soft deleting functionality.

In doing so, however, you lose the ability to use the cascading delete functionality that your database would otherwise provide. That is where this package aims to bridge the gap in functionality when using the SoftDeletes trait.

Code Samples

<?php

namespace App;

use App\Comment;
use Dyrynda\Database\Support\CascadeSoftDeletes;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Post extends Model
{
    use SoftDeletes, CascadeSoftDeletes;

    protected $cascadeDeletes = ['comments'];

    protected $dates = ['deleted_at'];

    public function comments()
    {
        return $this->hasMany(Comment::class);
    }
}

Now you can delete an App\Post record, and any associated App\Comment records will be deleted. If the App\Comment record implements the CascadeSoftDeletes trait as well, it's children will also be deleted and so on.

$post = App\Post::find($postId)
$post->delete(); // Soft delete the post, which will also trigger the delete() method on any comments and their children.

Note: It's important to know that when you cascade your soft deleted child records, there is no way to know which were deleted by the cascading operation, and which were deleted prior to that. This means that when you restore the blog post, the associated comments will not be.

Because this trait hooks into the deleting Eloquent model event, we can prevent the parent record from being deleted as well as any child records, if any exception is triggered. A LogicException will be triggered if the model does not use the Illuminate\Database\Eloquent\SoftDeletes trait, or if any of the defined cascadeDeletes relationships do not exist, or do not return an instance of Illuminate\Database\Eloquent\Relations\Relation.

Installation

This trait is installed via Composer. To install, simply add to your composer.json file:

$ composer require dyrynda/laravel-cascade-soft-deletes

Support

If you are having general issues with this package, feel free to contact me on Twitter.

If you believe you have found an issue, please report it using the GitHub issue tracker, or better yet, fork the repository and submit a pull request.

If you're using this package, I'd love to hear your thoughts. Thanks!

Treeware

You're free to use this package, but if it makes it to your production environment you are required to buy the world a tree.

It’s now common knowledge that one of the best tools to tackle the climate crisis and keep our temperatures from rising above 1.5C is to plant trees. If you support this package and contribute to the Treeware forest you’ll be creating employment for local families and restoring wildlife habitats.

You can buy trees here

Read more about Treeware at treeware.earth

laravel-cascade-soft-deletes's People

Contributors

adriandmitroca avatar d3v1 avatar davidhemphill avatar decaylala avatar dependabot-preview[bot] avatar gahlawat avatar jamesmills avatar jasonmccreary avatar laravel-shift avatar lukasleitsch avatar mattpramschufer avatar michaeldyrynda avatar paulredmond avatar schnittstabil avatar totov avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

laravel-cascade-soft-deletes's Issues

Deleting event won't fire when called on static

Hi! I have been facing a problem, the deleting event won't fire until I change static::deleting to self::deleting in trait declaration.
I have tried calling both delete and destroy Eloquent Model methods, but it won't work either way.

Is this a bug or am I doing things wrong?

I am using Laravel Framework 8.22.1 and PHP 7.4.3

image

Cannot instantiate trait Iatstuti\Database\Support\CascadeSoftDeletes

I'm using Laravel 5.3 and I'm getting the following error in the whole application.

FatalThrowableError in ProviderRepository.php line 146:
Cannot instantiate trait Iatstuti\Database\Support\CascadeSoftDeletes

Any idea what I could be doing wrong? I added

Iatstuti\Database\Support\CascadeSoftDeletes::class

to my config/app.php

Laravel 6.0 Support?

Hey, love this package. I want to upgrade to Laravel 6.0 and was wondering when you plan on adding support?

Thanks in advance and happy to contribute to the effort if need be.

It Doesn't work

Hi
Actually Its not properly working for me.
I have the following two models:
Menu
SubMenu

-->I also added the code on the model but its doesn't works.
`class Menu extends Model
{
use HasFactory;
use SoftDeletes;
use CascadeSoftDeletes;

protected $cascadeDeletes = ['subMenusFromMenu'];
protected $dates = ['deleted_at'];


public function subMenusFromMenu(): HasMany
{
    return $this->hasMany(SubMenu::class, 'menu_id', 'id');
}

}`

I tried to delete Menu , But after deleting menu the related child submenu records doesn't delete.

How to delete distant records?

If I have a moded that have many children models,
How to delete all posts if it was like this:

      class Adjustment extends Model
      {
          use HasFactory, SoftDeletes, CascadeSoftDeletes;
          protected $cascadeDeletes = ['adjustedProducts'];
      
          protected $guarded = [];
      
          public function getDateAttribute($value) {
              return Carbon::parse($value)->format('d M, Y');
          }
      
          public function adjustedProducts() {
              return $this->hasMany(AdjustedProduct::class, 'adjustment_id', 'id');
          }
      
          public static function boot() {
              parent::boot();
      
              static::creating(function ($model) {
                  $number = Adjustment::max('id') + 1;
                  $model->reference = make_reference_id('ADJ', $number);
              });
          }
      
      }

This isn't workin for me, when I delete an adjustment the adjustedProducts doesn't cascade with it.
How to cascade soft delete for this case?

Compatability with polymorphic relationships?

I installed the package successfully with one->many relationship child.
Cant seem to get it going with a polymorphic setup. Is it supported?

PS. Good work on the Laravel News podcast. It's great to hear a familiar accent in the Laravel world.

Call to a member function delete() on null

I'm receiving the following error when deleting a model using this trait.

FatalThrowableError in CascadeSoftDeletes.php line 40: Call to a member function delete() on null

Full stack trace:

in CascadeSoftDeletes.php line 40
at Post::Iatstuti\Database\Support\{closure}(object(Post))
at call_user_func_array(object(Closure), array(object(Post))) in Dispatcher.php line 221
at Dispatcher->fire('eloquent.deleting: App\Post', array(object(Post)), true) in Dispatcher.php line 164
at Dispatcher->until('eloquent.deleting: App\Post', object(Post)) in Model.php line 1686
at Model->fireModelEvent('eloquent.deleting: App\Post') in Model.php line 1122
at Model->delete() in PostsController.php line 142

The $child variable here in the trait is returning null.

$delete = $model->forceDeleting ? 'forceDelete' : 'delete';

            foreach ($model->getCascadingDeletes() as $relationship) {
                foreach ($model->{$relationship} as $child) {
                    $child->{$delete}();
                }
            }

I must be missing something obvious because testing it like so, it appears that $child is not being set when the for loop runs.

dd($model->getCascadingDeletes()); // returns ['website'] (the model relationship to be deleted)

 foreach ($model->getCascadingDeletes() as $relationship) {
                foreach ($model->{$relationship} as $child) {
                    dd($child); // returns Null
                    dd($model->{$relationship}); // returns the cascading model to be deleted. 
                    $child->{$delete}();
                }
            }

If anyone can point me in the right direction what I or this package is doing wrong, that would be much appreciated :)

Polymorphic many to many pivot table support?

(apologies for the code blocks - the formatter is working for me)

When using this package on a polymorphic many to many relationship, the pivot table entries are forcefully deleted and not soft deleted when a parent record is deleted.

Here is my code:

Parent Models receive the HasTag trait and this attribute:
protected $cascadeDeletes = [
"tags",
];

HasTag Trait:
public function tags(): MorphToMany
{
return $this->morphToMany(Tag::class, "taggable:)->withTimestamps();
}

Tag Model:
class Tag extends BaseModel
{
use HasFactory;
use SoftDeletes;
use CascadeSoftDeletes;

public $table = "tags";
protected $fillable = 
[
    "tag",
    "slug",
];

protected $cascadeDeletes = [
    "orders",
];

public function orders()
{
    return $this->morphedByMany(Order::class, "taggable");
}

}

Taggable Model:
class Taggable extends Model
{
use HasFactory;
use SoftDeletes;

public $table = "taggables";

}

Support for Laravel 7

Hi,
It seems that package has a conflict with newest version of Laravel 7. Most likely that one seems to be problematic:

"don't install illuminate/events 6.x-dev|don't install laravel/framework 7.x-dev"

Invalid argument supplied for foreach()

I have a structure where I have candidates, consultants, managers and customers in a system. Each of these 4 objects has a user. So a user can either be a candidate, consultant, manager or customer. This means that 3 of the 4 relation attributes will return null instead of an object.

I have the following settings in my user model.

use SoftDeletes, CascadeSoftDeletes;

    protected $cascadeDeletes = ['candidate', 'consultant', 'customer', 'manager'];

Now I get this error. I tested removing a candidate and filled the $cascadeDeletes with only candidate and then I got this error #10, but you are already working on it I saw. This is indeed a one-on-one relation.

in CascadeSoftDeletes.php line 39
at HandleExceptions->handleError('2', 'Invalid argument supplied for foreach()', '/home/langstra/Documents/work/randstad/ttempp/vendor/iatstuti/laravel-cascade-soft-deletes/src/CascadeSoftDeletes.php', '39', array('model' => object(User), 'invalidCascadingRelationships' => array(), 'delete' => 'delete', 'relationship' => 'candidate')) in CascadeSoftDeletes.php line 39
at User::Iatstuti\Database\Support\{closure}(object(User))
at call_user_func_array(object(Closure), array(object(User))) in Dispatcher.php line 221
at Dispatcher->fire('eloquent.deleting: Ttempp\Models\User', array(object(User)), true) in Dispatcher.php line 164
at Dispatcher->until('eloquent.deleting: Ttempp\Models\User', object(User)) in Model.php line 1686
at Model->fireModelEvent('eloquent.deleting: Ttempp\Models\User') in Model.php line 1122

Not work with blongToMany

I have a user and a shift model.
each user can be assigned to lots of shifts and each shift can be assigned to lots of users
so in the shift model I created the relation like this

in shift :
public function users()
{
return $this->belongsToMany(User::class, 'user_limitations');
}

your library use delete on ( blongsToMany) instead of detach . you should change it to detach on belongToMany relations

restore functionality?

deleted_at timestamp would tell you if children were deleted from cascading event? not sure if its possible to be at a different second though or if the time of the delete command will be set on all deleted records

Issue in composer install laravel-cascade-soft-deletes

Hi

I am using laravel 7.3 when i run this command 'composer require dyrynda/laravel-cascade-soft-deletes'

I have get this error :
Problem 1
- Root composer.json requires dyrynda/laravel-cascade-soft-deletes ^4.0 -> satisfiable by dyrynda/laravel-cascade-soft-deletes[4.0.0].
- dyrynda/laravel-cascade-soft-deletes 4.0.0 requires illuminate/database ^8.0 -> found illuminate/database[v8.0.0, ..., v8.13.0] but it conflicts with another require.

I am unable to found root casue can you please check this.

Models/Migrations?

Great idea this plugin! Thank you.

Do we have to add the use SoftDeletes trait to the child models? How about in the migrations? Do you have a full working example maybe? The installation steps are not really clear

(suggestion) Add cascade restore

<?php

declare(strict_types=1);

namespace App\Traits;

// https://github.com/michaeldyrynda/laravel-cascade-soft-deletes

use App\Exceptions\CascadeSoftDeleteException;
use Illuminate\Database\Eloquent\Relations\Relation;

trait CascadeSoftDeletes
{
    /**
     * Boot the trait.
     *
     * Listen for the deleting event of a soft deleting model, and run
     * the delete operation for any configured relationship methods.
     *
     * @throws \LogicException
     */
    protected static function bootCascadeSoftDeletes()
    {
        static::deleting(function ($model) {
            /** @var CascadeSoftDeletes $model */
            $model->validateCascadingSoftDelete();

            $model->runCascadingDeletes();
        });
        static::restoring(function ($model) {
            /** @var CascadeSoftDeletes $model */
            $model->validateCascadingRestore();

            $model->runCascadingRestores();
        });
    }

    /**
     * Validate that the calling model is correctly setup for cascading soft deletes.
     *
     * @throws \App\Exceptions\CascadeSoftDeleteException
     */
    protected function validateCascadingSoftDelete()
    {
        if (! $this->implementsSoftDeletes()) {
            throw CascadeSoftDeleteException::softDeleteNotImplemented(get_called_class());
        }

        if ($invalidCascadingDeletesRelationships = $this->hasInvalidCascadingDeletesRelationships()) {
            throw CascadeSoftDeleteException::invalidRelationships($invalidCascadingDeletesRelationships);
        }
    }

    /**
     * Validate that the calling model is correctly setup for cascading restores.
     *
     * @throws \App\Exceptions\CascadeSoftDeleteException
     */
    protected function validateCascadingRestore()
    {
        if (! $this->implementsSoftDeletes()) {
            throw CascadeSoftDeleteException::softDeleteNotImplemented(get_called_class());
        }

        if ($invalidCascadingRestoresRelationships = $this->hasInvalidCascadingRestoresRelationships()) {
            throw CascadeSoftDeleteException::invalidRelationships($invalidCascadingRestoresRelationships);
        }
    }

    /**
     * Run the cascading soft delete for this model.
     *
     * @return void
     */
    protected function runCascadingDeletes()
    {
        foreach ($this->getActiveCascadingDeletes() as $relationship) {
            $this->cascadeSoftDeletes($relationship);
        }
    }

    /**
     * Run the cascading restore for this model.
     *
     * @return void
     */
    protected function runCascadingRestores()
    {
        foreach ($this->getActiveCascadingRestores() as $relationship) {
            $this->cascadeRestores($relationship);
        }
    }

    /**
     * Cascade delete the given relationship on the given mode.
     *
     * @param  string  $relationship
     * @return void
     */
    protected function cascadeSoftDeletes($relationship)
    {
        $delete = $this->forceDeleting ? 'forceDelete' : 'delete';

        foreach ($this->{$relationship}()->get() as $model) {
            $model->pivot ? $model->pivot->{$delete}() : $model->{$delete}();
        }
    }

    /**
     * Cascade restore the given relationship.
     *
     * @param  string  $relationship
     * @return void
     */
    protected function cascadeRestores($relationship)
    {
        foreach ($this->{$relationship}()->onlyTrashed()->get() as $model) {
            /** @var \Illuminate\Database\Eloquent\SoftDeletes $model */
            $model->pivot ? $model->pivot->restore() : $model->restore();
        }
    }

    /**
     * Determine if the current model implements soft deletes.
     *
     * @return bool
     */
    protected function implementsSoftDeletes()
    {
        return method_exists($this, 'runSoftDelete');
    }

    /**
     * Determine if the current model has any invalid cascading deletes relationships defined.
     *
     * A relationship is considered invalid when the method does not exist, or the relationship
     * method does not return an instance of Illuminate\Database\Eloquent\Relations\Relation.
     *
     * @return array
     */
    protected function hasInvalidCascadingDeletesRelationships()
    {
        return array_filter($this->getCascadingDeletes(), function ($relationship) {
            return ! method_exists($this, $relationship) || ! $this->{$relationship}() instanceof Relation;
        });
    }

    /**
     * Determine if the current model has any invalid cascading restores relationships defined.
     *
     * A relationship is considered invalid when the method does not exist, or the relationship
     * method does not return an instance of Illuminate\Database\Eloquent\Relations\Relation.
     *
     * @return array
     */
    protected function hasInvalidCascadingRestoresRelationships()
    {
        return array_filter($this->getCascadingRestores(), function ($relationship) {
            return ! method_exists($this, $relationship) || ! $this->{$relationship}() instanceof Relation;
        });
    }

    /**
     * Fetch the defined cascading soft deletes for this model.
     *
     * @return array
     */
    protected function getCascadingDeletes()
    {
        return isset($this->cascadeDeletes) ? (array) $this->cascadeDeletes : [];
    }

    /**
     * Fetch the defined cascading restores for this model.
     *
     * @return array
     */
    protected function getCascadingRestores()
    {
        return isset($this->cascadeRestores) ? (array) $this->cascadeRestores : [];
    }

    /**
     * For the cascading deletes defined on the model, return only those that are not null.
     *
     * @return array
     */
    protected function getActiveCascadingDeletes()
    {
        return array_filter($this->getCascadingDeletes(), function ($relationship) {
            return ! is_null($this->{$relationship});
        });
    }

    /**
     * For the cascading restores defined on the model, return only those that are not null.
     *
     * @return array
     */
    protected function getActiveCascadingRestores()
    {
        return array_filter($this->getCascadingRestores(), function ($relationship) {
            return ! is_null($this->{$relationship}()->onlyTrashed());
        });
    }
}

Recursive deleting

Hello. I have this situation:

One category can have subcategories(they use the same model. The difference is that a subcategory have the ID of it's father, while the father category have the field 'FatherID' as NULL), and both can have posts on them. Something like this:

Category
Father posts
Subcategory
Subcategory Posts

When I delete a father category, I need to softdelete it's posts, it's subcategories and their posts. However, I'm not being able to softdelete the subcategories posts. Can you help me with this ?

Thanks in advance. This package is awesome =).

Order of 'deleting' listeners

First your package is great. works perfect. The issue that I am having is that I have my own event listeners for a model for 'deleting' your trait is firing before my deleting listener, I was wondering if you knew of a way to have it so my local model listener fired before your trait deleting listener.

Just to give you an example. I have a Model called Runs which has many Runners attached to it. I have an event setup for when a Run gets deleted to send a notification to all Runners that the run was deleted. Since your trait is cascading down to runners first, when i go to grab a collection of runners $run->runners()->get(); It returns 0 because they were already soft deleted.

What if onDelete is restrict instead of cascade?

I have model which uses soft delete but the action for onDelete is restrict instead of cascade.
I have gonna through your code and you haven't handling this case.

Can you be able to provide any solution to check the type of onDelete action if the type restrict then do not delete and show error?

at installation time, I had a error

Your requirements could not be resolved to an installable set of packages.

Problem 1
- Installation request for iatstuti/laravel-cascade-soft-deletes ^3.0 -> satisfiable by iatstuti/laravel-cascade-soft-deletes[3.0.0].
- Can only install one of: nesbot/carbon[2.x-dev, 1.39.1].
- Can only install one of: nesbot/carbon[2.x-dev, 1.39.1].
- Can only install one of: nesbot/carbon[2.x-dev, 1.39.1].
- Conclusion: install nesbot/carbon 2.x-dev
- Installation request for nesbot/carbon (locked at 1.39.1, required as ^1.22) -> satisfiable by nesbot/carbon[1.39.1].

Installation failed, reverting ./composer.json to its original content.

How to restore relational Data?

Suppose Post has several Comments, and comment has several Replies too.
Now if I soft delete a Post, it automatically soft deleting all the comments and replies under that Post. But, when I want to restore a Post, its comments and replies are not getting restored.

What can I do for it?

I tried this way:
Post::withTrashed()->whereId(1)->restore();

Feature suggestion for cascade restore

Quoting from the readme

Note: It's important to know that when you cascade your soft deleted child records, there is no way to know which were deleted by the cascading operation, and which were deleted prior to that. This means that when you restore the blog post, the associated comments will not be.

I just verified that the deleted_at timestamp does not get overwritten if records were deleted prior to the cascade delete happening. So it means that yes you can know which were deleted before or not by matching the timestamp with the one on the parent that was deleted. This means that technically, the restore operation on a blog post could restore any comments that have a (maybe nearly) exact timestamp. So yes this is technically possible.

Issue in pivot soft delete cascading

the package works perfect for one-to-many relationship, but when it comes to many-to-many relationship it's not working
here is my code :

    class Product extends Model
    {
        use HasFactory, SoftDeletes,CascadeSoftDeletes;
        protected $table = 'products';
        protected $cascadeDeletes  = ['product_categories_2'];
    
        public function product_categories_2(){
            return $this->belongsToMany(Category::class,'product_filter_categories')->using(ProductFilterCategory::class);
        }
    }

is it a wrong in my code or this package don't support many-to-many relation ?

One to One Relation

Hey guys!

I'm trying to delete a Model object that has a One-to-One relationship with another, but I get the following error from Laravel. Can someone help me?

"[Insurers] relationship must exist and return an object of type Illuminate\Database\Eloquent\Relations\Relation"

restore bug

When I restore a record, the relationship is not restored

Does not work

Hi

I have a project that is based on the Laravel 5.8 Boilerplate.

I have the following two models:

ERecipe
ERecipeIngredients

in the main I have this:

    protected $cascadeDeletes = ['e_ingredients'];

    public function e_ingredients()
    {
        return $this->hasMany(ERecipeIngredient::class,'recipe_id','id');
    }

But when deleting the Erecipe, the child records does not get deleted

e_ingredients returns: Illuminate\Database\Eloquent\Relations\HasMany

Problem with forcedelete() i polimorphic Relation Many

Hi,
I have a problem with permanently deleting records.

Namely, I have the following models:

class Employee extends Model
{
     use SoftDeletes;
     use CascadeSoftDeletes;

     protected array $cascadeDeletes = ['addresses'];

     public function addresses(): MorphMany
     {
         return $this->morphMany(Address::class, 'addressable');
     }
     ...
}
class Address extends Model
{
    use SoftDeletes;
    ...
}

Soft delete works fine - data is written to the deleted_at column.

And when I call it, it removes only the employee and the addresses assigned only to him are still orphaned in the database.

$employee->forceDelete();

How can I permanently delete a record in such a relationship? So far I have not found an answer in issues.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.