Сервисный слой в yii, как правильно?

Разбираюсь с проектирование приложения на yii2. Решил отказаться от ActiveRecord (разве что для админки оставить) и разделить всё по слоям.

Интерфейс модели пользователя
<?php
namespace app\core\models\user;
use app\core\models\city\CityInterface;
use app\core\models\country\CountryInterface;

interface UserInterface
{
    /**
     * Возвращает ID пользователя
     * @return integer
     */

    public function getId();

    /**
     * Устанавливает ID пользователя
     * @param  $id
     */
    public function setId($id);

    /**
     * Возвращает имя
     * @return string
     */
    public function getFirstName();

    /**
     * Возвращает фамилию
     * @return string
     */
    public function getLastName();

    /**
     * Возвращает отчество
     * @return string
     */
    public function getPatronymic();

    /**
     * Возвращает город
     * @return CityInterface
     */
    public function getCity();

    /**
     * Возвращает страну
     * @return CountryInterface
     */
    public function getCountry();

    /**
     * Возвращает дату регистрации пользователя
     * @return \DateTime
     */
    public function getRegistrationDate();

    /**
     * Возвращает email
     * @return string
     */
    public function getEmail();

    /**
     * Возвращает установленный пароль
     * @return string
     */
    public function getPassword();

    /**
     * @param $firstName string Устанвалвивет имя пользователя
     */
    public function setFirstName($firstName);

    /**
     * @param $lastName string Устанавливает фамилию пользователя
     */
    public function setLastName($lastName);

    /**
     * @param $patronymic string Устанавливает отчество пользователя
     */
    public function setPatronymic($patronymic);

    /**
     * @param $email string Устанавливает email пользователя
     */
    public function setEmail($email);

    /**
     * @param $password string Устанаваливает пароль
     */
    public function setPassword($password);


}

Реализация интерфейса пользователя
<?php
namespace app\core\models\user;
use yii\base\Model;
use yii\base\Object;
use app\core\models\city\CityInterface;
use app\core\models\country\CountryInterface;

class User extends Object implements UserInterface
{

    
    /**
     * @var $_id integer
     */
    private $_id;

    /**
     * @var $_firstName string
     */
    private $_firstName;
    /**
     * @var $_lastName string
     */
    private $_lastName;
    /**
     * @var $_patronymic string
     */
    private $_patronymic;
    /**
     * @var $_city CityInterface
     */
    private $_city;
    /**
     * @var $_country CountryInterface
     */
    private $_country;
    /**
     * @var $_registrationDate \DateTime
     */
    private $_registrationDate;
    /**
     * @var $_email string
     */
    private $_email;
    /**
     * @var $_password string
     */
    private $_password;


    /**
     * Возвращает имя
     * @return string
     */
    public function getFirstName()
    {
        return $this->_firstName;
    }

    /**
     * Возвращает фамилию
     * @return string
     */
    public function getLastName()
    {
        return $this->_lastName;
    }

    /**
     * Возвращает отчество
     * @return string
     */
    public function getPatronymic()
    {
        return $this->_patronymic;
    }

    /**
     * Возвращает город
     * @return CityInterface
     */
    public function getCity()
    {
        return $this->_city;
    }

    /**
     * Возвращает страну
     * @return CountryInterface
     */
    public function getCountry()
    {
        return $this->_country;
    }

    /**
     * Возвращает дату регистрации пользователя
     * @return \DateTime
     */
    public function getRegistrationDate()
    {
        return $this->_registrationDate;
    }

    /**
     * Возвращает email
     * @return string
     */
    public function getEmail()
    {
        return $this->_email;
    }

    /**
     * @param $firstName string Устанвалвивет имя пользователя
     */
    public function setFirstName($firstName)
    {
        $this->_firstName = $firstName;
    }

    /**
     * @param $lastName string Устанавливает фамилию пользователя
     */
    public function setLastName($lastName)
    {
        $this->_lastName = $lastName;
    }

    /**
     * @param $patronymic string Устанавливает отчество пользователя
     */
    public function setPatronymic($patronymic)
    {
        $this->_patronymic = $patronymic;
    }

    /**
     * @param $email string Устанавливает email пользователя
     */
    public function setEmail($email)
    {
        $this->_email = $email;
    }

    /**
     * @param $password string Устанаваливает пароль
     */
    public function setPassword($password)
    {
        $this->_password = $password;
    }

    /**
     * Возвращает ID пользователя
     * @return integer
     */
    public function getId()
    {
        return $this->_id;
    }

    /**
     * Устанавливает ID пользователя
     * @param  $id
     */
    public function setId($id)
    {
        $this->_id = $id;
    }
}


И есть сервис для этой модели.
Интерфейс сервиса модели юзера
namespace app\core\models\user;
interface UserServiceInterface
{

    /**
     * Возвращает пользователя по его ID
     * @param $id integer ID пользователя
     * @return UserInterface
     */
    public function findById($id);

    /**
     * Возвращает пользователя по его E-mail
     * @param $email string Email пользователя
     * @return UserInterface
     */
    public function findByEmail($email);


    /**
     * Возвращает всех пользователей
     * @return UserInterface[]
     */
    public function findAll();

    /**
     * Сохраняет пользователя
     * @param UserInterface $user
     *
     */
    public function save(UserInterface $user);

    /**
     * Удаляет пользователя
     * @param UserInterface $user
     */
    public function delete(UserInterface $user);

}


и реализация этого интерфейса сервиса
<?php


namespace app\core\models\user;


use yii\db\Command;
use yii\db\Query;

class UserService implements UserServiceInterface
{

    /**
     * Возвращает пользователя по его ID
     * @param $id integer ID пользователя
     * @return UserInterface
     */
    public function findById($id)
    {
        return $this->findOneByAttribute('id', $id);

    }

    /**
     * Возвращает пользователя по его E-mail
     * @param $email string Email пользователя
     * @return UserInterface
     */
    public function findByEmail($email)
    {
        return $this->findOneByAttribute('email', $email);
    }

    /**
     * Возвращает всех пользователей
     * @return UserInterface[]
     */
    public function findAll()
    {

        $query = new Query();
        $rows = $query->all();
        $users = [];
        foreach ($rows as $row) {
            $users = $this->load($row);
        }
        return $users;

    }

    /**
     * Сохраняет пользователя
     * @param UserInterface $user
     *
     */
    public function save(UserInterface $user)
    {
        $command = new Command();
        $columns = [
            'first_name' => $user->getFirstName(),
            'last_name' => $user->getLastName(),
            'patronymic' => $user->getPatronymic(),
            'email' => $user->getEmail(),
        ];

        if ($user->getPassword() != null) {
            $columns['password_hash'] = \Yii::$app->security->generatePasswordHash($user->getPassword());
        }
        if ($user->getId() == null) {
            $command->insert(UserActiveRecord::tableName(), $columns);
            $user->setId(\Yii::$app->db->getLastInsertID());
        } else {
            $command->update(UserActiveRecord::tableName(), $columns, ['id' => $user->getId()]);
        }
    }

    /**
     * Удаляет пользователя
     * @param UserInterface $user
     */
    public function delete(UserInterface $user)
    {
        $command = new Command();
        $command->delete(UserActiveRecord::tableName(), ['id' => $user->getId()]);
    }

    private function load($result)
    {
        $user = new User();
        $user->setId($result['id']);
        $user->setEmail($result['email']);
        $user->setFirstName($result['first_name']);
        return $user;
    }

    private function findOneByAttribute($attribute, $value)
    {
        $query = new Query();
        $row = $query->andWhere([$attribute => $value])->one();
        if ($row) {
            return $this->load($row);
        } else {
            return null;
        }
    }
}


Подскажите, в правильном ли я направлении двигаюсь? Не покидает чувство, что что-то не так :)
  • Вопрос задан
  • 1822 просмотра
Пригласить эксперта
Ответы на вопрос 3
mitaichik
@mitaichik
Нет, в не правильном.

Сервисный слой, это слой который стоит над бизнес-логикой какой-либо отдельной части приложения (или всего приложения). Служба предоставляет вовне (другим частям приложения\сторонним приложениям) интерфейс доступа к этой части приложения.

Ни про какой отказ от ActiveRecord тут и речи не идет - это совсем про другое. Служба вполне спокойно может оперировать моделями ActiveRecord, почему нет?

То что вы все заменили геттерами.сеттерами тоже к сервисному слою отношения не имеет. Плюс в Yii гораздо более удобный механизм геттеров сеттеров - читайте документацию.

Ваш UserService - это вовсе не служба, а репозиторий в чистом виде. Да, вы можете их делать, но почему он не может оперировать удобнейшим и гибким AR?

Что касается смены выборки пользователей - да, здесь репозиторий поможет, и интерфейс поможет . Только почему репозиторий не может юзать AR, а интерфейс не вешаться на модель?

Что касается "а что будет, если то да се, фреймворк сменим, и т.п" - не будет такого! За всю практику я встречал только 2 перехода - с yii1 на yii2, и с yii1 на java, все!

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

Да, что касается служб - советаю почитать про Service Locator и связанные с этим топики в документации, там написано как их лучше реализовывать технически.
Ответ написан
Комментировать
bitver
@bitver
Вы пишете на Yii, так и используйте средства фреймворка, то, под что он заточен. Сейчас это выглядит как попытка сделать что-то своё, ну так и пишите свой фреймворк или юзайте другой если не нравится Yii. Нет, не защищаю фреймворк, это самое очевидное что приходит в голову.
А по поводу организации Service Layer именно в Yii, он и так есть в шаблонах и Gii его генерит (SearchModel) - это своего рода прослойка меж моделью и контроллером. Не обязательно привязываться к конкретной модели, можно создать свой класс от yii\base\Model или yii\base\Object и делать там всё что душе угодно с готовыми приятными фичами в виде евентов и прочего.
Ответ написан
webinar
@webinar Куратор тега Yii
Учим yii: https://youtu.be/-WRMlGHLgRg
Зачем использовать framework если им не пользоваться? Я часто сталкиваюсь с доработкой готовых проектов на yii и поражаюсь. Когда ставится framework а потом пишется чистый php и хорошо еще если с использованием ООП. Зачем? Если не удобно работать с framework зачем его использовать? Заказчик настаивает? Ну тогда надо его использовать, а не делать вид.
Не нравится yii - попробуйте laravel, symfony, напишите свой в конце концов, но не городите подобное. Тем более что как правило это продиктовано не структурой framework, а его незнанием на должном уровне.
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы