Приветствую!
Мне кажется вы чуток неправильно следуете принципам ДДД.
Точнее вы неправильно проектируете свой код.
Если вы пишите по ДДД то вам не надо задумываться об полях в БД или о других инфраструктурных вещах.
Вам надо писать домен таким каким он есть в рамках вашего понимания.
Исходя из вашего текста я бы это написал это примерно как в примере что я привел. Хотя это было все написано на коленке без анализа юз кэйсов пользователя, без анализа домена и так далее. Так что трактируйте мой код как пример, или как черновик.
<?php
/**
* "Domain/Directories/File.php"
*/
final class File
{
/**
* Я использую название файла как его уникальный идентификатор.
* Надо учитывать что этот идентификатор уникален в рамках одной директории (папки).
* В случае если это сложно отслеживать можно использовать UUID как ИД и атрибут $name как допольнительнй.
*
* @var string
*/
private $name;
/**
* @var bool
*/
private $hidden = false;
/**
* @param string $name
*/
public function __construct(string $name)
{
$this->name = $name;
}
/**
* Скрываем временно файл.
*/
public function hide() : void
{
$this->hidden = true;
}
/**
* Востанавливаем доступ к срытому файлу.
*/
public function show() : void
{
$this->hidden = false;
}
/**
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* @return bool
*/
public function isHidden() : bool
{
return $this->hidden;
}
}
/**
* Aggregate Root.
* По моегму видению файл не может существовать без родительской папке.
* По этому папка и есть наш аггрегат.
*
* "/Domain/Directories/Directory.php"
*/
final class Directory
{
/**
* Название директории (папки) играет роль уникального идентификатора.
* Так-же как и в случае с файлом этот идентифактор может быть уникален только в рамках другой директории (папки).
* В случае если это сложно отслеживать можно использовать UUID как ИД и атрибут $name как допольнительнй.
*
* @var string
*/
private $name;
/**
* @var File[]
*/
private $files = [];
/**
* @var array
*/
private $removedFiles = [];
/**
* @var string
*/
private $ownerId;
/**
* @var bool
*/
private $hidden = false;
/**
* @param string $name
*/
public function __construct(string $name, string $ownerId)
{
$this->name = $name;
$thi->owner = $ownerId;
}
/**
* Этот метод не должен использоватся нигде больше кроме в MySQLStorage или в других сторэджах.
* Это дополнительный метод который нужен потому что другие доменные методы которые описывают бизнес логику могут содержать например события.
* Чтобы не выбрасывать все события при восстановлении используется такого рода хак.
* Вы же можете сипользовать что-то другое если у вас есть более приемлемый вариант.
* Я пока лучше что-то не нашел.
*
* @internal
*/
public function reconstruct(bool $hidden, File ...$files) : void
{
$this->hidden = $hidden;
foreach ($files as $file) {
$this->files[$file->getName()] = $file;
}
}
/**
* @param File $file
*/
public function addFile(File $file) : void
{
$this->files[$file->getName()] = $file;
}
/**
* @param File[] $files
*/
public function addFiles(Files ...$files) : void
{
foreach ($files as $file) {
$this->addFile($file);
}
}
/**
* Это то что вы называете "запретить навсегда".
* Надо трактировать удаление файле как `sof delete`.
* То есть файл не удаляется реально а просто ставится какой небудь флаг типа `deletedAt` которое показывает время удаления.
* Хочу заметить что это один из вариантов реализации, так как их много.
* Может возникнуть вопрос почему мы не делаем это удаление через сам файл `File.php`
* это потому что возможно этот файл удален в одной директиве а в другой нет.
* То есть возможно реальный файл есть на диске а его сслки в БД дублируются или еще что, и каждая директория сама контролирует это для себя.
* Вы можете удалить этот метод, и реальизовать свою логику как вам угодно.
*
* @param string $name
*/
public function removeFile(string $name) : void
{
if (!isset($this->files[$name])) {
throw new OutOfBoundsException('Invalid file ID');
}
$this->removedFiles[] = $name;
}
/**
* Скрываем временно дирекорию.
*/
public function hide() : void
{
$this->hidden = true;
}
/**
* Востанавливаем доступ к срытой директории.
*/
public function show() : void
{
$this->hidden = false;
}
/**
* @return string
*/
public function getName() : string
{
return $this->name;
}
/**
* @return string
*/
public function getOwnerId() : string
{
return $this->ownerId;
}
/**
* @return File[]
*/
public function getFiles() : array
{
return $this->files;
}
/**
* @return array
*/
public function getRemovedFiles() : array
{
return $this->removedFiles;
}
/**
* @return bool
*/
public function isHidden() : bool
{
return $this->hidden;
}
}
/**
* "Domain/Directories/Repository.php"
*/
final class Repository
{
/**
* @var Storage
*/
private $storage;
/**
* @param Storage $storage
*/
public function __construct(Storage $storage)
{
$this->storage = $storage;
}
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void
{
$this->storage->store($directory);
}
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array
{
$this->storage->getDirectoriesByOwner($ownerId);
}
}
/**
* "Domain/Directories/Storage.php"
*/
interface Storage
{
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void;
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array;
}
/**
* "Infrastructure/Persistence/Directories/MySQLStorage.php"
*/
final class MySQLStorage implments Storage
{
/**
* Название поля в БД.
*/
private CONST TABLE = 'Direcotry';
/**
* @var PDO
*/
private $database;
/**
* @param PDO $database
*/
public function __construct(PDO $database)
{
$this->database = $database;
}
/**
* @param Directory $directory
*/
public function store(Directory $directory) : void
{
// Провекра на сущестование директории в БД.
if (/* Ваша проверка на наличие в БД */) {
// Подгоавливаем директорию для сохранения.
$this->prepareRowData($direcotry);
// TODO: Обновите директорию в БД.
} else {
// Подгоавливаем директорию для сохранения.
$this->prepareRowData($direcotry);
/**
* TODO: Создайте новую директорию в БД.
* Этот процесс вероятнее всего в релационной БД будет содержать несколько шагов.
* Добалвение самой директории (папки), добалвение всех файлов в этой директории возможно в отдельной таблице.
* Ну и все другие операции которые уместны в этом сценарии.
*/
}
}
/**
* @return array
*/
public function getDirectoriesByOwner(string $ownerId) : array
{
/**
* Именно в этом методе и происходит выборка и фильтрация нужных директорий (папок) и файлов.
* Вы легко можете выбрать все файлы кторые не удаленны и не скрытие. Или все, или те которые вы посчитаете нужным.
* `WHERE Directory.isHidden = false AND File.isDeleted = false AND File.isHidden = false`
* То есть хочу заметить что вы свободны использовать любую структуру БД, которая вам подходит.
* Для целесности этой цепочки ниже после выборки данных вызывается метод преобразования из массива в Entity.
*/
// Запрашиваем данные из БД6 после чего преобразовываем все в доменные сущности и возвращаем результат.
$result = [];
foreach ($rows as $row) {
$result = $this->buildEntity($row);
}
return $result;
}
/**
* Этот метод конвертирует Entity в массив для дальнейшего его сохранения.
*
* @return array
*/
private function prepareRowData(Directory $directory) : array
{
$files = [];
foreach ($direcotry->getFiles() as $file) {
$files[] = [
'name' => $file->getName(),
'isHidden' => $file->isHidden(),
'isDeleted' => in_array($file->getName(), $direcotry->getremovedFiles()),
];
}
return [
'name' => $direcotry->getName(),
'ownerId' => $direcotry->getOwnerId(),
'isHidden' => $direcotry->isHidden(),
'files' => $files,
];
}
/**
* @param array $row
*
* @return Directory
*/
private function buildEntity(array $row) : Directory
{
$files = [];
foreach ($row['files'] as $item) {
$files[] = new File($item['name']);
}
$directory = new Directory($row['name']);
$directory->reconstruct($row['isHidden'], ...$files);
return $directory;
}
}
/**
* "Application/Directories/GetDirectoriesByOwner.php"
* Это так называемый `Use Case` или в книжках `Application Service`.
* Все наше приложение работает только через такие юз кэйсы, API, Console, Backend все что у нас есть вызывает такие юз кэйсы.
* Пример:
* $service = new GetDirectoriesByOwner(
* new Repository(
* new MySQLStorage($container[PDO::class])
* )
* );
*/
final class GetDirectoriesByOwner
{
/**
* @var Repository
*/
private $repository;
/**
* @param Repository $repository
*/
public function __construct(Repository $repository)
{
$this->repository = $repository;
}
/**
* @param string $ownerId
*
* @return array
*/
public function handle(string $ownerId) : array
{
return $this->repository->getDirectoriesByOwner($ownerId);
}
}