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

0
Перекладено ШІ
Оригінал: 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 забезпечує всебічне рішення для збереження цілісності даних, підтримуючи складні бізнес-потреби. Цей підхід дозволяє створювати надійні звіти, дотримуватись політики зберігання даних і забезпечувати можливості відновлення без шкоди для продуктивності застосунку

Популярні

Logomark Logotype

Nuxt 3 + Laravel Sanctum: Просте та надійне рішення для автентифікації вашого SPA та API

У сучасній веб-розробці аутентифікація є ключовою для захисту додатків і даних користувачів. Дізнайтеся, як модуль nuxt-sanctum-authentication спростить інтеграцію між Nuxt 3 та Laravel Sanctum, забезпечуючи надійний і зручний спосіб реалізації аутентифікації для вашого проєкту

Logomark Logotype

Обробка геопросторових даних за допомогою Laravel Magellan

Ви готові відкрити нові горизонти у роботі з геопросторовими даними в Laravel? Дізнайтеся, як за допомогою PostGIS та пакету Laravel-Magellan можна легко зберігати, запитувати та маніпулювати інформацією про розташування, перетворюючи ваші проекти на вражаючі рішення у сфері картографії та геолокації!

Logomark Logotype

Перетворення даних у типобезпечні DTO за допомогою пакету Data Model

Досліджуйте новий пакет Data Model для PHP, який спрощує процес гідратації об'єктів без зайвих складнощів! Дізнайтеся, як впровадження типобезпечних об'єктів може революціонізувати ваш підхід до розробки, читаючи нашу статтю