Code Monkey home page Code Monkey logo

how-laravel-soft-deletes's Introduction

Laravel’s SoftDelete is the combination of two things 

  1. A global scope to limit soft deleted records.
  2. An overridden eloquent’s delete method to insert fresh timestamps instead of actually deleting the records.

You see how simple is that, it just ask eloquent two things and these are

1) Global Scope

The first thing it asks is, hey eloquent, whenever you want to retrieve any record from the database, you are going to add just one more ‘where condition’ to the query. And that condition is whereNull(DELETED_AT), which means just retrieve only those records whose deleted_at column is null (non soft-deleted records)

2) Overridden Delete Method

And the second thing it asks is, hey eloquent, whenever you want to delete any record, you are going to call my delete method instead of your default delete method. What I am going to do in this method is, instead of actually deleting records, I will put fresh timestamps at deleted_at column.

Code Demonstration

Let's have a look at the code and see how these things are actually implemented in there.

So whenever we want to soft-delete our records, we use SoftDeletes trait in our model which is Illuminate\Database\Eloquent\SoftDeletes, right! Let's start from SoftDeletes trait and see what’s going on there.

There are many methods you will see but the most important method is bootSoftDeletes().

File: Illuminate\Database\Eloquent\SoftDeletes.php

public static function bootSoftDeletes(){   
  static::addGlobalScope(new SoftDeletingScope);
}

This method adds global scope to the modal which does the rest of the job. This method is called by eloquent whenever eloquent is created. If you wanna see how it gets called then let's jump to constructor of the eloquent modal.

File: Illuminate\Database\Eloquent\Model.php

public function __construct(array $attributes = []){
   $this->bootIfNotBooted();
   $this->syncOriginal();
   $this->fill($attributes);
}

The method of our interest is bootIfNotBooted().

File: Illuminate\Database\Eloquent\Model.php

protected function bootIfNotBooted(){
  if (! isset(static::$booted[static::class])) {
    static::$booted[static::class] = true;
    $this->fireModelEvent('booting', false);
    static::boot();
    $this->fireModelEvent('booted', false);
  }
}

the method of our concern here is static::boot().

protected static function boot(){
  static::bootTraits();
}

protected static function bootTraits(){

  $class = static::class;

  foreach(class_uses_recursive($class) as $trait) {

    if(method_exists( $class$method='boot'.class_basename($trait))) {
        forward_static_call([$class, $method]);
    }

   }

}

In brief, this method calls function of every trait in the modal whose name matches bootTraitName type signature i.e. bootMagicTrait for MagicTrait.

In our case of SoftDeletes trait, it is bootSoftDeletes();

File: Illuminate\Database\Eloquent\SoftDeletes.php

public static function bootSoftDeletes(){
  static::addGlobalScope(new SoftDeletingScope);
}

So this is the boot method of our SoftDeletes trait and it’s just adding a global scope SoftDeletingScope which we've already discussed.

Now it's time to jump to SoftDeletingScope.

class SoftDeletingScope implements Scope{
  
  public function apply(Builder $builder, Model $model){
    $builder->whereNull($model->getQualifiedDeletedAtColumn());
  }


  public function extend(Builder $builder){    

    foreach ($this->extensions as $extension) {      
      $this->{"add{$extension}"}($builder);
    }

    $builder->onDelete(function (Builder $builder) {
      $column = $this->getDeletedAtColumn($builder);

      return $builder->update([
        $column => $builder->getModel()->freshTimestampString(),
       ]);
    
    });

  } //extend function ends here


} //class ends here

The two important methods here are extends and apply. Eloquent calls both methods at some point of the execution. 

In brief words, the extend method is called whenever we build new query for the model (it's time when eloquent apply global scopes) and apply method is called whenever we retrieve new records through model.

Extend method:

Whenever we build query for any model, function newQuery() is called.

File: Illuminate\Database\Eloquent\Model.php

public function newQuery(){
  $builder = $this->newQueryWithoutScopes();

  foreach ($this->getGlobalScopes() as $identifier => $scope) {

    $builder->withGlobalScope($identifier, $scope);

  }

  return $builder;
}

This calls withGlobalScope method.

File: Illuminate\Database\Eloquent\Builder.php

public function withGlobalScope($identifier, $scope){

  $this->scopes[$identifier] = $scope;

  if (method_exists($scope, 'extend')) {
    $scope->extend($this);
  }

  return $this;
}

and this method calls extend method of the scope given to it. 

Let's jump to our extend method of SoftDeletingScope.

File: Illuminate\Database\Eloquent_SoftDeletingScope.php_

public function extend(Builder $builder){
  
  foreach ($this->extensions as $extension) {
    $this->{"add{$extension}"}($builder);
  }


  $builder->onDelete(function (Builder $builder) {

    $column = $this->getDeletedAtColumn($builder);

      return $builder->update([
        $column => $builder->getModel()->freshTimestampString(),
      ]);

   });


}

And in our extend method of SoftDeletingScope, we do two things. 

In the first part of of method, it just add some methods (macros) to query builder which you can call for specific purpose like forceDelete.

Builder's onDelete method

The second and most important thing is, we are overriding default delete method with our own method, which will be get called whenever any record is being deleted.

File: Illuminate\Database\Eloquent_SoftDeletingScope.php_

$builder->onDelete(function (Builder $builder) {

    $column = $this->getDeletedAtColumn($builder);

    return $builder->update([
        $column => $builder->getModel()->freshTimestampString()
    ]);

});

What we are doing here is instead of actually deleting any record, we are just updating deleted_at column with fresh timestamps. 

Apply method: 

This method is called whenever we try to retrieve new record from the database.

It's actually gets called from eloquent’s get method which is called by model's data retrieving methods like first, find, all or by user himself after any where clause.

File: Illuminate\Database\Eloquent\Builder.php

public function get($columns = ['*']){

  $builder = $this->applyScopes();

  $models = $builder->getModels($columns);

  if (count($models) > 0) {
    $models = $builder->eagerLoadRelations($models);
  }

  return $builder->getModel()->newCollection($models);

}

this method calls applyScopes

File: Illuminate\Database\Eloquent\Builder.php

public function applyScopes(){
  if (! $this->scopes) {
    return $this;
  }

  $builder = clone $this;

  foreach ($this->scopes as $scope) {

    $builder->callScope(function (Builder $builder) use ($scope) {
      if ($scope instanceof Closure) {
        $scope($builder);
      } elseif ($scope instanceof Scope) {
        $scope->apply($builder, $this->getModel());
      }

    });
  }
  return $builder;
}

The code of our interest is $scope->apply, which call apply method of specific scope.

And what our apply method does is, add a where clause to the query builder to restrict soft deleted columns.

File: Illuminate\Database\Eloquent_SoftDeletingScope.php_

public function apply(Builder $builder, Model $model){
  $builder->whereNull($model->getQualifiedDeletedAtColumn());
}

And what our apply method does is, add a where clause to the query builder to restrict soft deleted columns.

Happy coding :)

how-laravel-soft-deletes's People

Contributors

tkeer avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

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.