PHP-модель домена

Я программировал в PHP несколько лет и в прошлом использовал свои собственные методы обработки данных в своих приложениях.

Я построил свой собственный MVC в прошлом и имею разумное понимание ООП в php, но я знаю, что моя реализация требует серьезной работы.

В прошлом я использовал отношения is-a между моделью и таблицей базы данных. Теперь я знаю, после нескольких исследований, что это не лучший способ продвижения вперед. Насколько я понимаю, я должен создавать модели, которые действительно не заботятся о базовой базе данных (или о том, какой механизм хранения следует использовать), но заботятся только об их действиях и их данных.

Из этого я установил, что могу создавать модели let, например, например Person объект этого человека может иметь некоторых детей (человеческих детей), которые также являются объектами Person, содержащимися в массиве (с помощью методов addPerson и removePerson, принимающих объект Person).

Тогда я мог бы создать PersonMapper, который мог бы использовать для получения Лица с определенным "id" или для сохранения Person.

Затем можно найти данные отношений в таблице поиска и создать связанные дочерние объекты для запрашиваемого лица (если они есть), а также сохранить данные в таблице поиска в команде сохранения.

Теперь это подталкивает пределы моих знаний.....

Что, если бы я хотел смоделировать здание с разными уровнями и разными комнатами на этих уровнях? Что, если я захочу разместить некоторые предметы в этих комнатах?

Создать класс для построения, уровня, комнаты и элемента

со следующей структурой.

здание может иметь 1 или несколько объектов уровня, хранящихся в массиве уровень может содержать 1 или несколько объектов помещения, находящихся в массиве комната может содержать 1 или несколько объектов объектов в массиве

и mappers для каждого класса с более высоким уровнем отображения с использованием дочерних преобразователей для заполнения массивов (по запросу объекта верхнего уровня или ленивой загрузки по запросу)

Это, кажется, плотно соединяет разные объекты, хотя и в одном направлении (т.е. пол не обязательно должен находиться в здании, но здание может иметь уровни)

Правильно ли это происходит?

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

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

Если кто-то может помочь, это будет действительно полезно.

Ответ 1

Скажем, вы организуете свои объекты так: enter image description here

Чтобы инициализировать весь объект здания (с уровнями, комнатами, элементами), вам необходимо предоставить классы уровня db для выполнения задания. Один из способов получения всего, что вам нужно для древовидной структуры здания:

(увеличьте браузер для лучшего просмотра)

zoom for better view

Здание будет инициализироваться соответствующими данными в зависимости от параметров, предоставленных в качестве аргументов для метода initializeById. Этот подход также может работать при инициализации уровней и комнат. (Примечание: повторное использование этих методов initializeById при инициализации всего здания приведет к большому количеству запросов db, поэтому я использовал небольшой тэг индексации и SQL-запрос)

class RoomMapper implements RoomMapperInterface {

    public function fetchByLevelIds(array $levelIds) {
        foreach ($levelIds as $levelId) {
            $indexedRooms[$levelId] = array();
        }

        //SELECT FROM room WHERE level_id IN (comma separated $levelIds)
        // ...
        //$roomsData = fetchAll();

        foreach ($roomsData as $roomData) {
            $indexedRooms[$roomData['level_id']][] = $roomData;
        }

        return $indexedRooms;
    }

}

Теперь скажем, что у нас есть эта схема db

enter image description here

И, наконец, некоторый код.

Строительство

class Building implements BuildingInterface {

    /**
     * @var int
     */
    private $id;

    /**
     * @var string
     */
    private $name;

    /**
     * @var LevelInterface[]
     */
    private $levels = array();

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->name = $data['name'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function addLevel(LevelInterface $level) {
        $this->levels[$level->getId()] = $level;
    }

    /**
     * Initializes building data from the database. 
     * If all mappers are provided all data about levels, rooms and items 
     * will be initialized
     * 
     * @param BuildingMapperInterface $buildingMapper
     * @param LevelMapperInterface $levelMapper
     * @param RoomMapperInterface $roomMapper
     * @param ItemMapperInterface $itemMapper
     */
    public function initializeById(BuildingMapperInterface $buildingMapper, 
            LevelMapperInterface $levelMapper = NULL, 
            RoomMapperInterface $roomMapper = NULL, 
            ItemMapperInterface $itemMapper = NULL) {

        $buildingData = $buildingMapper->fetchById($this->id);

        $this->setData($buildingData);

        if (NULL !== $levelMapper) {
            //level mapper provided, fetching bulding levels data
            $levelsData = $levelMapper->fetchByBuildingId($this->id);

            //indexing levels by id
            foreach ($levelsData as $levelData) {
                $levels[$levelData['id']] = new Level($levelData);
            }

            //fetching room data for each level in the building
            if (NULL !== $roomMapper) {
                $levelIds = array_keys($levels);

                if (!empty($levelIds)) {
                    /**
                     * mapper will return an array level rooms 
                     * indexed by levelId
                     * array($levelId => array($room1Data, $room2Data, ...))
                     */
                    $indexedRooms = $roomMapper->fetchByLevelIds($levelIds);

                    $rooms = array();

                    foreach ($indexedRooms as $levelId => $levelRooms) {
                        //looping through rooms, key is level id
                        foreach ($levelRooms as $levelRoomData) {
                            $newRoom = new Room($levelRoomData);

                            //parent level easy to find
                            $levels[$levelId]->addRoom($newRoom);

                            //keeping track of all the rooms fetched 
                            //for easier association if item mapper provided
                            $rooms[$newRoom->getId()] = $newRoom;
                        }
                    }

                    if (NULL !== $itemMapper) {
                        $roomIds = array_keys($rooms);
                        $indexedItems = $itemMapper->fetchByRoomIds($roomIds);

                        foreach ($indexedItems as $roomId => $roomItems) {
                            foreach ($roomItems as $roomItemData) {
                                $newItem = new Item($roomItemData);
                                $rooms[$roomId]->addItem($newItem);
                            }
                        }
                    }
                }
            }

            $this->levels = $levels;
        }
    }

}

Уровень

class Level implements LevelInterface {

    private $id;
    private $buildingId;
    private $number;

    /**
     * @var RoomInterface[]
     */
    private $rooms;

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->buildingId = $data['building_id'];
        $this->number = $data['number'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function getId() {
        return $this->id;
    }

    public function addRoom(RoomInterface $room) {
        $this->rooms[$room->getId()] = $room;
    }

}

Номер

class Room implements RoomInterface {

    private $id;
    private $levelId;
    private $number;

    /**
     * Items in this room
     * @var ItemInterface[]
     */
    private $items;

    private function setData(array $roomData) {
        $this->id = $roomData['id'];
        $this->levelId = $roomData['level_id'];
        $this->number = $roomData['number'];
    }

    private function getData() {
        return array(
            'level_id' => $this->levelId,
            'number' => $this->number
        );
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    public function getId() {
        return $this->id;
    }

    public function addItem(ItemInterface $item) {
        $this->items[$item->getId()] = $item;
    }

    /**
     * Saves room in the databse, will do an update if room has an id
     * @param RoomMapperInterface $roomMapper
     */
    public function save(RoomMapperInterface $roomMapper) {
        if (NULL === $this->id) {
            //insert
            $roomMapper->insert($this->getData());
        } else {
            //update
            $where['id'] = $this->id;
            $roomMapper->update($this->getData(), $where);
        }
    }

}

Item

class Item implements ItemInterface {

    private $id;
    private $roomId;
    private $name;

    private function setData(array $data) {
        $this->id = $data['id'];
        $this->roomId = $data['room_id'];
        $this->name = $data['name'];
    }

    public function __construct(array $data = NULL) {
        if (NULL !== $data) {
            $this->setData($data);
        }
    }

    /**
     * Returns room id (needed for indexing)
     * @return int
     */
    public function getId() {
        return $this->id;
    }

}

Ответ 2

Теперь это подталкивает пределы моих знаний.....

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

Это, кажется, плотно соединяет разные объекты, хотя и в одном направлении

В этом нет ничего плохого. У зданий в реальном мире есть полы, комнаты и т.д., И вы просто моделируете этот факт.

и mappers для каждого класса с более высоким уровнем отображения с использованием дочерних преобразователей

В терминологии DDD эти "mappers" называются "репозиториями". Кроме того, ваш объект Building может считаться "совокупным", если он владеет всеми полами/комнатами/элементами внутри него, и если нет смысла загружать Room самостоятельно без здания. В этом случае вам понадобится только один BuildingRepository, который может загрузить все дерево зданий. Если вы используете любую современную библиотеку ORM, она должна автоматически выполнять всю работу по составлению карт (включая загрузку дочерних объектов).

Ответ 3

Если я правильно понимаю ваш вопрос, ваша основная проблема заключается в том, что вы не используете абстрактные классы должным образом. В принципе, у вас должны быть разные классы для каждого вашего здания, уровней, комнат и т.д. Например, у вас должен быть абстрактный класс Building, абстрактный класс Levels, который расширяется Building и т.д., Зависит от того, что вы хотите иметь точно, и например, у вас есть дерево building- > level- > room, но это скорее похоже на двойной список, потому что каждое здание имеет массив объектов уровня, и каждый уровень имеет родительский объект здания. Вы также должны использовать интерфейсы, так как многие люди игнорируют их, и они помогут вам и вашей команде в будущем.

Что касается моделей зданий более общим способом, лучший способ сделать это, на мой взгляд, это иметь класс, который реализует те же методы для каждого типа базы данных или другого метода хранения, который вы используете. Например, у вас есть база данных mongo и база данных mysql, у вас будет класс для каждого из них, и у них будут такие методы, как добавление, удаление, обновление, push и т.д. Чтобы быть уверенным, что вы не совершаете никаких ошибок, и все будет работайте правильно, лучший способ сделать это - иметь базу данных интерфейса, которая будет хранить методы, и вы не будете использовать метод mongo где-нибудь, где метод mysql не определен. Вы также можете определить абстрактный класс для общих методов, если они есть. Надеюсь, это будет полезно, приветствия!