Skip to content

Instantly share code, notes, and snippets.

@afiqiqmal
Last active December 24, 2020 11:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save afiqiqmal/48e338de437c56a76ee771bb97e96749 to your computer and use it in GitHub Desktop.
Save afiqiqmal/48e338de437c56a76ee771bb97e96749 to your computer and use it in GitHub Desktop.
use Laravel SoftDeletes as boolean and avoid unique constraint problem
<?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 [];
}
}
<?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();
}
}
<?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();
}
}
<?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