Руководство по Soft Delete в Laravel

Источник: «A Guide to Soft Deletes in Laravel»
В веб-приложениях могут возникать ситуации, когда необходимо разрешить пользователям удалять данные без их окончательного удаления из базы данных. Например, можно разрешить администратору удалить учётную запись другого пользователя, но при этом сохранить его данные в базе данных на случай ошибки администратора. Это позволит администратору при необходимости восстановить удалённую учётную запись. Именно в этом случае полезно мягкое удаление — soft delete.

В этой статье мы рассмотрим, что такое soft delete, каковы преимущества и недостатки его использования, а также как применять его в приложении Laravel. Мы рассмотрим, как подготовить модель и базу данных к soft delete, как удалять и восстанавливать модели, как удалять модели навсегда и как делать запросы к мягко удаляемым моделям. Затем мы рассмотрим, как тестировать модели с soft delete и как избежать распространённых ошибок при использовании мягкого удаления с помощью DB фасада.

К концу статьи вы должны хорошо понимать, что такое soft delete и как начать использовать его в своих приложениях Laravel.

Что такое soft delete

В самом простом виде soft delete — это способ удаления данных в приложении без их удаления из базы данных. Вместо этого для строки в базе данных устанавливается флаг, указывающий на то, что она была удалена. В Laravel таким флагом является столбец временной метки deleted_at.

Когда выполняется запрос на получение данных из базы, мы можем определить, были ли эти данные "удалены", проверив значение столбца deleted_at. Если значение равно null, то данные не были удалены. Если значение не равно null и имеет временную метку, то данные были удалены.

Вот две ситуации, в которых может потребоваться использование soft delete:

Простой способ представить себе soft delete — это как "корзина" для вашего приложения. Когда вы используете свой компьютер, вы можете удалять файлы, и они отправляются в вашу "корзину". Таким образом, они "удаляются" в том смысле, что к ним нельзя получить доступ из их первоначального местоположения, но они все ещё там. Затем можно выбрать, удалить ли их из "корзины" навсегда или восстановить в исходное место.

Однако важно отметить, что если приложение использует soft delete, это не означает, что оно также должно поддерживать восстановление данных или их безвозвратное удаление. Soft delete может быть добавлена разработчиком для собственных целей, а не для того, чтобы сделать её функцией приложения, о которой знают пользователи.

Преимущества использования soft delete

Использование soft delete в приложении Laravel имеет ряд преимуществ. Давайте рассмотрим некоторые из них.

Восстановление данных

Как мы уже вкратце упоминали, возможность восстановления "удалённых" данных является огромным преимуществом использования soft delete.

Все мы, совершаем ошибки. Поэтому неизбежно, что в какой-то момент пользователь случайно удалит данные. Если данные были удалены безвозвратно, то восстановить их будет крайне сложно. Однако если данные были удалены мягким способом, их можно восстановить, если, конечно, в приложении есть функция "восстановления".

Аудит данных

В зависимости от типа создаваемого приложения и хранимых данных может потребоваться отслеживание данных даже после их удаления. Это может быть сделано для аналитических целей, например, для отслеживания статистики использования. Кроме того, это может потребоваться для соблюдения правовых или нормативных требований, поскольку данные сохраняются даже тогда, когда они больше не нужны пользователю. Например, представьте, что у вас есть сайт интернет-магазина с панелью администратора, с помощью которой владелец магазина может управлять товарами и продажами. Если клиенты попросят удалить их учётные записи, то, возможно, придётся сохранить некоторые данные о них (например, заказы) для целей налогообложения, а остальные данные удалить.

Двухэтапное удаление

Для некоторых типов данных может потребоваться двухэтапный процесс удаления. Например, у вас в приложении есть несколько ролей: "администратор", "менеджер" и "пользователь".

Возможно, вы захотите разрешить менеджерам удалять пользователей. При удалении пользователя менеджером пользователь фактически удаляется из приложения, но его данные остаются в базе данных. После этого пользователь-администратор должен утвердить окончательное удаление пользователя из базы данных или восстановить его. Такой подход может быть полезен, когда необходимо быть абсолютно уверенным в том, что данные пользователя должны быть удалены, и использовать несколько пользователей для подтверждения удаления.

Недостатки использования soft delete

Хотя использование мягкого удаления имеет много преимуществ, есть и недостатки. Рассмотрим некоторые из них.

Увеличение объёма базы данных

Как мы уже говорили, записи с мягким удалением не удаляются из базы данных. Вместо этого они помечаются как удалённые с помощью определённого столбца в таблице. Это означает, что данные по-прежнему хранятся в базе данных.

В результате объём данных, хранящихся в базе данных, скорее всего, будет увеличиваться гораздо быстрее, чем при постоянном удалении данных. Хотя для небольших приложений это не является проблемой, для крупных приложений с большим объёмом данных это может стать проблемой и привести к увеличению затрат на инфраструктуру базы данных.

Повышение вероятности случайного запроса "удалённых" данных

Далее в этой статье мы рассмотрим распространённую "ловушку", с которой, по моим наблюдениям, сталкиваются многие разработчики. Это когда разработчик пытается запросить таблицу в базе данных, в которой разрешено мягкое удаление, но забывает включить проверку поля флага мягкого удаления. В результате разработчик может случайно включить в запрос "удалённые" данные, что может привести к неожиданным результатам.

Поэтому, хотя soft delete может быть полезным, оно может добавить дополнительный уровень сложности (хотя и небольшой) в ваше приложение, о котором необходимо помнить, особенно при написании более сложных запросов к базе данных.

Проблемы конфиденциальности данных

Попытка найти баланс между возможностью мягкого удаления и соблюдением законов о конфиденциальности данных может оказаться непростой задачей. Рассмотрим это на примере. Представьте себе, что пользователь, находящийся в Европейском союзе (ЕС), имеет учётную запись в веб-приложении, которое позволяет ему удалить свою учётную запись. Однако приложение может также предоставлять пользователю возможность восстановить удалённую учётную запись, если он передумает. Это означает, что приложению необходимо сохранять данные пользователя в базе данных даже после удаления учётной записи. Однако сохранение данных в базе может привести к тому, что приложение нарушит правила Общего регламента по защите данных (GDPR), который является законом о конфиденциальности данных в ЕС. У пользователя может сложиться впечатление, что его данные полностью удалены из системы, но на самом деле они все ещё там.

Если необходимо сохранить данные пользователя (за исключением персональной информации), возможно, удастся соблюсти правила путём их обезличивания. Например, можно заменить имя пользователя, его адрес электронной почты, номер телефона, адрес и т.д. на ряд случайных символов. Однако это не всегда возможно, особенно если пользователь впоследствии захочет восстановить свою учётную запись и продолжить работу с приложением, используя старые, правильные данные.

Важно отметить, что данная статья в блоге не является юридической консультацией по вопросам конфиденциальности и безопасности данных. Если вы сомневаетесь в чем-либо, связанном с конфиденциальностью или безопасностью данных, вам следует обратиться за юридической консультацией к специалисту, который лучше разбирается в этой области.

Не заменяет журнал аудита

Создание в базе данных мягко удаляемых данных может быть быстрым и полезным способом отслеживания удалённых данных. Например, в контексте модели в приложении Laravel, использующей столбцы по умолчанию, мы можем использовать следующие столбцы:

С помощью этих столбцов можно сформировать краткую временную шкалу жизненного цикла модели. Это может быть полезно для простых целей отладки и для получения разработчиком краткого представления о модели. В зависимости от места использования она также может быть использована для представления полезной информации пользователю. Однако это не заменяет полноценный журнал аудита.

Если ваше приложение требует более глубокого отслеживания данных, то следует рассмотреть возможность использования журнала аудита. Хотя наши поля created_at, updated_at и deleted_at могут сообщить нам время наступления этих событий, у нас нет никакого дополнительного контекста. Они не могут помочь нам ответить ни на один из этих вопросов:

В результате использования журнала аудита мы с большей вероятностью сможем ответить на эти вопросы. Однако это не означает, что нельзя использовать soft delete и журнал аудита вместе. Мягкое удаление может быть полезным по всем тем причинам, которые мы уже рассмотрели, а журнал аудита может использоваться для создания более подробного контекста данных.

Подготовка модели и базы данных

Для того чтобы приступить к использованию soft delete в приложении Laravel, необходимо подготовить модель с возможностью мягкого удаления и её таблицу в базе данных.

Laravel предоставляет удобный трейт Illuminate\Database\Eloquent\SoftDeletes, позволяющий добавить его к любой модели, которую вы хотите сделать мягко удаляемой. Этот трейт добавляет в модель несколько полезных методов, например, следующие:

Он также регистрирует некоторые события модели, возникающие при мягком удалении, принудительном удалении и восстановлении модели.

Ещё одно огромное преимущество использования этого трейта заключается в том, что он регистрирует глобальную область видимости (с помощью класса Illuminate\Database\Eloquent\SoftDeletingScope, входящего в комплект поставки), которая автоматически исключает из запросов все мягко удалённые модели. Этот класс также регистрирует некоторые макросы запросов, которые можно использовать для включения моделей с мягким удалением в запросы. Более подробно эти макросы мы рассмотрим далее в этой статье.

Теперь, когда у нас есть некоторое представление о трейте SoftDeletes, давайте добавим его в нашу модель. Для целей этой статьи представим, что мы создаём приложение для ведения блога и у нас есть модель Author, которую мы хотим сделать мягко удаляемой. Мы добавим трейт SoftDeletes в модель следующим образом:

// app/Models/User.php
declare(strict_types=1);

namespace App\Models;

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

final class Author extends Model
{
use SoftDeletes;

// ...
}

Теперь, когда мы добавили в модель функцию soft delete, необходимо подготовить таблицу базы данных, чтобы можно было отслеживать, была ли модель мягко удалена. Как мы уже говорили, в Laravel для этого используется timestamp колонка deleted_at. Поэтому добавим этот столбец в нашу таблицу authors с помощью миграции.

Мы создадим новую миграцию, выполнив следующую команду artisan:

php artisan make:migration add_deleted_at_to_authors_table --table=authors

Эта команда создаст новый класс миграции с путём к файлу, например database/migrations/2023_07_10_111209_add_deleted_at_to_authors_table.php. Давайте обновим миграцию, чтобы добавить новый столбец deleted_at. Для этого мы можем воспользоваться удобным методом softDeletes, который доступен в классе Blueprint, предоставляемом Laravel. Аналогичным образом мы можем использовать метод dropSoftDeletes для удаления столбца.

Миграция должна выглядеть примерно так:

declare(strict_types=1);

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

return new class extends Migration
{
public function up()
{
Schema::table('authors', function (Blueprint $table) {
$table->softDeletes();
});
}

public function down()
{
Schema::table('authors', function (Blueprint $table) {
$table->dropSoftDeletes();
});
}
};

Теперь мы можем запустить миграцию с помощью команды migrate artisan, чтобы добавить столбец deleted_at в таблицу authors:

php artisan migrate

Soft delete и восстановление моделей

Теперь ваша модель и база данных должны быть готовы к использованию мягкого удаления. Давайте рассмотрим, как можно выполнять soft delete и восстановление моделей.

Для мягкого удаления модели можно вызвать метод delete на экземпляре модели. Поскольку модель использует трейт SoftDeletes, метод delete автоматически установит в столбце deleted_at текущую дату и время, а не удалит строку из базы данных. В качестве примера, для soft delete модели Author (которая, как мы предполагаем, имеет идентификатор 1), можно сделать следующее:

$author = Author::find(1);

$author->delete();

Теперь модель Author будет мягко удалена. Следует отметить, что вызов функции delete для уже удалённой модели приведёт к обновлению столбца deleted_at на текущую дату и время. Хотя это может не вызвать никаких проблем в вашем приложении, об этом стоит помнить, если вам необходимо сохранить запись о времени первоначального удаления модели, например, для аналитических целей.

Если мы хотим восстановить модель (чтобы она больше не была мягко удалена), то можем вызвать метод restore для экземпляра модели:

$author->restore();

Если необходимо проверить, является ли данная модель мягко удалённой, можно использовать метод trashed для экземпляра модели. Если модель является мягко удалённой, то он вернёт true, в противном случае — false. Приведём пример:

$author = Author::find(1);

$author->trashed(); // false

$author->delete();

$author->trashed(); // true

Окончательное удаление моделей

Если необходимо окончательно удалить модель из базы данных (иногда это называют "жёстким удалением"), можно использовать метод forceDelete для модели. Это можно сделать как для мягко удалённых, так и для не мягко удалённых моделей следующим образом:

$author = Author::find(1);

$author->forceDelete();

Важно помнить, что после вызова этого метода запись будет навсегда удалена из базы данных и не сможет быть восстановлена.

Запрос к моделям с мягким удалением

Как мы уже упоминали, трейт SoftDeletes регистрирует глобальную область видимости, которая по умолчанию автоматически исключает все мягко удалённые модели из запросов. Это означает, что при выполнении таких запросов, как Author::find(1), Author::all() или Author::where('name', 'John')->get(), мягко удалённые модели не будут возвращены.

В большинстве случаев это желаемое поведение. Однако бывают случаи, когда необходимо включить в запрос мягко удалённые модели или возвращать только мягко удалённые модели. В Laravel зарегистрировано несколько макросов запросов, которые можно использовать для достижения этой цели. Давайте их рассмотрим.

Включая мягко удалённые модели

Бывают случаи, когда требуется получить все модели в таблице базы данных, независимо от того, были ли они мягко удалены. В качестве примера можно привести запрос на анализ статистики использования учётной записи пользователя.

Чтобы включить в запрос мягко удалённые модели, можно использовать метод withTrashed для экземпляра конструктора запроса. Например, если необходимо получить все модели Author, включая мягко удалённые, можно написать следующий запрос:

$authors = Author::withTrashed()->get();

Аналогичным образом, если необходимо получить одну модель, которая может быть или не быть мягко удалённой, можно использовать метод withTrashed следующим образом:

$author = Author::withTrashed()->find(1);

Только мягко удалённые модели

Бывают случаи, когда необходимо получить из базы данных только мягко удалённые модели и исключить все не удалённые модели. Например, это может потребоваться при создании функции "корзины" для приложения, позволяющей пользователям восстанавливать или безвозвратно удалять мягко удалённые модели.

Для получения только мягко удалённых моделей можно использовать метод onlyTrashed для экземпляра конструктора запросов. Например, если необходимо получить только мягко удалённые модели, можно написать следующий запрос:

$authors = Author::onlyTrashed()->get();

Аналогично, если необходимо получить только одну мягко удалённую модель, можно использовать метод onlyTrashed следующим образом:

$author = Author::onlyTrashed()->find(1);

Используя фасад DB

До сих пор наши примеры кода были посвящены использованию функциональности, предоставляемой трейтом SoftDeletes, для запросов к мягко удалённым моделям. Однако могут возникнуть ситуации, когда необходимо написать запрос с использованием фасада DB. Например, если вы создаёте подробный отчёт для своего приложения, используя более сложный запрос, то вам может понадобиться следующее.

Если вы решите запросить модель с мягким удалением с помощью фасада DB, то вам следует знать об одной распространённой "ловушке". Я видел, как с этим сталкивались многие разработчики, в том числе и я сам, поэтому об этом стоит упомянуть.

Трейт SoftDeletes регистрирует область видимости запроса SoftDeletingScope в мягко удаляемой модели, а значит, может быть применён к объекту Illuminate\Database\Eloquent\Builder, используемому при построении запроса Eloquent (как показано выше). Однако, когда вы используете фасад DB, вы используете объект Illuminate\Database\Query\Builder, а не Illuminate\Database\Eloquent\Builder.

Таким образом, SoftDeletingScope не применяется к запросу, и если написать запрос, используя фасад DB, то в результатах будут возвращены мягко удалённые модели. Это означает, что необходимо вручную исключить из запроса мягко удалённые модели.

Рассмотрим базовый пример на эту тему. Представьте, что есть следующий запрос, использующий фасад DB для получения всех авторов:

$authors = DB::table('authors')->get();

Если выполнить приведённый выше код, то из базы данных будут извлечены как мягко удалённые, так и не удалённые модели. Если бы мы захотели обновить запрос, чтобы исключить мягко удалённые модели, мы могли бы сделать следующее:

$authors = DB::table('authors')
->whereNull('deleted_at')
->get();

Как видно из примера кода, мы использовали метод whereNull, чтобы исключить из запроса все мягко удалённые модели.

Важно не забывать добавлять это условие при построении запросов. Отличным способом проверки правильности составления запросов является качественный тест, утверждающий, что в запросе не возвращаются мягко удалённые модели. Более подробно об этом мы расскажем далее в статье.

Мягко удалённые модели и привязка модели к маршруту

В Laravel при определении маршрутов можно использовать "привязку модели к маршруту" для автоматической инъекции экземпляра модели в метод контроллера. Это позволяет легко получить экземпляр модели из базы данных и получить его готовым к использованию непосредственно в контроллере. Например, если необходимо получить из базы данных экземпляр модели Author и сделать его доступным в контроллере, то можно определить следующий маршрут:

Route::get('/authors/{author}', [AuthorController::class, 'show']);

В методе show контроллера AuthorController мы можем указать модель Author и внедрить её в метод следующим образом:

public function show(Author $author)
{
// ...
}

Если пользователь перейдёт по адресу /authors/1, то переменная $author будет содержать экземпляр модели Author для автора с идентификатором 1. Это очень удобная функция!

Однако по умолчанию привязка модели к маршруту игнорирует мягко удалённые модели. Это означает, что если вы перейдёте по адресу /authors/1, но автор с идентификатором 1 будет удалён, то вы получите HTTP-ответ 404.

В большинстве случаев именно такое поведение является желательным. Однако могут возникнуть ситуации, когда необходимо включить в привязку мягко удалённые модели. Например, необходимо создать панель администрирования, позволяющую просматривать и восстанавливать мягко удалённые модели. Для этого можно использовать метод withTrashed в определении привязки модели к маршруту. Например, мы можем обновить определение маршрута следующим образом:

Route::get('/authors/{author}', [AuthorController::class, 'show'])->withTrashed();

Важно отметить, что при этом в связке будут использоваться не только мягко удалённые модели. Вместо этого будут использоваться как мягко удалённые, так и не удалённые модели. Поэтому, если вы хотите включить только мягко удалённые модели, вам необходимо добавить в контроллер проверку на то, что модель является мягко удалённой. Например, мы можем обновить метод show в контроллере AuthorController следующим образом:

public function show(Author $author)
{
// Если автор не является мягко удалённым, возвращается ответ 404.
if (!$author->trashed()) {
abort(404);
}

// Продолжаем в обычном режиме...
}

Тестирование soft delete

Как и в любой другой части приложения, важно не забывать тестировать свой код. Это поможет убедиться в том, что код работает так, как ожидается, и снизит вероятность появления ошибок при внесении изменений в будущем.

Утверждение, является ли модель мягко удалённой

При тестировании моделей с мягким удалением часто возникает необходимость проверить, является ли модель мягко удалённой. В Laravel это можно сделать несколькими способами.

Во-первых, как мы уже говорили, для проверки мягкого удаления модели можно использовать метод trashed на модели. Например, представим, что у нас есть маршрут и контроллер (доступный по DELETE /authors/{author} с именем маршрута authors.destroy), позволяющий пользователю мягко удалять модель. Мы можем написать следующий тест для подтверждения того, что автор мягко удалён:

use App\Models\Author;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function author_can_be_soft_deleted(): void
{
$author = Author::factory()->create();

$this->delete(route('authors.destroy', $author))
->assertOk();

$this->assertTrue($author->fresh()->trashed());
}

В качестве альтернативы можно использовать вспомогательный метод assertSoftDeleted, предоставляемый Laravel. Мы можем переписать приведённый выше тест для использования этого метода следующим образом:

use App\Models\Author;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function author_can_be_soft_deleted(): void
{
$author = Author::factory()->create();

$this->delete(route('authors.destroy', $author));
->assertOk();

$this->assertSoftDeleted($author);
}

Аналогично двум приведённым выше подходам, если мы хотим подтвердить, что модель не является мягко удалённой, мы можем использовать вспомогательный метод assertNotSoftDeleted, предоставляемый Laravel. Например, у нас может быть маршрут и контроллер (доступный по POST /authors/{author}/restore с именем маршрута authors.restore), используемый для восстановления мягко удалённых авторов. Если бы мы хотели написать тест для проверки того, что модель не является мягко удалённой, мы могли бы написать следующее:

use App\Models\Author;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function author_can_be_restored(): void
{
$author = Author::factory()
->trashed()
->create();

$this->post(route('authors.restore', $author))
->assertOk();

$this->assertNotSoftDeleted($author);
}

Возможно, вы заметили, что в приведённом выше тесте мы вызвали метод trashed на фабрике модели Author. Этот метод предоставляется Laravel и позволяет нам создавать мягко удаляемые модели. Это может быть очень полезно, когда необходимо быстро создать мягко удаляемую модель в тестах.

Если необходимо проверить, что модель была удалена из базы данных навсегда, можно воспользоваться вспомогательным методом assertModelMissing, предоставляемым Laravel. Например, если бы у нас был маршрут и контроллер (доступный по DELETE /authors/{author}/force с именем маршрута authors.force), которые мы могли бы использовать для окончательного удаления мягко удалённой модели, мы могли бы написать следующий тест для подтверждения того, что модель была окончательно удалена:

use App\Models\Author;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function soft_author_can_be_permanently_deleted(): void
{
$author = Author::factory()
->trashed()
->create();

$this->delete(route('authors.force', $author))
->assertOk();

$this->assertModelMissing($author);
}

Если маршрут позволяет удалять только мягко удалённые модели и возвращает HTTP ответ 404, если попытаться удалить не мягко удалённую модель. То можно использовать assertModelExists для написания следующего теста, подтверждающего, что модель не удалена окончательно:

use App\Models\Author;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function non_soft_author_cannot_be_permanently_deleted(): void
{
$author = Author::factory()->create();

$this->delete(route('authors.force', $author))
->assertNotFound();

$this->assertModelExists($author);
}

Утверждение о включении или исключении из запросов мягко удалённых моделей

Скорее всего, вам потребуется написать тесты, которые будут подтверждать, что модели с мягким удалением включены или исключены из запросов. Подобные тесты могут обеспечить спокойствие и уверенность в том, что вы случайно не включаете в запросы мягко удалённые модели.

Рассмотрим в качестве примера следующий базовый метод контроллера, возвращающий представление, содержащее всех авторов (за исключением мягко удалённых):

class AuthorController extends Controller
{
public function index()
{
return view('authors.index', [
'authors' => Author::all(),
]);
}
}

Чтобы убедиться в том, что мы случайно не включаем в запрос мягко удалённых авторов, можно написать следующий тест:

use App\Models\Author;
use Illuminate\Database\Eloquent\Collection;
use PHPUnit\Framework\Attributes\Test;
use Tests\TestCase;

#[Test]
public function authors_view_is_returned(): void
{
// Создайте двух не мягко удалённых авторов.
// Они должны быть включены в результаты.
$authors = Author::factory()
->count(2)
->create();

// Создайте мягко удалённого автора.
// Они не должны быть включены в результаты.
Author::factory()
->trashed()
->create();

$this->get(route('authors.index'))
->assertOk()
->assertViewIs('authors.index')
->assertViewHas(
key: 'authors',
value: fn (Collection $authors): bool =>
$authors->pluck('id')->toArray() === [
$authors[0]->id,
$authors[1]->id,
]
);
}

Как видно из теста, мы явно проверяем, что Collection, передаваемый представлению, включает только двух не мягко удалённых авторов. Таким образом, мы можем быть уверены, что случайно не включим в запрос мягко удалённых авторов.

На самом деле, такой общий подход к созданию модели, не удовлетворяющей одному из ограничений запроса, очень удобен при тестировании любого типа запросов. Его можно использовать во всех тестах приложения, чтобы гарантировать, что ограничения запроса работают так, как ожидается, и что вы возвращаете только те модели, которые ожидаете получить. Это может быть полезно при тестировании сложных запросов с большим количеством условных ограничений.

Заключение

Эта статья должна дать представление, что такое soft delete, а также о преимуществах и недостатках его использования. Теперь вы должны уметь использовать soft delete в своих приложениях Laravel и быть уверенными в том, что сможете их протестировать. Вы также должны знать о распространённых ошибках, которых следует избегать при использовании soft delete и написании запросов к базе данных с использованием фасада DB.

Дополнительные материалы

Предыдущая Статья

Новое в Symfony 6.4: Обработчик подпроцессов

Следующая Статья

Новое в Symfony 6.4: Маршруты на основе FQCN