How to keep foreign key restriction using softdelete?

Asked

Viewed 719 times

4

How can I maintain the integrity of my database when using softdelete?

Example: try to exclude a person which is linked to a account through a foreign key, in normal ways this will not be possible because it violates the restriction of foreign key, but using the softdelete is possible even because softdelete is a update.

Someone has a tip on how to keep foreign key restriction using softdelete?

Observing: I’m using the method

delete();
  • @geek.com I have some doubt, this is not what you wanted?

  • opa @Virgilionovic, very good your answer really helped a lot, but I solved it in a different way, I will propose as a response and wait for the community vote on which solution would be more interesting.

  • ok @geek.com, I’m also looking forward to seeing this other approach, I look forward to.

2 answers

4

You’re quite right in what you say, but this is one of the ways proposed by not to summarily exclude the data (permanently), standing up as historical in its groundwork, with the possibility of searching for old data, which is very important in my view.

I believe the demonstration of a minimal example of a relationship would be ideal for your understanding:

Models

Person:

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Pessoa extends Model
{
    use SoftDeletes;

    protected $primaryKey = "id";
    protected $fillable = array('nome');
    protected $table = "pessoas";
    public $timestamps = false;
    protected $dates = ['deleted_at'];

    public function contas()
    {
        return $this->hasMany(Conta::class, 'pessoa_id','id');
    }
}

Bill

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;

class Conta extends Model
{
    use SoftDeletes;

    protected $primaryKey = "id";
    protected $fillable = array('conta', 'pessoa_id');
    protected $table = "contas";
    public $timestamps = false;
    protected $dates = ['deleted_at'];

    public function pessoa()
    {
        return $this->belongsTo(Pessoa::class,'pessoa_id','id');
    }
}

If a command is executed delete() in the model Pessoa, as an example below:

Pessoa::find(1)->delete();

how is using the resource Softdeletes, the field is updated deleted_at with a current date and with that record is considered deletado. What I could understand is that you want to simulate what the bank would do by giving a erro because there are data in the relationship with the model Conta, that is, exists in the table of accounts data with the relationship of person of id equal 1. A similar mistake as this:

17:42:03 delete from people Where id = 1 Error Code: 1451. Cannot delete or update a Parent Row: a Foreign key Constraint fails (db1.contas, CONSTRAINT contas_pessoa_id_foreign FOREIGN KEY (pessoa_id) REFERENCES pessoas (id)

How could you then protect this record by using Softdeletes?

The only way is to do research in existing relationships with pessoas, in the example contas:

if (Conta::where('pessoa_id', 1)->count() == 0)
{
    Pessoa::find(1)->delete();
}

this is the way it has, ie, via programming, but, there may be a problem, if the table pessoas has various relations have to check all relations, to then give the command delete(). It is worth remembering that in contas may also be using the Softdeletes, and if by chance all accounts are automatically deleted, you can exclude the pessoa with this simple code. If you prefer you can give a message that this person cannot be excluded by having relations (pendencia):

if (Conta::where('pessoa_id', 1)->count() == 0)
{
    Pessoa::find(1)->delete();
} 
else
{
    //texto meramente ilustrativo, pode ser tomada outras decisões
    echo "Não pode ser excluido por ter relação com contas";
}

Observing: if all your models (Model) are using the Softdeletes, is ideal to use the command delete in relations (there is nothing implemented that resolves in a single way, at least until now), and only remembering that with this resource its base would be with the information history and with extra commands, this data can be recovered.

Example of how to recover a deleted record with Softdeletes

Recovering the Deleted Record:

$pessoa = Pessoa::withTrashed()
            ->where('id', 1)
            ->first();

Restoring the deleted item:

$pessoa->restore();

And finally, there is also a form of force the summary exclusion (really delete the database record) with the following method:

Permanently delete the record from the table:

$pessoa->forceDelete();

Observing: if there is a relation between the tables and records that makes the relation, at the time of exclusion the database will not allow and will send an error Foreign key Constraint

References:

0

I will summarize the solution I adopted in a few steps:

1. Trying to force physical delete (definitive), as there is foreign key restriction in the database the method executes a rollback in the transaction.

$pessoa->forceDelete();
DB::rollback();

2. Runs a logical delete (softdelete).

$pessoa = Pessoa::findOrFail($id);
$pessoa->delete();

3. Confirms the transaction.

DB::commit()

4. Retrieving the exceptions: Queryexception(Pdoexception),

catch (QueryException $e) {
    DB::rollback();
    $pessoa = array('mensagem' => 'impossível excluir esse dado');

Below the full code I hope it’s clear.

    use Illuminate\Database\QueryException;

    private function destroy($id)
    {

    DB::beginTransaction();

    try {

        $pessoa = Pessoa::findOrFail($id);

        //1
        $pessoa->forceDelete();
        DB::rollback();

        //2
        $pessoa = Pessoa::findOrFail($id);
        $pessoa->delete();

        //3
        DB::commit();

        //4
    } catch (QueryException $e) {
        DB::rollback();
        $pessoa = array('mensagem' => 'impossível excluir esse dado');
    }

        return $pessoa;
    }

Reference: Database Transactions

  • Why make an exception? because, do not check, so if there are records that may or may not be deleted definitively, and also now the doubt has arisen, whether to delete if there is no relation and if you have softdelete? I’m sorry, saying more is quite problematic this solution since there is an option to check without giving errors. Good do not take me to bad, are observations ... ;)

  • 1

    Calm @Virgilionovic, here are my ideas to try to give you better what I thought:

    1. Why make an exception? This is a "trick", just to generate an exception and then treat it. 2. Why, not check, then if there are records that can or can’t be deleted definitively? At this point I thought about performance: imagine doing a Count() in many entities in search of relationships, I imagine that maybe so was faster, maybe I’m wrong. 3. If you want to delete if you have no relation and if you have it in softdelete? Exactly, I will keep softdelete.
  • Then, item 2) can be done optimally by checking 1 relationship at times and if find items return false and make a softdelete (can be optimized, mainly by the precedence of the tables), and in an entity does not have many relationships if they are mounted in the 3FN correctly. the 1 sincerely will not comment, the 3 I agree. Well, it has how to do better without try catch which should be used when running away from developer action, but, all right @geekcom vlw.

  • 1

    Well, it’s an alternative, I won’t say it’s the best nor the only one, about what you said in item 2, I agree, I’ll take a few moments in this part to try something better, so I’ll develop I’ll update the answer.

Browser other questions tagged

You are not signed in. Login or sign up in order to post.