A free and open-source book on ZF3 for beginners


4.20. Типы моделей

В Zend Framework 3, нет одной единственной директории 'Model' для хранения классов моделей, как вы могли бы предположить. Вместо этого модели условно делятся на следующие основные типы, и каждый хранится в своей собственной поддиректории.

Таблица 4.9. Типы моделей и их местоположение
Имя модели Директория
Сущности APP_DIR/module/Application/src/Entity
Репозитории APP_DIR/module/Application/src/Repository
Объекты-значения APP_DIR/module/Application/src/ValueObject
Сервисы APP_DIR/module/Application/src/Service
Фабрики В поддиректории Factory для каждой директории типа модели. Например, фабрики контроллеров будут храниться в APP_DIR/module/Application/src/Controller/Factory

Разделение моделей на разные типы упрощает создание бизнес-логики. Это также называется "проблемно-ориентированное проектирование" (Domain Driven Design или DDD). Впервые DDD предложил использовать Эрик Эванс в его книге Domain-Driven Design — Tackling Complexity in the Heart of Software.

Ниже мы опишем основные типы моделей более детально.

4.20.1. Сущности (Entities)

Сущности предназначены для хранения данных и всегда имеют свойство идентификатор, чтобы вы могли точно идентифицировать объект. Например, сущность User всегда будет иметь уникальное свойство login, и вы можете идентифицировать пользователя по этому свойству. Вы можете менять некоторые другие свойства сущности, как например firstName или address, но ее идентификатор не будет изменяться. Сущности обычно хранятся в базе данных, в файловой системе или в любом другом хранилище.

Ниже вы найдете образец сущности User, которая представляет посетителя сайта:

// Сущность User представляет посетителя сайта
class User 
{
    // Свойства
    private $login;     // например, "admin"
    private $title;     // например, "Mr."
    private $firstName; // например, "Ivan"
    private $lastName;  // например, "Ivanov"
    private $country;   // например, "Russia"
    private $city;      // например, "Moscow"
    private $postCode;  // например, "174354"
    private $address;   // например, "Stroiteley st."
    
    // Поведения   
    public function getLogin() 
    {
        return $this->login;
    }
        
    public function setLogin($login) 
    {
        $this->login = $login;
    }
    
    //...
}

В строках 5-12 мы определяем свойства модели User. Лучше всего определять свойства, используя приватный тип доступа и делать их доступными для вызова через публичные методы - геттеры и сеттеры (такие как getLogin() и setLogin() и т.д.)

Методы поведения модели не ограничены геттерами и сеттерами. Вы можете создавать другие методы, которые будут управлять данными модели. Например, для удобства вы можете определить метод getFullName(), который будет возвращать полное имя пользователя, например, "Mr. Ivan Ivanov".

4.20.2. Репозитории (Repositories)

Репозитории - это специальные модели, ответственные за хранение и извлечение сущностей. Например, UserRepository может представлять таблицу базы данных и предоставлять методы для извлещения сущностей User. Обычно вы используете репозитории для хранения сущностей в базе данных. С помощью репозиториев вы можете инкапсулировать SQL-запросы в одно место и с легкостью поддерживать их и тестировать.

Мы узнаем о репозиториях более детально в Управление БД с Doctrine, когда будем говорить о библиотеке Doctrine.

4.20.3. Объекты-значения (Value Objects)

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

Например, модель, обрабатывающая сумму денег может быть объектом-значением:

class MoneyAmount 
{
    // Свойства
    private $currency;
    private $amount;
  
    // Конструктор
    public function __construct($amount, $currency='USD') 
    {
        $this->amount = $amount;
        $this->currency = $currency;
    }
  
    // Получаем код валюты
    public function getCurrency() 
    {
        return $this->currency;  
    }
  
    // Получаем сумму денег
    public function getAmount() 
    {
        return $this->amount;
    }  
}

В строках 4-5 мы определяем два свойства: currency и amount. У модели нет свойства идентификатора, вместо этого она идентифицируется всеми свойствами, как одним целым: если вы поменяете либо currency либо amount, у вас будет другой объект суммы денег.

В строках 8-12 мы определяем метод конструктора, который инициализирует свойства.

В строках 15-24 мы определяем геттеры для свойств модели. Заметьте, что мы не определяем сеттеры (так как модель неизменна).

4.20.4. Сервисы (Services)

Модели-сервисы обычно инкапсулируют некоторую функциональность бизнес-логики. Сервисы, как правило, имеют легко узнаваемые имена, заканчивающиеся на суффикс "er", например FileUploader или UserManager.

Ниже представлен образец сервиса Mailer. У него есть метод sendMail(), который берет объект-значение EmailMessage и отправляет электронное письмо, используя стандартную PHP-функцию mail().

<?php

// Объект-значение и-мейла
class EmailMessage 
{  
    private $recipient;
    private $subject;
    private $text;  
  
    // Конструктор
    public function __construct($recipient, $subject, $text) 
    {
        $this->recipient = $recipient;
        $this->subject = $subject;
        $this->text = $text;
    } 
  
    // Геттеры
    public function getRecipient() 
    {
        return $this->recipient;
    }
  
    public function getSubject() 
    {
        return $this->subject;
    }
  
    public function getText() 
    {
        return $this->text;
    }
}

// Сервис Mailer, который может отправлять сообщения по эл. почте
class Mailer 
{
    public function sendMail($message) 
    {
        // Используем PHP-функцию mail() для отправки письма
        if(!mail($message->getRecipient(), $message->getSubject(), 
                 $message()->getText())) 
        {
            // Ошибка отправки письма
            return false;
        }
    
        return true;
    }
}

В Zend Framework вы, как правило, регистрируете ваши сервисы в менеджере сервисов (Service Manager).

4.20.5. Фабрики (Factories)

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

Классы фабрик обычно имеют имена заканчивающиеся на 'Factory', такие как СurrencyConverterFactory, MailerFactory, и т.д.

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

<?php
namespace Application\Service\Factory;

use Interop\Container\ContainerInterface;
use Zend\ServiceManager\Factory\FactoryInterface;
use Application\Service\CurrencyConverter;
use Application\Service\PurchaseManager;

/**
 * This is the factory for PurchaseManager service. Its purpose is to instantiate the 
 * service and inject its dependencies.
 */
class PurchaseManagerFactory implements FactoryInterface
{
    public function __invoke(ContainerInterface $container, 
                $requestedName, array $options = null)
    {
        // Get CurrencyConverter service from the service manager.
        $currencyConverter = $container->get(CurrencyConverter::class);
        
        // Instantiate the service and inject dependencies.
        return new PurchaseManager($currencyConverter);
    }
}

В коде, приведенном выше, у нас есть класс PurchaseManagerFactory, который реализует интерфейс Zend\ServiceManager\Factory\FactoryInterface. У класса фабрики есть метод __invoke(), целью которого является инстанцирование объекта. У этого метода есть аргумент $container, который является менеджером сервисов. Вы можете использовать $container, чтобы извлекать сервисы из менеджера сервисов и передавать их создаваемому объекту.


Top