Забезпечення цілісності даних за допомогою м'якого видалення в Laravel для їх відновлення та дотримання вимог

Перекладено ШІ
Оригінал: Laravel News
Оновлено: 22 серпня, 2025
Використання функціональності м’якого видалення в Laravel відкриває нові горизонти в управлінні даними, дозволяючи зберігати повну історію записів навіть під час їх логічного видалення. Як впровадити цю потужну можливість у своїх проектах? Читайте далі, щоб дізнатися про налаштування м’якого видалення та його значення для забезпечення відповідності вимогам

Функція м'якого видалення в Laravel пропонує витончений підхід до управління даними, зберігаючи повну інформацію, водночас дозволяючи логічне видалення. Ця функція є важливою для відповідності вимогам, відновлення даних і підтримання зв'язності у складних додатках.

# Налаштування м'якого видалення

Додайте необхідний стовпець до бази даних за допомогою міграцій Laravel:

Schema::table('documents', function (Blueprint $table) {
    $table->softDeletes();
});

Увімкніть м'яке видалення у вашій Eloquent моделі:

<?php
  
namespace App\Models;
  
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
  
class Document extends Model
{
    use SoftDeletes;
  
    protected $fillable = ['title', 'content', 'category', 'author_id'];
}

Основні операції з м'яким видаленням працюють безперешкодно:

// М'яке видалення документа
$document->delete();
  
// Відновлення м'яко видаленого документа
$document->restore();
  
// Перевірка, чи документ м'яко видалений
if ($document->trashed()) {
    // Обробка стану м'якого видалення
}

Розгляньмо систему управління юридичними документами, де важливо зберігати повні звіти та забезпечувати дотримання нормативних вимог. Організаціям потрібно відстежувати зміни у життєвому циклі документів, зберігаючи історичні дані для судових справ і дотримання норм:

<?php
  
namespace App\Models;
  
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
  
class LegalDocument extends Model
{
    use SoftDeletes;
  
    protected $fillable = [
        'title', 'document_type', 'classification', 'retention_period',
        'author_id', 'department_id', 'version', 'content_hash'
    ];
  
    protected $casts = [
        'retention_expires_at' => 'datetime',
        'archived_at' => 'datetime',
    ];
  
    public function author(): BelongsTo
    {
        return $this->belongsTo(User::class, 'author_id');
    }
  
    public function department(): BelongsTo
    {
        return $this->belongsTo(Department::class);
    }
  
    public function revisions(): HasMany
    {
        return $this->hasMany(DocumentRevision::class);
    }
  
    public function annotations(): HasMany
    {
        return $this->hasMany(DocumentAnnotation::class);
    }
  
    protected static function booted()
    {
        static::deleting(function ($document) {
            // М'яке видалення пов'язаних анотацій
            $document->annotations()->delete();
  
            // Логування видалення для звіту
            ActivityLog::create([
                'action' => 'document_deleted',
                'subject_type' => LegalDocument::class,
                'subject_id' => $document->id,
                'user_id' => auth()->id(),
                'properties' => [
                    'document_title' => $document->title,
                    'classification' => $document->classification,
                    'deletion_reason' => request()->input('deletion_reason'),
                ],
            ]);
        });
  
        static::restoring(function ($document) {
            // Відновлення пов'язаних анотацій
            $document->annotations()->restore();
  
            // Логування відновлення
            ActivityLog::create([
                'action' => 'document_restored',
                'subject_type' => LegalDocument::class,
                'subject_id' => $document->id,
                'user_id' => auth()->id(),
                'properties' => [
                    'document_title' => $document->title,
                    'restored_from' => $document->deleted_at,
                ],
            ]);
        });
    }
  
    public function scopeAwaitingDestruction($query)
    {
        return $query->onlyTrashed()
                    ->where('retention_expires_at', '<', now())
                    ->where('deleted_at', '<', now()->subMonths(6));
    }
  
    public function isPermanentlyDeletable(): bool
    {
        return $this->trashed() &&
               $this->retention_expires_at < now() &&
               $this->deleted_at < now()->subMonths(6);
    }
}
  
class DocumentRevision extends Model
{
    use SoftDeletes;
  
    protected $fillable = [
        'document_id', 'version_number', 'change_summary',
        'content_diff', 'author_id'
    ];
  
    public function document(): BelongsTo
    {
        return $this->belongsTo(LegalDocument::class, 'document_id');
    }
}
  
class DocumentAnnotation extends Model
{
    use SoftDeletes;
  
    protected $fillable = [
        'document_id', 'author_id', 'annotation_text',
        'page_number', 'position_data'
    ];
  
    public function document(): BelongsTo
    {
        return $this->belongsTo(LegalDocument::class, 'document_id');
    }
}

Розширені можливості запитів дозволяють управляти складними сценаріями відповідності:

<?php
  
namespace App\Services;
  
use App\Models\LegalDocument;
use Illuminate\Support\Collection;
  
class DocumentComplianceService
{
    public function getActiveDocuments(): Collection
    {
        return LegalDocument::where('classification', '!=', 'archived')
                           ->orderBy('updated_at', 'desc')
                           ->get();
    }
  
    public function getDeletedDocumentsForAudit(): Collection
    {
        return LegalDocument::onlyTrashed()
                           ->with(['author', 'department'])
                           ->orderBy('deleted_at', 'desc')
                           ->get();
    }
  
    public function getAllDocumentsIncludingDeleted(): Collection
    {
        return LegalDocument::withTrashed()
                           ->with(['author', 'department', 'annotations'])
                           ->orderBy('created_at', 'desc')
                           ->get();
    }
  
    public function findDocumentForLitigation(int $documentId): ?LegalDocument
    {
        // Включити м'яко видалені документи для юридичного розслідування
        return LegalDocument::withTrashed()
                           ->with(['revisions.author', 'annotations'])
                           ->find($documentId);
    }
  
    public function getExpiredDocumentsAwaitingDestruction(): Collection
    {
        return LegalDocument::awaitingDestruction()
                           ->with(['author', 'department'])
                           ->get();
    }
  
    public function performRetentionCleanup(): array
    {
        $expiredDocuments = $this->getExpiredDocumentsAwaitingDestruction();
        $deletedCount = 0;
        $errors = [];
  
        foreach ($expiredDocuments as $document) {
            try {
                if ($document->isPermanentlyDeletable()) {
                    // Примусово видалити після закінчення терміну зберігання
                    $document->forceDelete();
                    $deletedCount++;
  
                    ActivityLog::create([
                        'action' => 'document_permanently_deleted',
                        'subject_type' => LegalDocument::class,
                        'subject_id' => $document->id,
                        'properties' => [
                            'document_title' => $document->title,
                            'retention_expired' => $document->retention_expires_at,
                            'soft_deleted_at' => $document->deleted_at,
                        ],
                    ]);
                }
            } catch (\Exception $e) {
                $errors[] = [
                    'document_id' => $document->id,
                    'error' => $e->getMessage(),
                ];
            }
        }
  
        return [
            'deleted_count' => $deletedCount,
            'errors' => $errors,
        ];
    }
  
    public function restoreDocumentWithRelated(int $documentId): bool
    {
        $document = LegalDocument::withTrashed()->find($documentId);
  
        if (!$document || !$document->trashed()) {
            return false;
        }
  
        // Відновлення документа та всіх пов'язаних анотацій
        $document->restore();
  
        return true;
    }
}
  
class DocumentController extends Controller
{
    public function __construct(
        private DocumentComplianceService $complianceService
    ) {}
  
    public function destroy(LegalDocument $document, Request $request)
    {
        $request->validate([
            'deletion_reason' => 'required|string|max:500',
        ]);
  
        $document->delete();
  
        return response()->json([
            'message' => 'Документ м'яко видалено успішно',
            'can_restore' => true,
            'deleted_at' => $document->fresh()->deleted_at,
        ]);
    }
  
    public function restore(int $documentId)
    {
        $restored = $this->complianceService->restoreDocumentWithRelated($documentId);
  
        if (!$restored) {
            return response()->json(['message' => 'Документ не знайдено або не видалено'], 404);
        }
  
        return response()->json(['message' => 'Документ успішно відновлено']);
    }
  
    public function audit()
    {
        return response()->json([
            'active_documents' => $this->complianceService->getActiveDocuments()->count(),
            'deleted_documents' => $this->complianceService->getDeletedDocumentsForAudit()->count(),
            'pending_destruction' => $this->complianceService->getExpiredDocumentsAwaitingDestruction()->count(),
        ]);
    }
}

М'яке видалення у Laravel забезпечує всебічне рішення для збереження цілісності даних, підтримуючи складні бізнес-потреби. Цей підхід дозволяє створювати надійні звіти, дотримуватись політики зберігання даних і забезпечувати можливості відновлення без шкоди для продуктивності застосунку