Валидация сущностей

Обсуждаем, как правильно строить приложения
Ответить
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Валидация сущностей

Сообщение FeRaMon4ik »

Доброго времени суток. В данной теме viewtopic.php?f=34&t=36725&start=20#p188218 был разговор про валидацию данных перед предоставлением их сущности. У меня возникла проблема, к решению которой не могу найти правильный подход. Суть самой проблемы:
У нас есть 2 сущности. Видео и группы видео. На сервере принимаем реквест на api c данными для создания группы видео и преобразую в dto.

Код: Выделить всё

modules\video\dto\VideoGroupDto Object
(
    [name] => Тест
    [videos] => Array
        (
            [0] => Array
                (
                    [fileHostingUrl] => url2
                    [videoHostingUrl] => url1
                    [audioTypeId] => 1
                    [countryId] => 1
                    [platformIds] => Array
                        (
                            [0] => 1
                            [1] => 2
                        )

                    [typeIds] => Array
                        (
                            [0] => 1
                            [1] => 3
                        )

                )

            [1] => Array
                (
                    [fileHostingUrl] => 
                    [videoHostingUrl] => 
                    [audioTypeId] => 1
                    [countryId] => 1
                    [platformIds] => Array
                        (
                            [0] => 2
                        )

                    [typeIds] => Array
                        (
                        )

                )

        )

)
Код экшена выглядит так

Код: Выделить всё

public function actionVideoGroup()
    {
        if (Yii::$app->request->isPost) {
            try {

                $videoGroupDto = VideoGroupDto::createFromRequest(Yii::$app->request);
                print_r($videoGroupDto);
                $videoCreateService = new VideoCreateService(
                    new VideoCanals(),
                    new VideoChronometries(),
                    new VideoFormats()
                );
                $videoGroupCreateService = new VideoGroupCreateService($videoCreateService);
                $videoGroupCreateService->createFromDto($videoGroupDto);

            } catch (ValidationException $validationException) {
                return ['errors' => $validationException->getErrors()];
            } catch (Exception $exception) {
                return ['errors' => ['_error' => $exception->getMessage()]];
            }

        } else {
            $dataForForm = new VideoGroupDataForFormCreateService(
                new VideoAudioTypes(),
                new VideoCountries(),
                new VideoPlatforms(),
                new VideoTypes()
            );

            return $dataForForm->create();
        }
    }
Сам метод. Он ужасен.) Вот насмотришься как люди красиво код пишут и ты стараешься, но еще новичок в данном деле)

Код: Выделить всё

public function createFromDto(VideoGroupDto $videoGroupDto)
    {
        $transaction = Yii::$app->db->beginTransaction();
        try {
            $videoGroup = new VideoGroups();
            $videoGroup->name = $videoGroupDto->name;
            if ($videoGroup->validate()) {
                if (!empty($videoGroupDto->videos) && is_array($videoGroupDto->videos)) {
                    $this->createAndLinkVideos($videoGroupDto->videos);
                }
            } else {
                throw new ValidationException(
                    'Ошибка создания группы',
                    0,
                    null,
                    $videoGroup->getErrors()
                );
            }
            $transaction->commit();
        } catch (ValidationException $validationException) {
            $transaction->rollBack();
            throw $validationException;
        } catch (Exception $exception) {
            $transaction->rollBack();
            throw new ValidationException(
                $exception->getMessage(),
                0,
                null,
                ['errors' => $exception->getMessage()]
            );
        }
    }
Сама проблема в том, что щас валидация происходит прям в сервисе. Плюс возникает множество вопросов:
1) как правильно возвращать ошибки клиенту? (По сути возвращаться должны ошибки для группы видео + ошибки для каждого видео в виде массива примерно такого плана)

Код: Выделить всё

[
	//Ошибки группы
	'name' = [
		'не должно быть меньше 3 символов'
	],
	'preview' = [
		'только jpg'
	],
	'videos' => [
		//Ошибки видео в группе
		0 => [
			'audioType' => 'Такого типа нет',
			'platforms' => 'Не больше 3 платформ'
		],
		1 => [
			......
		]
	]
]
2) Валидация yii2 возращает ошибки для полей модели(они у меня в snake case), но ошибки нужно отдавать для полей camelCase и названия могу варьироваться, тогда для этого случая нужно писать какой-то трансформер?
3) Может ли метод сервис возвращать только что созданную модель, чтобы прилинковать ее к группу видео?
zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: Валидация сущностей

Сообщение zelenin »

я не буду комментировать по теме, по причине того, что к архитектуре и дизайну это никак не относится. это обычный yii-код с частично работающим сервисным слоем - без изоляции слоев (да и вообще слоев в принципе), без di, без разграничения ответственностей, с сильной связанностью, с AR в конце концов.
Ты не наш клиент => делай как хочешь - этому коду не помочь.
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

zelenin писал(а): 2017.02.24, 02:02 я не буду комментировать по теме, по причине того, что к архитектуре и дизайну это никак не относится. это обычный yii-код с частично работающим сервисным слоем - без изоляции слоев (да и вообще слоев в принципе), без di, без разграничения ответственностей, с сильной связанностью, с AR в конце концов.
Ты не наш клиент => делай как хочешь - этому коду не помочь.
Я знаю, что то что я пишу априори говнокод. Я стараюсь из этого вылезти и написал ради небольшой помощи. И был бы очень благодарен в наставлении.
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Валидация сущностей

Сообщение anton_z »

FeRaMon4ik писал(а): 2017.02.24, 02:20 Я знаю, что то что я пишу априори говнокод. Я стараюсь из этого вылезти и написал ради небольшой помощи. И был бы очень благодарен в наставлении.
Формы валидируете? Ну валидируйте их в UI слое. Создайте классы форм для видео и групп видео. Наследуйте их от yii/base/Model. Формы могут сами использовать зависимости. Их можно туда внедрять с помощью контейнера или вручную. Таким образом, формы могут и к нижележащим сервисам обращаться для получения каких-либо данных, которые нужны для валидации (проверка уникальности и т.п.), заполнения списков возможных значений и.т.п.

Если вываливается Exception при выполнении доменной операции, делаешь в контроллере в блоке catch что-то наподобие $form->addError($e->getMesssage());

Да, в домене лучше от yii и AR отвязаться, но если не хочется/не можется/нет времени, с сервисным слоем все равно лучше. Только надо чтобы использование yii не было сквозным: не делайте DTO и сервисы наследниками yii классов, в сервисы и из сервисов должны приходить не yii объекты. Потом, когда задачу решите, появится время/желание/понимание, сервисы отрефакторите, UI слой останется прежним. Yii из домена уйдет и будет все по правилам.

P.S. Правильно, вылезайте. Сразу не вылезете. Это процесс постепенный. Читайте книги по архитектуре. Главное тут не бросать. Сам такой)
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

anton_z писал(а): 2017.02.24, 13:45 Да, в домене лучше от yii и AR отвязаться, но если не хочется/не можется/нет времени, с сервисным слоем все равно лучше. Только надо чтобы использование yii не было сквозным: не делайте DTO и сервисы наследниками yii классов, в сервисы и из сервисов должны приходить не yii объекты. Потом, когда задачу решите, появится время/желание/понимание, сервисы отрефакторите, UI слой останется прежним. Yii из домена уйдет и будет все по правилам.

P.S. Правильно, вылезайте. Сразу не вылезете. Это процесс постепенный. Читайте книги по архитектуре. Главное тут не бросать. Сам такой)
Большое спасибо за ответ. Меня еще интересует один вопрос. В сервис должны приходить не yii объекты. Есть сервис которому необходимы данные из 4-6 таблиц в БД и он формирует список фильтров. Как в таком случае поступать, что давать на вход сервиса?
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Валидация сущностей

Сообщение anton_z »

FeRaMon4ik писал(а): 2017.02.24, 14:39 Большое спасибо за ответ. Меня еще интересует один вопрос. В сервис должны приходить не yii объекты. Есть сервис которому необходимы данные из 4-6 таблиц в БД и он формирует список фильтров. Как в таком случае поступать, что давать на вход сервиса?
Сложно дать ответ на такой абстрактный вопрос. Конкретнее, если можно: что за таблицы, что за фильтры?
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

Таблицы весьма просты. Категории, каналы, типы. Вот такого формата:

Код: Выделить всё

/**
 *
 * @property integer $video_canal_id
 * @property string $name
 *
 * @property Videos[] $videos
 */
class VideoCanals extends \yii\db\ActiveRecord
{
    public static function tableName()
    {
        return 'video_canals';
    }

    public function getVideos()
    {
        return $this->hasMany(Videos::className(), ['video_canal_id' => 'video_canal_id']);
    }
}
Фильтр из себя представляет в конечном итоге JSON вида:

Код: Выделить всё

{
	//Это фильтр по каналам, созданный из таблицы VideoCanals
	'canals' => [
		{
			'name' = > Каналы,
			'items' => [ //Данные из таблицы
				{
					'id' =>1,
					'name' => 'Первый канал'
				},
				{
					'id' =>2,
					'name' => 'Второй канал'
				}
			]
		}
	],
	//Это фильтр по каналам, созданный из таблицы VideoTypes
	'types' => [
		и т.д
	]
}
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Валидация сущностей

Сообщение anton_z »

На основе чего у вас делается выборка из таблиц? Критерии выборки какие?
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

На основе AR. На данный момент выбираются все строки без критериев, так как записей там по 5-10.
Можно объявить интерфейс Read репозитория и сделать YiiVideoCanalsReadRepostiry implements Read который передавать в сервис для формирования фильтров? Или что то не так ?)
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Валидация сущностей

Сообщение anton_z »

Нет, ну если критериев никаких нет (из UI например), то пусть сервис сам и вытянет нужные записи и сформирует фильтьтры на их основе.
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

anton_z писал(а): 2017.02.25, 03:29 Нет, ну если критериев никаких нет (из UI например), то пусть сервис сам и вытянет нужные записи и сформирует фильтьтры на их основе.
Переписал контроллер

Код: Выделить всё

public $dataForFormCreateService;
    public $videoGroupCreateService;

    public function __construct(
        $id,
        Module $module,
        VideoGroupDataForFormCreateInterface $dataForFormCreateService,
        VideoGroupCreateInterface $videoGroupCreateService,
        Array $config = []
    ) {
        $this->dataForFormCreateService = $dataForFormCreateService;
        $this->videoGroupCreateService = $videoGroupCreateService;
        parent::__construct($id, $module, $config);
    }

    public function actionVideoGroup()
    {
        if (!Yii::$app->request->isPost) {
            return $this->dataForFormCreateService->create();
        }

        $videoGroupForm = new VideoGroup(new Video());
        $videoGroupDto = VideoGroupDto::createFromRequest(Yii::$app->request);
        $videoGroupForm->setAttributes($videoGroupDto->toArray());
        if ($videoGroupForm->validate()) {
            try {
                $this->videoGroupCreateService->createFromDto($videoGroupDto);
            } catch (\Exception $exception) {
                return ['errors' => ['_error' => $exception->getMessage()]];
            }
        } else {
            return ['errors' => $videoGroupForm->getErrors()];
        }
    }
Вот такой вопрос возник про DI. На данный момент описываю конфиг DI в конфиге frontend. Нормально ли это? Или лучше описывать конфиги в модулях и подлючать их уже в главный?

Код: Выделить всё

return [
    'definitions' => [
        'modules\video\interfaces\VideoGroupDataForFormCreateInterface' => [
            'class' => 'modules\video\services\VideoGroupDataForFormCreateService',
        ],
        'modules\video\interfaces\VideoCreateInterface' => [
            'class' => 'modules\video\services\VideoCreateService',
        ],
        'modules\video\interfaces\VideoGroupCreateInterface' => [
            'class' => 'modules\video\services\VideoGroupCreateService',
            \yii\di\Instance::of('modules\video\interfaces\VideoCreateServiceInterface')
        ],
    ],
];

Код: Выделить всё


Нормально ли делать тайп хинтинг в методах интерфейса? 

namespace modules\video\interfaces;

use modules\video\dto\VideoGroupDto;

interface VideoGroupCreateInterface
{
    public function __construct(VideoCreateInterface $videoCreateService);

    public function createFromDto(VideoGroupDto $videoGroupDto);
}
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Валидация сущностей

Сообщение ElisDN »

FeRaMon4ik писал(а): 2017.02.27, 15:20 VideoGroupDataForFormCreateInterface
VideoGroupDataForFormCreateService
Интерфейсы используют как базовый тип для изменяемых вещей. Не используйте интерфейсы для сервисов, которые неизменны.
FeRaMon4ik писал(а): 2017.02.27, 15:20 Нормально ли делать тайп хинтинг в методах интерфейса?
Нормально.
FeRaMon4ik писал(а): 2017.02.27, 15:20

Код: Выделить всё

interface VideoGroupCreateInterface
{
    public function __construct(VideoCreateInterface $videoCreateService);
    public function createFromDto(VideoGroupDto $videoGroupDto);
}
Не используйте __construct в интерфейсах.
FeRaMon4ik
Сообщения: 26
Зарегистрирован: 2015.07.28, 17:48

Re: Валидация сущностей

Сообщение FeRaMon4ik »

ElisDN писал(а): 2017.02.27, 16:09 Не используйте __construct в интерфейсах.
Спасибо за ответ. Насчет использования construct в интрефейсах. Я правильно понимаю, что дополнительные данные, такие как сторонние сервисы

Код: Выделить всё

 public function __construct(VideoCreateInterface $videoCreateService)
- это чисто детали реализации сервиса? То есть главное реализовать метод

Код: Выделить всё

 public function createFromDto(VideoGroupDto $videoGroupDto);
, а что приходить в сервис как зависимости не важно?
anton_z
Сообщения: 483
Зарегистрирован: 2017.01.15, 15:01

Re: Валидация сущностей

Сообщение anton_z »

FeRaMon4ik писал(а): 2017.02.27, 16:24
ElisDN писал(а): 2017.02.27, 16:09 Не используйте __construct в интерфейсах.
Спасибо за ответ. Насчет использования construct в интрефейсах. Я правильно понимаю, что дополнительные данные, такие как сторонние сервисы

Код: Выделить всё

 public function __construct(VideoCreateInterface $videoCreateService)
- это чисто детали реализации сервиса? То есть главное реализовать метод

Код: Выделить всё

 public function createFromDto(VideoGroupDto $videoGroupDto);
, а что приходить в сервис как зависимости не важно?
Да, верно понимаете.
Ответить