Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Обсуждаем, как правильно строить приложения
Ответить
Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.05, 22:24

Добрый вечер!

Продолжаю изучать DDD.

Реализовал базовый класс для DTO, в котором добавил поддержу геттеров и функцию make (просто для красоты). Геттеры позволят объявлять свойства как protected и при этом не писать функцию getXXX.

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

abstract class BaseDto
{


	/**
	 * @return string
	 */
	public static function className()
	{
		return get_called_class();
	}


	/**
	 * @param $name
	 *
	 * @return mixed
	 *
	 * @throws UnknownPropertyException
	 */
	public function __get($name)
	{
		$getter = 'get' . $name;
		if (method_exists($this, $getter)) {
			return $this->$getter();
		} elseif (property_exists($this, $name)) {
			return $this->{$name};
		}
		throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
	}


	/**
	 * @param array ...$args
	 * @return BaseDto
	 */
	public static function make(...$args)
	{
		$reflection = new \ReflectionClass(self::className());
		return $reflection->newInstanceArgs($args);
	}


}
А на основе базового класса BaseDto сделал DTO для ошибок.

Error - для передачи ошибки в виде строки + код ошибки.

InvalidParamErrors - для передачи ошибок в сущностях на базе Error. Дополнительные свойства - data (ошибки разбитые по параметрам) и errors (плоский список ошибок).

ModelErrors — для передачи ошибок в случаях, когда в качестве сущности используется ActiveRecord.

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

/**
 * @property string $error
 * @property mixed $code
 */
class Error extends BaseDto
{


	/**
	 * @var string
	 */
	protected $error;


	/**
	 * @var mixed
	 */
	protected $code;


	/**
	 * @param string $error
	 * @param mixed $code
	 */
	public function __construct($error, $code = null)
	{
		$this->error = (string)$error;
		$this->code = $code;
	}


	/**
	 * @return string
	 */
	public function __toString()
	{
		return $this->error;
	}


}


/**
 * @property array $data
 * @property array $errors
 */
class InvalidParamErrors extends Error
{


	/**
	 * Ошибки сгруппированные по параметрам:
	 *  [
	 *      'param1' => ['Error 1', 'Error 2', …],
	 *      'param2' => ['Error 1', 'Error 2', …],
	 *      …
	 *  ]
	 * @var array
	 */
	protected $data;


	/**
	 * Список ошибок: ['Error 1', 'Error 2', …]
	 * @var array
	 */
	protected $errors;


	/**
	 * @param array $errorsData
	 * @param null $code
	 */
	public function __construct($errorsData, $code = null)
	{
		$this->data = $errorsData;
		$this->errors = $this->makeErrors();
		parent::__construct($this->makeError(), $code);
	}


	/**
	 * @return array
	 */
	protected function makeErrors()
	{
		$errors = [];
		foreach ($this->data as $e) {
			$errors = array_merge($errors, $e);
		}
		return $errors;
	}


	/**
	 * @return string
	 */
	protected function makeError()
	{
		return implode(PHP_EOL, $this->errors);
	}


}


class ModelErrors extends InvalidParamErrors
{


	/**
	 * @param Model $model
	 * @param null $code
	 */
	public function __construct(Model $model, $code = null)
	{
		parent::__construct($model->getErrors(), $code);
	}
	

}
Покритикуйте :)

anton_z
Сообщения: 206
Зарегистрирован: 2017.01.15, 15:01

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение anton_z » 2017.07.06, 07:03

Базоый класс для DTO?! Ну это вообще что-то с чем-то. Зачем в DTO иерархия? Откуда это желание для всего делать базовые классы? Наследование надо применять, только если это действительно необходимо.

DTO это скорее замена ассоциативному массиву, облегчающая работу (автокомплит в IDE и find-usages) + легче понять, что методу или конструктору нужно (куда DTO передаете). Можно вообще без DTO жить. DTO это вообще не объект, а по сути ассоциативный массив. Структура данных лишенная поведения. Советую сначала почитать книги по ООП и объектно-ориентированному проектированию - Гради Буч подойдет прекрасно, а уже потом браться зз DDD, если захочется.
Вернон в свое DDD добавляет много вещей чуждых ООП, про DDD in PHP вообще молчу, там симфонисты со своими извращениями все написали.

Для ошибок лучше использовать Exceptions и кидать их, а не засовывать в DTO.

Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.06, 09:22

anton_z писал(а):
2017.07.06, 07:03
Базоый класс для DTO?! Ну это вообще что-то с чем-то. Зачем в DTO иерархия? Откуда это желание для всего делать базовые классы? Наследование надо применять, только если это действительно необходимо.

DTO это скорее замена ассоциативному массиву, облегчающая работу (автокомплит в IDE и find-usages) + легче понять, что методу или конструктору нужно (куда DTO передаете). Можно вообще без DTO жить. DTO это вообще не объект, а по сути ассоциативный массив. Структура данных лишенная поведения.
Да, возможно идеологически это не правильно... Но на практике как этом может помешать?
Класс немного допилил:

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

<?php

namespace common\dto;

use yii\base\UnknownPropertyException;


abstract class BaseDto
{


	/**
	 * @param $name
	 * @return mixed
	 * @throws UnknownPropertyException
	 */
	public function __get($name)
	{
		if (property_exists($this, $name)) {
			return $this->{$name};
		}
		throw new UnknownPropertyException('Getting unknown property: ' . get_class($this) . '::' . $name);
	}


	/**
	 * @param array ...$args
	 * @return self
	 */
	public static function make(...$args)
	{
		$reflection = new \ReflectionClass(get_called_class());
		return $reflection->newInstanceArgs($args);
	}


	/**
	 * @return array
	 */
	public function toArray()
	{
		$reflection = new \ReflectionClass(get_called_class());
		$data = [];
		foreach ($reflection->getProperties(\ReflectionProperty::IS_PROTECTED) as $property) {
			$name = $property->getName();
			$value = $this->{$property->getName()};
			$data[$name] = $value instanceof BaseDto ? $value->toArray() : $value;
		}
		return $data;
	}


	public function toJson(){
		return json_encode($this->toArray());
	}


}
Исходил из следующих мыслей:
  • В некоторых случаях DTO нужно представить в виде массива или JSON - для этого сделал соответствующие методы.
  • DTO создаётся и больше не изменяется. Поэтому свойства protected, а доступ через магию __get. Выпилил возможность использования геттеров, ни к чему они в DTO.
Получается такое использование BaseDTO:
1) DTO создаётся с помощью new Class(…) или Class::make(…) и в дальнейшем внести изменения нельзя.
2) DTO превращается в массив $var->toArray();
3) DTO кодируется в json $var->toJson();

В качестве примера:

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

class NameDto extends BaseDto
{
	protected $firstName;
	protected $lastName;
	public function __construct($firstName, $lastName)
	{
		$this->firstName = $firstName;
		$this->lastName = $lastName;
	}
}
$nameDto = NameDto::make('Иван', 'Иванов')
… или

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

class NameDto extends BaseDto
{
	protected $firstName;
	protected $lastName;
	public function __construct($config)
	{
		foreach ($config as $name => $value) {
			$this->{$name} = $value;
		}
	}
}
$nameDto = NameDto::make([
	'firstName' => 'Иван',
	'lastName' => 'Иванов',
]);
… и дальнейшее использование:

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

var_dump($nameDto->firstName);
var_dump($nameDto->lastName);
var_dump($nameDto->toArray());
var_dump($nameDto->toJson());
Получаем удобство, но с другой стороны идём на "идеологоическое преступление" :) Но будет ли наказание?

anton_z писал(а):
2017.07.06, 07:03
Для ошибок лучше использовать Exceptions и кидать их, а не засовывать в DTO.
Бесспорно, я и не собирался использовать в таком виде. Это для передачи ошибок например во вьюху.

anton_z
Сообщения: 206
Зарегистрирован: 2017.01.15, 15:01

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение anton_z » 2017.07.06, 10:17

А зачем тогда спрашиваете? Наказания конечно не будет. Можно писать как угодно, программы будут работать. Все говнокоды работают.
Тут вопрос в том, опираетесь ли вы на правила проектирования и методологию или занимаетесь "творчеством".

Вместо ваших классов можно использовать array и json_encode/decode. То же самое будет.

Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.06, 10:21

anton_z писал(а):
2017.07.06, 10:17
А зачем тогда спрашиваете? Наказания конечно не будет. Можно писать как угодно, программы будут работать. Все говнокоды работают.
Тут вопрос в том, опираетесь ли вы на правила проектирования и методологию или занимаетесь "творчеством".
Вопрос не про "будет ли работать". Вопрос - в какой ситуации это будет мешать на практике и приведет не к упрощению, а к усложнению работы.

anton_z
Сообщения: 206
Зарегистрирован: 2017.01.15, 15:01

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение anton_z » 2017.07.06, 10:23

vjik писал(а):
2017.07.06, 10:21
anton_z писал(а):
2017.07.06, 10:17
А зачем тогда спрашиваете? Наказания конечно не будет. Можно писать как угодно, программы будут работать. Все говнокоды работают.
Тут вопрос в том, опираетесь ли вы на правила проектирования и методологию или занимаетесь "творчеством".
Вопрос не про "будет ли работать". Вопрос - в какой ситуации это будет мешать на практике и приведет не к упрощению, а к усложнению работы.
Тут настолько все простое, что никакой разницы не будет. А если не видно разницы, зачем классы создавать? Пихать туда рефлексию... Вообще использвание рефлексии это code smell.

Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.06, 10:36

Убедил :) Загон это.

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

class NameDto
{
	public $firstName;
	public $lastName;
	public function __construct($config)
	{
		foreach ($config as $name => $value) {
			$this->{$name} = $value;
		}
	}
}
$nameDto = new NameDto([
	'firstName' => 'Иван',
	'lastName' => 'Иванов',
]);
var_dump($nameDto->firstName);
var_dump($nameDto->lastName);
var_dump((array)$nameDto);
var_dump(json_encode($nameDto));
По большому счёту тоже самое, а то, что можно изменить параметры - ну так не использовать эту возможность и всё.

anton_z
Сообщения: 206
Зарегистрирован: 2017.01.15, 15:01

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение anton_z » 2017.07.06, 10:43

Последнее - нормальный dto. Я вообще без конструктора делаю, просто публичные свойства. Все равно содержимое dto будет каким-либо образом проверяться и если что-то не так - исключение.

Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.06, 10:50

anton_z писал(а):
2017.07.06, 10:43
Последнее - нормальный dto. Я вообще без конструктора делаю, просто публичные свойства. Все равно содержимое dto будет каким-либо образом проверяться и если что-то не так - исключение.
С конструктором запись удобная. Вместо

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

$nameDto = new NameDto();
$nameDto->firstName = 'Иван';
$nameDto->lastName = 'Иванов';
… можно писать одну строчку:

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

$nameDto = new NameDto('Иван', 'Иванов');


Аватара пользователя
slavcodev
Сообщения: 3131
Зарегистрирован: 2009.04.02, 21:42
Откуда: Altea, Spain
Контактная информация:

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение slavcodev » 2017.07.07, 02:34

anton_z писал(а):
2017.07.06, 10:23
Вообще использвание рефлексии это code smell.
О как я рад слышать что не я один такого мнения.
Жду Yii 3!

Nex-Otaku
Сообщения: 181
Зарегистрирован: 2016.07.09, 21:07

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение Nex-Otaku » 2017.07.07, 09:42

Мой сегодняшний класс DTO выглядит так.

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

class BookDto
{
    public $author;
    public $year;
    public $isbn;
    
    public function __construct(
            $author,
            $year,
            $isbn
        )
    {
        $this->author = $author;
        $this->year = $year;
        $this->isbn = $isbn;
    }
}
Не изящно, зато проще пареной репы. Даже по пьяни не запутаешься.

Аватара пользователя
vjik
Сообщения: 20
Зарегистрирован: 2012.04.02, 18:59

Re: Базовый класс DTO и DTO для ошибок в сущностях и ActiveRecord. Покритикуйте…

Сообщение vjik » 2017.07.07, 09:44

Nex-Otaku писал(а):
2017.07.07, 09:42
Мой сегодняшний класс DTO выглядит так.
Не изящно, зато проще пареной репы. Даже по пьяни не запутаешься.
К этому и пришли :)

Ответить

Кто сейчас на конференции

Сейчас этот форум просматривают: нет зарегистрированных пользователей и 2 гостя