Last active
December 24, 2020 11:45
-
-
Save afiqiqmal/48e338de437c56a76ee771bb97e96749 to your computer and use it in GitHub Desktop.
use Laravel SoftDeletes as boolean and avoid unique constraint problem
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Models\Traits; | |
use App\Observers\AvoidDuplicateConstraintSoftDeleteObserver; | |
trait AvoidDuplicateConstraintSoftDelete | |
{ | |
public static function bootAvoidDuplicateConstraintSoftDelete() | |
{ | |
static::observe(app(AvoidDuplicateConstraintSoftDeleteObserver::class)); | |
} | |
public function getDuplicateAvoidColumns() : array | |
{ | |
return []; | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Observers; | |
use Illuminate\Database\Eloquent\Model; | |
class AvoidDuplicateConstraintSoftDeleteObserver | |
{ | |
private const DELIMITER = '--'; | |
public function restoring(Model $model) | |
{ | |
if ($model->trashed()) { | |
foreach ($model->getDuplicateAvoidColumns() as $column) { | |
if ($value = (explode(self::DELIMITER, $model->{$column})[1] ?? null)) { | |
$model->{$column} = $value; | |
} | |
} | |
} | |
} | |
public function deleted(Model $model) | |
{ | |
foreach ($model->getDuplicateAvoidColumns() as $column) { | |
$model->{$column} = time().self::DELIMITER.$model->{$column}; | |
} | |
$model->save(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Models\Traits; | |
use App\Models\Scopes\SoftDeletingBooleanScope; | |
use Illuminate\Database\Eloquent\SoftDeletes; | |
/** | |
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withTrashed() | |
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder onlyTrashed() | |
* @method static static|\Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Query\Builder withoutTrashed() | |
*/ | |
trait SoftDeletesBoolean | |
{ | |
use SoftDeletes, AvoidDuplicateConstraintSoftDelete; | |
/** | |
* Indicates if the model is currently force deleting. | |
* | |
* @var bool | |
*/ | |
protected $forceDeleting = false; | |
/** | |
* Boot the soft deleting trait for a model. | |
* | |
* @return void | |
*/ | |
public static function bootSoftDeletes() | |
{ | |
} | |
/** | |
* Boot the soft deleting trait for a model. | |
* | |
* @return void | |
*/ | |
public static function bootSoftDeletesBoolean() | |
{ | |
static::addGlobalScope(new SoftDeletingBooleanScope()); | |
} | |
/** | |
* Force a hard delete on a soft deleted model. | |
* | |
* @return bool|null | |
*/ | |
public function forceDelete() | |
{ | |
$this->forceDeleting = true; | |
$deleted = $this->delete(); | |
$this->forceDeleting = false; | |
return $deleted; | |
} | |
/** | |
* Perform the actual delete query on this model instance. | |
* | |
* @return mixed | |
*/ | |
protected function performDeleteOnModel() | |
{ | |
if ($this->forceDeleting) { | |
$this->exists = false; | |
return $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey())->forceDelete(); | |
} | |
return $this->runSoftDelete(); | |
} | |
/** | |
* Perform the actual delete query on this model instance. | |
* | |
* @return void | |
*/ | |
protected function runSoftDelete() | |
{ | |
$query = $this->newQueryWithoutScopes()->where($this->getKeyName(), $this->getKey()); | |
$time = $this->freshTimestamp(); | |
$columns = [ | |
$this->getIsDeletedColumn() => 1, | |
$this->getDeletedAtColumn() => $this->fromDateTime($time) | |
]; | |
$this->{$this->getIsDeletedColumn()} = 1; | |
$this->{$this->getDeletedAtColumn()} = $time; | |
if ($this->timestamps && ! is_null($this->getUpdatedAtColumn())) { | |
$this->{$this->getUpdatedAtColumn()} = $time; | |
$columns[$this->getUpdatedAtColumn()] = $this->fromDateTime($time); | |
} | |
$query->update($columns); | |
} | |
/** | |
* Restore a soft-deleted model instance. | |
* | |
* @return bool|null | |
*/ | |
public function restore() | |
{ | |
// If the restoring event does not return false, we will proceed with this | |
// restore operation. Otherwise, we bail out so the developer will stop | |
// the restore totally. We will clear the deleted timestamp and save. | |
if ($this->fireModelEvent('restoring') === false) { | |
return false; | |
} | |
$this->{$this->getIsDeletedColumn()} = 0; | |
$this->{$this->getDeletedAtColumn()} = null; | |
// Once we have saved the model, we will fire the "restored" event so this | |
// developer will do anything they need to after a restore operation is | |
// totally finished. Then we will return the result of the save call. | |
$this->exists = true; | |
$result = $this->save(); | |
$this->fireModelEvent('restored', false); | |
return $result; | |
} | |
/** | |
* Determine if the model instance has been soft-deleted. | |
* | |
* @return bool | |
*/ | |
public function trashed() | |
{ | |
return (bool)$this->{$this->getIsDeletedColumn()}; | |
} | |
/** | |
* Register a restoring model event with the dispatcher. | |
* | |
* @param \Closure|string $callback | |
* @return void | |
*/ | |
public static function restoring($callback) | |
{ | |
static::registerModelEvent('restoring', $callback); | |
} | |
/** | |
* Register a restored model event with the dispatcher. | |
* | |
* @param \Closure|string $callback | |
* @return void | |
*/ | |
public static function restored($callback) | |
{ | |
static::registerModelEvent('restored', $callback); | |
} | |
/** | |
* Determine if the model is currently force deleting. | |
* | |
* @return bool | |
*/ | |
public function isForceDeleting() | |
{ | |
return $this->forceDeleting; | |
} | |
/** | |
* Get the name of the "deleted at" column. | |
* | |
* @return string | |
*/ | |
public function getIsDeletedColumn() | |
{ | |
return defined('static::IS_DELETED') ? static::IS_DELETED : 'is_deleted'; | |
} | |
/** | |
* Get the fully qualified "deleted at" column. | |
* | |
* @return string | |
*/ | |
public function getQualifiedIsDeletedColumn() | |
{ | |
return $this->getTable().'.'.$this->getIsDeletedColumn(); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
use Illuminate\Database\Eloquent\Builder; | |
use Illuminate\Database\Eloquent\Model; | |
use Illuminate\Database\Eloquent\SoftDeletingScope; | |
class SoftDeletingBooleanScope extends SoftDeletingScope | |
{ | |
/** | |
* All of the extensions to be added to the builder. | |
* | |
* @var array | |
*/ | |
protected $extensions = ['Restore', 'WithTrashed', 'WithoutTrashed', 'OnlyTrashed']; | |
/** | |
* Apply the scope to a given Eloquent query builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @param \Illuminate\Database\Eloquent\Model $model | |
* @return void | |
*/ | |
public function apply(Builder $builder, Model $model) | |
{ | |
$builder->where($model->getQualifiedIsDeletedColumn(), 0); | |
} | |
/** | |
* Extend the query builder with the needed functions. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return void | |
*/ | |
public function extend(Builder $builder) | |
{ | |
foreach ($this->extensions as $extension) { | |
$this->{"add{$extension}"}($builder); | |
} | |
$builder->onDelete(function (Builder $builder) { | |
$column = $this->getIsDeletedColumn($builder); | |
return $builder->update([ | |
$column => 1, | |
]); | |
}); | |
} | |
/** | |
* Get the "deleted at" column for the builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return string | |
*/ | |
protected function getIsDeletedColumn(Builder $builder) | |
{ | |
if (count((array) $builder->getQuery()->joins) > 0) { | |
return $builder->getModel()->getQualifiedIsDeletedColumn(); | |
} | |
return $builder->getModel()->getIsDeletedColumn(); | |
} | |
/** | |
* Add the restore extension to the builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return void | |
*/ | |
protected function addRestore(Builder $builder) | |
{ | |
$builder->macro('restore', function (Builder $builder) { | |
$builder->withTrashed(); | |
return $builder->update([$builder->getModel()->getIsDeletedColumn() => 0]); | |
}); | |
} | |
/** | |
* Add the with-trashed extension to the builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return void | |
*/ | |
protected function addWithTrashed(Builder $builder) | |
{ | |
$builder->macro('withTrashed', function (Builder $builder) { | |
return $builder->withoutGlobalScope($this); | |
}); | |
} | |
/** | |
* Add the without-trashed extension to the builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return void | |
*/ | |
protected function addWithoutTrashed(Builder $builder) | |
{ | |
$builder->macro('withoutTrashed', function (Builder $builder) { | |
$model = $builder->getModel(); | |
$builder->withoutGlobalScope($this)->where( | |
$model->getQualifiedIsDeletedColumn(),0 | |
); | |
return $builder; | |
}); | |
} | |
/** | |
* Add the only-trashed extension to the builder. | |
* | |
* @param \Illuminate\Database\Eloquent\Builder $builder | |
* @return void | |
*/ | |
protected function addOnlyTrashed(Builder $builder) | |
{ | |
$builder->macro('onlyTrashed', function (Builder $builder) { | |
$model = $builder->getModel(); | |
$builder->withoutGlobalScope($this)->where( | |
$model->getQualifiedIsDeletedColumn(), 1 | |
); | |
return $builder; | |
}); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment