Об это я в общем-то и написал ментейнеру.
Если отделять Doctrine от Doctrine Specification, то это должен быть как минимум отдельный репозиторий и по сути новая либа с сохранением некоторых старых методов пользования.
Если кому интересно, есть подвижки по отделению Doctrine Specification от Doctrine (1, 2).
На сколько это осмыслено большой вопрос, но работа в этом направлении велась.
Мы приняли, что геттеры и сеттеры не соответствуют DTO, превращая его в анемичную модель
Наоборот. Мы решили, что DTO может содержать геттеры и сеттеры. Да, такие DTO похожи на анемичную модель, но DTO это не модель. DTO — это контейнер для передачи данных между уровнями приложения. Они не должны содержать бизнес логики и могут содержать только методы для доступа к данным (геттеры и сеттеры).
В сочетании с отсутствием контроля типов свойств в PHP
DTO появились в Java и используется в других языках, не все из которых поддерживают контроль типов свойств. В PHP собираются ввести контроль типов свойств, но до тех пор мы вынуждены пользоваться геттерами и сеттерами, если хотим контролировать типы.
Ruby, например, не поддерживает type hinting, но это не значит что в нем нельзя использовать DTO.
Возлагать ответственность на клиента нельзя, поскольку клиент ничего не знает о типах данных, которые должны быть помещены в DTO (в лучшем случае он знает названия полей)
Как раз наоборот. Только клиент знает структуру данных полученных от пользователя, и только он знает как создать DTO на основе пользовательских данных, и только он знает как отреагировать на ошибки валидации пользовательских данных.
Если пользовательские данные получены из web-формы, то необходимо пользователю сообщить об ошибках заполнения формы подсветив неправильные поля. Служба предметной области ничего не знает о web-форме. Она только выполняет бизнес транзакцию. Если ей передать некорректное данные, то она может только выбросить исключение. А о web-форме знает клиент. Клиент может проваледировать данные и отобразить ошибки.
Также, нужно учитывать, что служба предметной области может быть вызвана не только в результат обработки заполненной web-формы, но и, например, в результате вызова API, консольного вызова или заполнения нескольких комбинированных форм. В этом случае мы имеем несколько клиентов/контроллеров которые обрабатывают пользовательские данные и вызывают одну службу предметной области. И вот для того что-бы не зависит от клиента, нам пригодится единый интерфейс для службы предметной области.
Разница между использованием массива
function renameArticle(array $command)
и использованием DTO
function renameArticle(RenameArticle $command)
хотя бы в том, что у службы появляется более явный интерфейс. Клиенту не нужно знать какую структуру массива ожидает служба. Клиенту вообще не нужно знать ничего о реализации службы. Клиенту достаточно знать что у службы есть есть метод renameArticle() и на вход он ожидает DTO команду RenameArticle. То есть для работы клиента достаточно интерфейса:
interface ArticleService
{
public function renameArticle(RenameArticle $command);
}
Могу предположить, что нам потребуется, с одной стороны, какая-то фабрика DTO, а с другой — его валидатор
Совершенно верно. В этом нет ничего страшного.
// можно использовать фабрику
$command = $factory->create($data);
// можно использовать фабричный метод
$command = RenameArticle::createFromRequest($request);
// в роли фабричного метода может выступать конструктор
$command = new RenameArticle($data);
// можно и ручками заполнить
$command = new RenameArticle();
$command->article_id = $article->id();
$command->new_name = $data['name'];
Многие предпочитают использовать последние 2 метода заполнения DTO.
На счет валидатора. Валидатор это сервис и он единый для всех. А вот правила валидации специфичны для каждого DTO. И правильней описывать правила валидации в самом DTO, так как никто кроме самого DTO не знает как лучше проваледировать его.
Если я правильно понял, то это вытягивание счетчиков из внутренних таблиц PostgreSQL без обращения к основной таблице.
Ну ок. Это сойдет для простых запросов когда нам нужно просто общее число записей в таблице. Значительно чаще же нам нужны счетчики с выборкой.
Сколько активных пользователей?
Сколько активных сообществ?
Сколько активных пользователей в конкретном сообществе?
Сколько фоточек загрузил конкретный пользователь?
Сколько комментариев к конкретной статье?
Сколько просмотров у конкретной новости?
Сколько товаров в конкретной категории?
Как только в запросе появляется WHERE вся эта ваша система разбивается о стену.
В смысле как? Вы не знаете как создать объект в php? Или для вас проблема заполнить объект пользовательскими данными?
В TypeScript так же есть базовые типы
И в php есть базовые типы. И в php можно создавать свои типы и ваша библиотека для этого совсем не нужна.
Вы просто смешали у себя в голове понятия валидации и типизации. Вы сделали валидацию:
interface TypeInterface
{
public function validate($aValue): bool;
}
Стати́ческая типиза́ция — приём, широко используемый в языках программирования, при котором переменная, параметр подпрограммы, возвращаемое значение функции связывается с типом в момент объявления и тип не может быть изменён позже (переменная или параметр будут принимать, а функция — возвращать значения только этого типа). wiki
Ваша библиотека не реализует типизацию. Пользовательские типы в php реализуется так:
class СountryCode
{
private $code;
public function __construct(string $code)
{
if (strlen($code) != 2) {
throw new TypeExeption('Invaled country code.');
}
$this->code = $code;
}
public function code(): string
{
return $this->code;
}
}
Вот мы объявляем переменную типа СountryCode:
$code = new СountryCode($input['code']);
// для базовых типов это выглядит так
$age = (int) $input['age'];
Вот мы проверяем переменную на соответствие типу:
if ($code instanceof СountryCode) {
// do something
}
// для базовых типов это выглядит так
if (is_int($age)) {
// do something
}
Type hinting в аргументах
function some(СountryCode $code) {
// do something
}
// для базовых типов это выглядит так
function some(int $age) {
// do something
}
Type hinting в возвращаемых значениях
function some(): СountryCode {
return $code;
}
// для базовых типов это выглядит так
function some(): int {
return $age;
}
Пока ваша библиотека не будет реализовывать хотя бы поддержку type hinting для аргументов и возвращаемых значений, она не может называться типизацией.
Поэтому, делать ссылки только со slug неправильно. Это может привести к следующим проблемам:
Slug поменялся и ссылка отдает 404.
Это плохо для пользователя и для поисковиков. При частой смене slug можно и бан словить.
Slug сущности А сменился, а slug сущности Б сменился на slug сущности А.
Теперь по старой ссылке отдается другой контент. Это плохо для пользователя и для SEO.
Смена slug затрудняет идентификацию сущности в логах.
Ещё Google рекомендует в ссылках указывать числовой идентификатор (лень искать ссылку).
Решение описанных проблем это использование в URL, и ID и slug. И выглядеть это будет так:
/article/{id}-{slug}
Что это на даёт:
Мы гарантируем уникальность ссылок даже при смене slug.
Ссылки соответствуют рекомендациям Google.
В логах мы увидим ID сущности.
ЧПУ у нас сохраняется.
Мы можем избавится от 404 из-за смены slug.
Вы наверное уже догадались как избавится от 404, но я всё же поясню.
В контроллер выбираем сущность по ID из запроса. Проверяем текущий slug в сущности и slug из запроса. Если не совпадают, делаем 301 редирект на правильный slug.
а можно ли рассматривать в качестве DTO объект с геттерами и сеттерами, необходимыми при отсутствии типизированных свойств в PHP? Это же фактически поведение)
Нет. Геттеры и сеттеры это методы доступа к данным. Для этого есть термин — анемичная модель.
Например, в роли DTO можно рассматривать сам пришедший объект данных без всяких трансформаций — JSON-объект совершенно точно не имеет поведения)
JSON — объект только в js. В php же это просто строка, которую он может декодировать в массив или stdClass. Некоторые рассматривают массив как DTO, но это не правильно. Главное отличие DTO, от массива в том что объект имеет явный список свойств и он типизированныей, что позволяет идентифицировать данные.
Валидация — это действие, совершаемое над данными, и она может происходить в том числе в отношении их типа.
Я разве говорил что не надо проверять данные на соответсвие типа? Я говорил что правила:
Значение является строкой
Строка длинною 2 симвоал
Это два отдельных правила, а не одно. А вот правило:
Значение является кодом страны
Это одно правило. И для удовлетворения этого правила значение должно быть строкой длинной 2 символа, но это описано внутри типа код страны. Говоря вашими понятиями это будет выглядеть так:
field('code', country_code())
Но я попробую привести пример — INT(11). 11 — это длина строкового представления числа, которое занимает 4 байта.
Это пример из баз данных которые делались как универсальный сервис хранения данных. В приложении можно создавать свои типы и поэтому такие универсальные типы не нужны. Например:
Phone — в БД будет INT(11) для Российского региона. (int не самый лучший способ хранения телефона)
СountryCode — в БД будет CHAR(2)
ArticleId — в БД будет INT или CAR(36) если это UUID
Если же просто передать неправильные значения в функцию с ограниченными типами аргументов, то результатом будет исключение TypeError
Всё верно. Вы создали валидатор и пользоваться им нужно как валидатором. Как я и говорил ранее, правила можно комбинировать. Условно:
field('age', [int(), min(2), max(5)])
field('name', [string(), length(128)])
field('titles', [lot(), size(5)])
Зачем, кстати, проверять размер массива если нам важно только чтоб он соответствовал ожидаемой схеме если это ассоциативный массив? Если это нумерованный список то нам важен тип значения. Разве что у нас нумерованный список ограниченной длинны, но тогда его лучше описывать как структуру.
С объектами, кстати, тоже не понятно. Если вы проверяете пользовательские данные которые по определению не могут содержать объекты, зачем вам валидатор объектов?
GET запросы к сторонним сервисам лучше кешировать у себя и обновлять кеш по крону, а не по запросу от пользователя.
POST запросу лучше повторять с некоторым временным интервалом, чтоб не досить чужой сервис. А еще лучше их ставить в очередь на обработку, и в случае ошибки, повторно закидывать в очередь.
Проектировали как типизацию, а сделали валидацию.
Обычно, запросы от пользователя трансформируют в DTO, его уже валидируют и перенаправляют на доменный уровень.
То есть «строка длиной два символа» — это тип, а «код страны, в которой у нашей кампании есть филиал, отражённый в базе данных» — это уже валидация.
Неправильно.
Строка — это простой тип;
Строка длинною 2 символа — это валидация;
Код страны — это тип предметной области (ValueObject);
Код страны, в которой у нашей кампании есть филиал — это валидация уровня предметной области.
Если речь идёт о некоем целом диапазоне, то логичнее использовать int
То есть, если я не знаю точно может ли число ровняться 0, но точно знаю что оно не отрицательно, то я должен использовать int, вместо uint. Не кажется ли вам это нелогичным?
Вообще uint теряет свой смысл так как эквивалентен int(0).
то их разновидностей будет достаточно много и это будет только путать.
Так в этом весь смысл. Много простых, логичных и легко запоминающихся валидатор которые удобно комбинировать. В вашем же случае сходу возникают проблемы:
Какой порядок у аргументов функции?
Он конечно логичен, но все равно возникает желание перепроверить перед написанием кода.
Что, если нужно ограничить только верхнюю планку величины числа? Каждый раз передавать null первым аргументом?
Указывать минимальный размер числа нужно гораздо реже. Если сменить порядок аргументов, то мы возвращаемся к проблеме №1
Валидаторы Min и Max не имеют смысла вне контекста чисел и будут выполнять ровно те же проверки
А для этого у нас есть type hinting в php 7. Конечно в этом случае нельзя ограничить размер floot, но это и не надо на мой взгляд, так как в этом случае еще может возникнуть необходимость ограничить количество знаков после запятой, а это уже совсем другая задача.
int — эквивалент is_int();
uint — эквивалент is_int() + >=0;
min — эквивалент:
function(int $int, int $min) {
return $int >= $min;
}
длину имеют строки и массивы
Ну так, для строки можно использовать length(), а для массивов size(), по аналогии с функциями из php.
ObjectType без класса — это скорее костыль, недоработка в этом)
Так тоже легко решается.
object() — эквивалент is_object();
instanceof() — эквивалент instanceof. Если будет передан не объект, получим false.
uint это число >=0. uint вполне может быть интервал [2,5).
function number(float $aMin = null, float $aMax = null): NumericType;
function int(int $aMin = null, int $aMax = null): IntType;
function uint(int $aMax = null): UIntType;
Ограничение $aMin, $aMax и $aLength нужно делать отдельным валидатором. Это нарушение SRP.
Аргумент у object() то же самое. Нарушение SRP.
struct() спорный валидатор. По мне так лучше проверять через объект.
Вообще, готовых валидатор великое множество. Ваш интересен тем что позволяет валидировать многомерные массивы. Хотя и для этого полно других инструментов.
Мне больше нравится валидатор Symfony. Простое описание правил и удобно использовать в middleware. Для многомерных массивов строю композит из DTO+payload.
Зачем счетчики вырезали? Их надо было просто сделать по нормальному, а не в лоб и былоб всё хорошо.
Счетчики можно кешировать;
Счетчики можно хранить в Redis и пересчитывать по cron;
Счетчики можно хранить в Redis и обновлять по событию из Domain layer.
Последний вариант предпочтительней так как мы всегда имеем актуальные данные в счетчиках и не трогаем БД вообще.
ORM зло? С чего бы? Единственный запрос которые ORM генерит сама, это получение связей, но эти запросы максимально оптимальны. Все остальные запросы составляет сам разработчик используя всякие QueryBuilder-ы которые очень точно транслируют запрос в SQL.
QueryBuilder-ы имеют плюсы по сравнению с plain text запросами, хотя бы потому что их можно использовать в спецификациях и сворачивать большую портянку до одной строчки кода которая очень чётко отражает то что она делвет:
Бывают супер сложные запросы с подзапросами, UNION и алгоритмов на основе нумерации строк. Такие запросы можно описать через QueryBuilder с помощью бубна и такой-то матери, но это не нужно. Такие запросы пишутся один раз и используются только в одном месте. Их вполне можно писать и как plain text. Таких запросов один на сто. И все в команде о них знают.
Единственное что делает не оптимально, те ORM с которыми я работал, это выборка в связке ManyToMany одних элементов, по id вторых:
SELECT
u.*
FROM
user AS u
INNER JOIN
users_groups AS ug
ON
u.id = ug.user_id
INNER JOIN
group AS g
ON
g.id = ug.group_id
WHERE
u.enabled = TRUE AND
g.id = 2;
Второй JOIN делает ORM и он здесь не нужен, но из-за выборки по primary key, мы не имеем проблем с производительелстью.
Загрузка необходимых зависимостей через composer require symfony/bundle
Регистрация бандла в app/AppKernel.php
Регистрация маршрутов, которые предоставляет наш новый бандл, если таковые имеются
Регистрация необходимых настроек для бандла в app/config/config.yml
Реализовал в своем проекте пункты 2, 3, 4 несколько лет назад (кажется году эдак в 2013). На основе этого функционала сделал систему плагинов в приложении, а потом еще добавил систему обновлений (self-update).
И сводилось все действительно к одно команде:
composer require vendor/depdency
Планировал написать об этом статью на хабре, но руки так и не дошли.
Попробовал реализацию через рефлексию. Оказалось действительно в чем-то удобнее. Спасибо что просветили.
Проблему с человеческим описанием в myclabs/php-enum можно решить так:
final class Action extends Enum
{
const VIEW = 'view';
const EDIT = 'edit';
// можно использовать для radio/checkbox/select
public static function choices()
{
$choices = [];
foreach (self::values() as $value) {
$choices[$value->getValue()] = (string) $value;
}
return $choices;
}
public function __toString()
{
return 'acme.demo.action.'.strtolower($this->getKey());
}
}
Но вот решил я сравнить производительность реализации через рефлексию и с явным описанием вариантов значений и получилось что рефлексия в 3-4 раза медленнее в зависимости от версии php.
$ php tests/benchmark.php 100000
Reflection enum: 1.25 MiB - 14079 ms
Reflection enum no magic: 1.25 MiB - 11286 ms
Explicit enum: 1.25 MiB - 3221 ms
Тест no magic это тест без использования магических функций __call() и __callStatic() и даже он оказывается медленнее в 3 раза.
Отсюда делаем вывод. За удобство нужно платить.
Я бы посоветовал, прежде чем делать большой PR, сначала обсудить его в issues с мейнтейнером. Это поможет избавиться от лишней работы и предвидеть правки PR. Хотя по моему опыту на PR мейнтейнеры реагируют активнее.
Никакой информации о классе (родители, интерфейсы, трейты).
Никакой информации о интерфейсе класса (методы публичные и приватные).
Никакой информации о доступе к свойствам класса. Свойства есть, но не известно публичные они или приватные и если приватные, то как их получить.
Особенно эта проблема ощущается когда проваливаешься по иерархии на 2, 3, 4 и тд уровни.
Один из главных минусов xdebug — медленный и забивает диск логами профыйлинга, если явно не указать писать лог в один файл. Что не скажешь о либе которую я привел.
Настраивать профайлинг для Web и CLI сложнее чем для либы которую я упомянул (одна строчка в конфиге и все).
Не скажу что xdebug плох. Просто мне лично он на подходит. Хотя бы потому что медленный.
Уже несколько лет пользуюсь вот этой либой для отладки кода. Удобный аналог var_dump. Гараздо более читаемый и юзабельный. Рендер под web и в cli. Удобное отображение ошибок. Удобней чем в xdebug.
А что мешает написать свое решение и просто выложить его?
@springimport я в результате так и делаю. Как я и сказал, пишу свой велосипеде и выкладываю его на GitHub. Но это не совсем правильно, о чем я уже написал.
Что хорошего в том что каждый будет писать свою реализацию функциональности и постить ее на GitHub?
Просто для примера, список реализаций CQRS на PHP.
Я просмотрел около 20 проектов из списка, не найдя удобно мне сделал свою реализацию и думаю выложить её на GitHub. Хорошо ли это? Не уверен.
Об это я в общем-то и написал ментейнеру.
Если отделять Doctrine от Doctrine Specification, то это должен быть как минимум отдельный репозиторий и по сути новая либа с сохранением некоторых старых методов пользования.
Если кому интересно, есть подвижки по отделению Doctrine Specification от Doctrine (1, 2).
На сколько это осмыслено большой вопрос, но работа в этом направлении велась.
Наоборот. Мы решили, что DTO может содержать геттеры и сеттеры. Да, такие DTO похожи на анемичную модель, но DTO это не модель. DTO — это контейнер для передачи данных между уровнями приложения. Они не должны содержать бизнес логики и могут содержать только методы для доступа к данным (геттеры и сеттеры).
DTO появились в Java и используется в других языках, не все из которых поддерживают контроль типов свойств. В PHP собираются ввести контроль типов свойств, но до тех пор мы вынуждены пользоваться геттерами и сеттерами, если хотим контролировать типы.
Ruby, например, не поддерживает type hinting, но это не значит что в нем нельзя использовать DTO.
Как раз наоборот. Только клиент знает структуру данных полученных от пользователя, и только он знает как создать DTO на основе пользовательских данных, и только он знает как отреагировать на ошибки валидации пользовательских данных.
Если пользовательские данные получены из web-формы, то необходимо пользователю сообщить об ошибках заполнения формы подсветив неправильные поля. Служба предметной области ничего не знает о web-форме. Она только выполняет бизнес транзакцию. Если ей передать некорректное данные, то она может только выбросить исключение. А о web-форме знает клиент. Клиент может проваледировать данные и отобразить ошибки.
Также, нужно учитывать, что служба предметной области может быть вызвана не только в результат обработки заполненной web-формы, но и, например, в результате вызова API, консольного вызова или заполнения нескольких комбинированных форм. В этом случае мы имеем несколько клиентов/контроллеров которые обрабатывают пользовательские данные и вызывают одну службу предметной области. И вот для того что-бы не зависит от клиента, нам пригодится единый интерфейс для службы предметной области.
Разница между использованием массива
и использованием DTO
хотя бы в том, что у службы появляется более явный интерфейс. Клиенту не нужно знать какую структуру массива ожидает служба. Клиенту вообще не нужно знать ничего о реализации службы. Клиенту достаточно знать что у службы есть есть метод
renameArticle()
и на вход он ожидает DTO командуRenameArticle
. То есть для работы клиента достаточно интерфейса:Совершенно верно. В этом нет ничего страшного.
Многие предпочитают использовать последние 2 метода заполнения DTO.
На счет валидатора. Валидатор это сервис и он единый для всех. А вот правила валидации специфичны для каждого DTO. И правильней описывать правила валидации в самом DTO, так как никто кроме самого DTO не знает как лучше проваледировать его.
Как результат, у нас нет ни фабрик, ни валидаторов, а есть только DTO.
Если я правильно понял, то это вытягивание счетчиков из внутренних таблиц PostgreSQL без обращения к основной таблице.
Ну ок. Это сойдет для простых запросов когда нам нужно просто общее число записей в таблице. Значительно чаще же нам нужны счетчики с выборкой.
Как только в запросе появляется
WHERE
вся эта ваша система разбивается о стену.В смысле как? Вы не знаете как создать объект в php? Или для вас проблема заполнить объект пользовательскими данными?
И в php есть базовые типы. И в php можно создавать свои типы и ваша библиотека для этого совсем не нужна.
Вы просто смешали у себя в голове понятия валидации и типизации. Вы сделали валидацию:
Ваша библиотека не реализует типизацию. Пользовательские типы в php реализуется так:
Вот мы объявляем переменную типа
СountryCode
:Вот мы проверяем переменную на соответствие типу:
Type hinting в аргументах
Type hinting в возвращаемых значениях
Пока ваша библиотека не будет реализовывать хотя бы поддержку type hinting для аргументов и возвращаемых значений, она не может называться типизацией.
Поэтому, делать ссылки только со slug неправильно. Это может привести к следующим проблемам:
Это плохо для пользователя и для поисковиков. При частой смене slug можно и бан словить.
Теперь по старой ссылке отдается другой контент. Это плохо для пользователя и для SEO.
Ещё Google рекомендует в ссылках указывать числовой идентификатор (лень искать ссылку).
Решение описанных проблем это использование в URL, и ID и slug. И выглядеть это будет так:
Что это на даёт:
Вы наверное уже догадались как избавится от 404, но я всё же поясню.
В контроллер выбираем сущность по ID из запроса. Проверяем текущий slug в сущности и slug из запроса. Если не совпадают, делаем 301 редирект на правильный slug.
Лёгким мановением руки решили все проблемы)))
Нет. Геттеры и сеттеры это методы доступа к данным. Для этого есть термин — анемичная модель.
JSON — объект только в js. В php же это просто строка, которую он может декодировать в массив или
stdClass
. Некоторые рассматривают массив как DTO, но это не правильно. Главное отличие DTO, от массива в том что объект имеет явный список свойств и он типизированныей, что позволяет идентифицировать данные.Я разве говорил что не надо проверять данные на соответсвие типа? Я говорил что правила:
Это два отдельных правила, а не одно. А вот правило:
Это одно правило. И для удовлетворения этого правила значение должно быть строкой длинной 2 символа, но это описано внутри типа код страны. Говоря вашими понятиями это будет выглядеть так:
Это пример из баз данных которые делались как универсальный сервис хранения данных. В приложении можно создавать свои типы и поэтому такие универсальные типы не нужны. Например:
INT(11)
для Российского региона.(int не самый лучший способ хранения телефона)
CHAR(2)
INT
илиCAR(36)
если это UUIDВсё верно. Вы создали валидатор и пользоваться им нужно как валидатором. Как я и говорил ранее, правила можно комбинировать. Условно:
Зачем, кстати, проверять размер массива если нам важно только чтоб он соответствовал ожидаемой схеме если это ассоциативный массив? Если это нумерованный список то нам важен тип значения. Разве что у нас нумерованный список ограниченной длинны, но тогда его лучше описывать как структуру.
С объектами, кстати, тоже не понятно. Если вы проверяете пользовательские данные которые по определению не могут содержать объекты, зачем вам валидатор объектов?
Пара советов:
Проектировали как типизацию, а сделали валидацию.
Обычно, запросы от пользователя трансформируют в DTO, его уже валидируют и перенаправляют на доменный уровень.
Неправильно.
То есть, если я не знаю точно может ли число ровняться 0, но точно знаю что оно не отрицательно, то я должен использовать
int
, вместоuint
. Не кажется ли вам это нелогичным?Вообще
uint
теряет свой смысл так как эквивалентенint(0)
.Так в этом весь смысл. Много простых, логичных и легко запоминающихся валидатор которые удобно комбинировать. В вашем же случае сходу возникают проблемы:
Он конечно логичен, но все равно возникает желание перепроверить перед написанием кода.
null
первым аргументом?Указывать минимальный размер числа нужно гораздо реже. Если сменить порядок аргументов, то мы возвращаемся к проблеме №1
А для этого у нас есть type hinting в php 7. Конечно в этом случае нельзя ограничить размер
floot
, но это и не надо на мой взгляд, так как в этом случае еще может возникнуть необходимость ограничить количество знаков после запятой, а это уже совсем другая задача.is_int()
;is_int()
+>=0
;Ну так, для строки можно использовать
length()
, а для массивовsize()
, по аналогии с функциями из php.Так тоже легко решается.
object()
— эквивалентis_object()
;instanceof()
— эквивалентinstanceof
. Если будет передан не объект, получимfalse
.$aMin
,$aMax
и$aLength
нужно делать отдельным валидатором. Это нарушение SRP.object()
то же самое. Нарушение SRP.struct()
спорный валидатор. По мне так лучше проверять через объект.Вообще, готовых валидатор великое множество. Ваш интересен тем что позволяет валидировать многомерные массивы. Хотя и для этого полно других инструментов.
Мне больше нравится валидатор Symfony. Простое описание правил и удобно использовать в middleware. Для многомерных массивов строю композит из DTO+payload.
Зачем счетчики вырезали? Их надо было просто сделать по нормальному, а не в лоб и былоб всё хорошо.
Последний вариант предпочтительней так как мы всегда имеем актуальные данные в счетчиках и не трогаем БД вообще.
ORM зло? С чего бы? Единственный запрос которые ORM генерит сама, это получение связей, но эти запросы максимально оптимальны. Все остальные запросы составляет сам разработчик используя всякие QueryBuilder-ы которые очень точно транслируют запрос в SQL.
QueryBuilder-ы имеют плюсы по сравнению с plain text запросами, хотя бы потому что их можно использовать в спецификациях и сворачивать большую портянку до одной строчки кода которая очень чётко отражает то что она делвет:
Бывают супер сложные запросы с подзапросами,
UNION
и алгоритмов на основе нумерации строк. Такие запросы можно описать через QueryBuilder с помощью бубна и такой-то матери, но это не нужно. Такие запросы пишутся один раз и используются только в одном месте. Их вполне можно писать и как plain text. Таких запросов один на сто. И все в команде о них знают.Единственное что делает не оптимально, те ORM с которыми я работал, это выборка в связке ManyToMany одних элементов, по id вторых:
Второй
JOIN
делает ORM и он здесь не нужен, но из-за выборки поprimary key
, мы не имеем проблем с производительелстью.Реализовал в своем проекте пункты 2, 3, 4 несколько лет назад (кажется году эдак в 2013). На основе этого функционала сделал систему плагинов в приложении, а потом еще добавил систему обновлений (self-update).
И сводилось все действительно к одно команде:
Планировал написать об этом статью на хабре, но руки так и не дошли.
Ошибся со ссылкой для happy-types/enumerable-type.
огорчу. кеш есть.
Для сравнения добавил в тест:
Результат:
билд и сорцы.
Попробовал реализацию через рефлексию. Оказалось действительно в чем-то удобнее. Спасибо что просветили.
Проблему с человеческим описанием в myclabs/php-enum можно решить так:
Но вот решил я сравнить производительность реализации через рефлексию и с явным описанием вариантов значений и получилось что рефлексия в 3-4 раза медленнее в зависимости от версии php.
Тест no magic это тест без использования магических функций
__call()
и__callStatic()
и даже он оказывается медленнее в 3 раза.Отсюда делаем вывод. За удобство нужно платить.
Я бы посоветовал, прежде чем делать большой PR, сначала обсудить его в issues с мейнтейнером. Это поможет избавиться от лишней работы и предвидеть правки PR. Хотя по моему опыту на PR мейнтейнеры реагируют активнее.
Врет. Я только выкладывал сорцы по просьбе трудящихся. Автор не я.
Ох, если бы тот же самый.
Не скажу что xdebug плох. Просто мне лично он на подходит. Хотя бы потому что медленный.
PS: Я не пиарюсь. Это не моя либа.
Уже несколько лет пользуюсь вот этой либой для отладки кода. Удобный аналог var_dump. Гараздо более читаемый и юзабельный. Рендер под web и в cli. Удобное отображение ошибок. Удобней чем в xdebug.
@springimport я в результате так и делаю. Как я и сказал, пишу свой велосипеде и выкладываю его на GitHub. Но это не совсем правильно, о чем я уже написал.
Что хорошего в том что каждый будет писать свою реализацию функциональности и постить ее на GitHub?
Просто для примера, список реализаций CQRS на PHP.
Я просмотрел около 20 проектов из списка, не найдя удобно мне сделал свою реализацию и думаю выложить её на GitHub. Хорошо ли это? Не уверен.