Думаю, многие замечали, что зачастую создание проекта сводится не столько к программированию сколько к написанию кода интеграции нескольких готовых решений. Иногда такие комбинации превращаются в новые решения, которые можно неоднократно использовать в последующих задачах.
Перейдем к конкретной «ходовой» задаче — объектная прослойка для работы с базами данных в PHP. Решений великое множество, начиная от PDO и заканчивая многоуровневыми (и, на мой взгляд, не совсем уместными в PHP) ORM движками.
Большинство этих решений перекочевали в PHP из других платформ. Но зачастую авторы не учитывают особенности PHP, которые позволили бы резко упростить как написание, так и использование портируемых конструкций.
Одной из распространенных архитектур для данного класса задач является паттерн Active Record. В частности, по этому шаблону строятся так называемые Entity (сущности), в том или ином виде использующиеся в ряде платформ, начиная от персистентных бинов в EJB3 заканчивая EF в .NET.
Итак, построим подобную конструкцию для PHP. Соединим между собой две клёвые штуки — готовую библиотеку ADODB и слаботипизированность и динамические свойства объектов языка PHP.
Одной из многочисленных фич ADODB является так называемая автогенерация SQL запросов для вставки (INSERT) и обновления (UPDATE) записей на основе ассоциативных массивов с данными.
Собственно нет ничего военного взять массив, где ключи — имена полей а значения — соответственно, данные и сгенерировать строку SQL запроса. Но ADODB делает это более интеллектуально. Запрос строится на основе структуры таблицы, которая предварительно считывается с схемы БД. В результате во-первых, в sql попадают только существующие поля а не всё подряд, во-вторых, учитывается тип поля — для строк добавляются кавычки, форматы дат могут формироваться на основе timestamp если ADODB видит оный вместо строки в передаваемом значении и т.д.
Теперь зайдем со стороны PHP.
Представим такой класс (упрощенно).
class Entity{
protected $fields = array();
public final function __set($name, $value) {
$this->fields[$name] = $value;
}
public final function __get($name) {
return $this->fields[$name];
}
}
Передавая внутренний массив библиотеке ADODB мы можем автоматически генерировать SQL запросы для обновления записи в БД данным объектом, При этом, не нужны громоздкие конструкции маппинга полей таблиц БД на поля объекта сущности на основе XML и тому подобного. Нужно только чтобы имя поля совпадало со свойством объекта. Поскольку то, как именуются поля в БД и поля объекта, для компьютера значения не имеет, то нет никаких причин чтобы они не совпадали.
Покажем, как это работает в конечном варианте.
Код законченного класса расположен на Gist. Это абстрактный класс, содержащий минимум необходимого для работы с БД. Отмечу, что данный класс — упрощенная версия решения, отработанного на нескольких десятках проектов.
Представим, что у нас такая табличка:
CREATE TABLE `users` (
`username` varchar(255) ,
`created` date ,
`user_id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`user_id`)
)
Тип БД не имеет значения — ADODB обеспечивает переносимость для всех распространенных серверов БД.
Создадим класс сущности Пользователь, на основе класса Entity
/**
* @table=users
* @keyfield=user_id
*/
class User extends Entity{
}
Собственно и все.
Используется просто:
$user = new User();
$user->username='Вася Пупкин';
$user->created=time();
$user->save(); //сохраняем в хранилище
// загрузим опять
$thesameuser = User::load($user->user_id);
echo $thesameuser ->username;
Таблицу и ключевое поле указываем в псевдоаннотациях.
Также можем указать представление (к примеру, view=usersview) если, как это часто бывает, сущность выбирается на основе своей таблицы с приджойнеными или вычисляемыми полями. В этом случае выбираться данные будут из представления а обновляться будет таблица. Кому не нравятся такие аннотации могут переопределить метод getMetatada() и указать параметры таблицы в возвращаемом массиве.
Что еще полезного представляет класс Entity в данной реализации?
Например, мы можем переопределить метод init(), который вызывается после создания экземпляра Entity, чтобы инициализировать дату создания по умолчанию.
Или перегрузить метод afterLoad(), который автоматически вызывается после загрузки сущности из БД, чтобы преобразовать дату в timestamp для дальнейшего более удобного использования.
В результате получим не намного более сложную конструкцию.
/**
* @table=users
* @view=usersview
* @keyfield=user_id
*/
class User extends Entity{
protected function init() {
$this->created = time();
}
protected function afterLoad() {
$this->created = strtotime($this->created);
}
}
Также можно перегрузить методы beforeSave и beforeDelete и другие события жизненного цикла где можно, например, выполнить валидацию перед сохранением или какие нибудь другие действия — например, убрать за собой картинки из аплоада при удалении пользователя.
Загружаем список сущностей по критерию (по сути условия для WHERE ).
$users = User::load("username like 'Пупкин' ");
Также класс Entity позволяет выполнить произвольный, «нативный» так сказать SQL запрос. Например, мы хотим вернуть список пользователей с какими нибудь группировками по статистике. Какие конкретно поля вернутся, не имеет значения (главное чтобы там было user_id, если есть необходимость дальнейшей манипуляции сущностью), нужно только знать их наименования чтобы обратится к выбранным полям. При сохранении сущности, как очевидно из вышеприведенного, тоже не надо заполнять все поля, какие будут присутствовать в объекте сущности, те и пойдут в БД. То есть нам не нужно создавать дополнительные классы для произвольных выборок. Примерно как анонимные структуры при выборке в EF только здесь это тот же класс сущности со всеми методами бизнес-логики.
Строго говоря, вышеприведенные методы получения списков несколько выходят за пределы паттерна AR. По сути — это фабричные методы. Но как завещал старик Оккама, не будем плодить сущности сверх необходимого и городить отдельный Entity Manager или типа того.
Поскольку загрузка и сохранение сущностей — это базовые операции, которые занимают две трети обращений к БД, мы можем избежать сотен строк рутинного кодирования.
Заметим что вышеприведенное — просто классы PHP и их можно как угодно расширять и модифицировать, дописывать в сущности (или базовый класс Entity) свойства и методы бизнес-логики. То есть мы получаем не просто копию строки таблицы БД, а именно бизнес-сущность как часть объектной архитектуры приложения.
Кому это может быть полезно? Разумеется, не прокачанным разрабам, которые считают что использование чего то проще доктрины — не солидно, и не перфекционистам, уверенным, что если решение не вытягивает миллиард обращений к Бд в секунду то это не решение. Судя по форумам, перед многими обычными разработчиками, работающими над обычными (коих 99.9%) проектами рано или поздно возникает проблема найти простой и удобный объектный способ для доступа к БД. Но сталкиваются с тем, что большинство решений либо неоправданно наворочены, либо являются частью какого-либо фреймворка.
P.S. Вынес решение из фреймворка отдельным проектом GitHub
Комментарии (236)
bromzh
04.05.2016 14:10+3Поскольку то, как именуются поля в БД и поля объекта, для компьютера значения не имеет, то нет никаких причин чтобы они не совпадали.
Зато для компиляторов имеет значение, т.к. есть такая штука, как зарезервированные слова. Что, если уже есть готовая таблица, в которой колонка названа
class
илиpublic
?
клёвые штуки…
слаботипизированность
уж слабая типизация ничего, кроме головной боли, обычно не несёт.
caballero
04.05.2016 14:19ну, человек должен думать когда задает имена полям как в коде так и в структуре БД.
Насчет слаботипизации — это отдельная долгая дискусия которой уверен на хабре было полно. Опять же в PHP с каждой версией ввожить все больше контроля типов если это нужно. Но главное — отается и нетипизированые возможности. Кстати в той же яве появилось куча скриптовых языков типа Groovy именно для того чтобы сочетать мощь типизировного языка с более простом кодированием.bromzh
04.05.2016 14:40+2ну, человек должен думать когда задает имена полям как в коде так и в структуре БД.
А если БД уже есть? И нужно пристроится к готовой БД. Колонку переименовать тоже не всегда можно. Не во всех же случаях ты и код пишешь, и БД проектируешь.
На счёт типизации — вы явно путаете слабую типизацию с динамической. Ну да ладно.
caballero
04.05.2016 14:57я и не обещал лекарство от всех проблем. Если Бд уже есть то там скорее всего и программный код уже есть.
Впрочем так и не понял зачем переименовывать поля в БД — суть в том что для данного решения пофиг какое там поле. Если кто то влепил имя поля как то противоречащее ключевым словам PHP — то напишете на это конкретное свойство гетер и сеттер в классе сущности и все дела.
caballero
04.05.2016 14:48я и не обещал лекарство от всех проблем. Если Бд уже есть то там скорее всего и программный код уже есть.
Впрочем так и не понял зачем переименовывать поля в БД — суть в том что для данного решения пофиг какое там поле. Если кто то влепил имя поля как то противоречащее ключевым словам PHP — то напишете на это конкретное свойство гетер и сеттер в классе сущности и все дела.
symbix
04.05.2016 14:49Простейший AR на одну сущность действительно пишется в экран кода.
Вот только сущности обычно связаны.
Попробуйте добавить туда хотя бы hasOne и hasMany. Боюсь, сразу окажется проще взять готовое решение типа Eloquent :-)caballero
04.05.2016 14:56-1Не буду и пробовать потому как это не ORM.
Но я не зря написал что решение отработано и все шишки уже набиты и грабли уже наступлены.
Нет никаких проблем со связями.
Прочитайте внимательно абзац с нативным SQL.
Кроме того в отличие от например явы, контекст PHP разрушается после отработки страницы — посему нет никакого практического смысла вытягивать гроздья взаимосвязанных сущностей.
WST
04.05.2016 15:24+1> Кроме того в отличие от например явы, контекст PHP разрушается после отработки страницы
Работать с базой данных может понадобиться не только в веб-приложениях, и даже если в веб-приложениях, то необязательно в таких, которые реализуют принцип «PHP is meant to die». Конечно, иных нынче мало, но ведь в будущем ситуация может измениться — вдруг разработчики фреймворков внезапно массово заинтересуются чем-то типа PHP-SGI или хотя бы просто задействуют какой-нибудь ReactPHP.andrewnester
04.05.2016 15:26WST опередили меня :)
недавно столкнулся с проблемой выбора слоя для работы с БД для приложения построенного на основе ReactPHP
caballero
04.05.2016 15:46Как говорится нельзя объять необъятное. Как я отметил ориентируюсь на большиство обычных задач которые нужны повседневно.
andrewnester
04.05.2016 15:24+4зачем использовать ADODB, если есть PDO?
Из минусов:
- Нельзя конфигурировать конекшен к базе: для разных Entity — разные конекшены (если переопределить getConnect, то конекшен переопределиться для всех классов)
- Использование магических __get, __set (что если я присвою значение несуществующему в БД полю и вызову save() ?)
- Быстродействие (парсить мтаданные для каждой сущности одного класса не кажется быстрым)
- По самому коду очень много вопросов, например https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L274 а потом вы возвращаете false
И пару вопросов:
- Как вы решаете проблему валидации?
- Где вы держите бизнес-логику?
- Как тестируете сущности?
caballero
04.05.2016 15:38-3зачем использовать ADODB, если есть PDO
вообще то я написал почему именно ADODB.
Нельзя конфигурировать конекшен к базе: для разных Entity — разные конекшены
а нафига? проект с больше чем одной БД? Давайте оставим экзотику в стороне.
Использование магических __get, __set (что если я присвою значение несуществующему в БД полю и вызову save() ?)
как я писал выше — абсолютно ничего. Потому и ADODB.
https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L274
косяк несущественный но спасибо.
Как вы решаете проблему валидации?
в методе befireSave() — по сути это как бы событие жизненного цикла — срабатывает перед записью в БД. Если нужна валидация — переопределяете метод в сущности.
Где вы держите бизнес-логику?
смотря какую. Если бизнес-логика относится к бизнес-сущности (класс User к примеру) то в этом классе, разумеется.
Как тестируете сущности?
тестирование зависит от проекта в котором данное решение используется. не вижу какой то специфики.
andrewnester
04.05.2016 17:41вообще то я написал почему именно ADODB.
как я писал выше — абсолютно ничего. Потому и ADODB.
Так дело в том, что это наоборот не очень. Я глядя на код где устанавливается поле и потом сущность сохраняет ожидаю, что оно будет сохранено. Потому что не указано какие поля виртуальные, а какие реальные в БД.
Да и тянуть целую библиотеку ради возможности из массива сделать UPDATE/INSERT запрос тоже не вижу смысла, это пишется очень просто.
а нафига? проект с больше чем одной БД? Давайте оставим экзотику в стороне.
не такая уж и экзотика.
как вы тогда справляетсь с разными конфигурациями БД на живом, тестовм и локальном сервере?
тестирование зависит от проекта в котором данное решение используется. не вижу какой то специфики.
валидация сущности в beforeSave, он protected. Я хочу протестировать валидацию. Как мне это сделать без БД?
Я к тому, что класс жёстко завязан на БД и нельзя мокнуть её, а мокать БД в тестах — первое дело
косяк несущественный но спасибо.
https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L96
а ещё вместо таких конструкций проще вызвать метод сразу со static::caballero
04.05.2016 20:59-2как вы тогда справляетсь с разными конфигурациями БД на живом, тестовм и локальном сервере?
поменял адрес в конфиге и все дела.
Я к тому, что класс жёстко завязан на БД и нельзя мокнуть её, а мокать БД в тестах — первое дело
не хочу усложнять что можно не усложнять. Пока нормально обхожусь комплексным тестированием на уровне приложения.
а ещё вместо таких конструкций проще вызвать метод сразу со static::
не совсем понял но мне нужно имя как раз вызывающего класса-наследника.
я и не претендую на идеальное качество кода — это я писал не просто так а для фреймворка который тоже писал не для красоты. В этом случае хочется сразу писать конечные задачи а не возится с моками. Собственно на этих задачах я все и отлаживал. Это не по классике но это опенсорс, я за деньги не продаю.
Загляните для интереса в некоторые очень популярные проекты — там не то что моки и ORM там дикая смесь кода разметки и запросов к БД.
Но если людям нужно ехать а не шашечки то как то справляются.
lair
04.05.2016 15:25+2Одной из распространенных архитектур для данного класса задач является паттерн Active Record. В частности, по этому шаблону строятся так называемые Entity (сущности), в том или ином виде использующиеся в ряде платформ, начиная от персистентных бинов в EJB3 заканчивая EF в .NET.
А ничего, что в EF никакого ActiveRecord нет, там честный ORM?
$thesameuser = User::load($user->user_id);
Статический метод? А тестировать как?
(про повторное использование подобной статики тоже вопрос открытый)
caballero
04.05.2016 15:42-9Статический метод? А тестировать как?
это к разработчикам PHP — зачем они ввели в язык статические методы которые оказывается невозможно тестировать.
вы как обычно придираетесь не по существу. Будем считать что вы уже отметились коментом и в этой статье тоже. Поставьте галочку что не пропустили и все.
lair
04.05.2016 15:45+2это к разработчикам PHP — зачем они ввели в язык статические методы которые оказывается невозможно тестировать.
Нет, это конкретно к вам — потому что вы используете эти методы. Каждому инструменту своя область применения.
(Я так понимаю, по поводу EF ответа не будет)
caballero
04.05.2016 15:59-8Я так понимаю, по поводу EF ответа не будет
более бесполезного занятия чем дискутировать что такое EF в статье про PHP не нашли?lair
04.05.2016 16:01+7(а теперь вы с той же элегантностью ушли от вопроса про тестирование)
более бесполезного занятия чем дискутировать что такое EF в статье про PHP не нашли?
Ну, вы же зачем-то помянули EF в статье про PHP? Ложные утверждения — они везде ложные утверждения.
Fesor
06.05.2016 14:48+1это к разработчикам PHP — зачем они ввели в язык статические методы которые оказывается невозможно тестировать.
Статические методы придуманы просто для других вещей, в них удобно складывать stateless-логику, которая не должна принодлижать одному конкретному инстансу (или если инстанса еще нет). Чаще всего использование статики сводится к фабричным методам, реже — какие-нибудь проверки которые нужно производить над несколькими сущностями:
$owner = User::whoInCharge(...$users);
, в этом случае статический методwhoInCharge
просто пробежится по списку сущностей, заберет например значение приватных полей и проголосует кто из них главнее. Пример синтетический, но изредко такое бывате нужно.
Их проще воспринимать как обычные функции, которым положено знать о внутренней реализации объектов. И вот такие штуки тестировать проблем нет. А теперь посмотрим на ваш случай:
public static function getConnect() { if (self::$conn instanceof \ADOConnection) { return self::$conn; } self::$conn = \ADONewConnection(self::$driver); self::$conn->Connect("localhost", "root", "root", "test"); self::$conn->Execute("SET NAMES 'utf8'"); return self::$conn; }
Тут мы просто забили на управление зависимостями, они не передаются внутрь а стало быть "замокать" это дело мы не сможем. Да и не станем (никто в здравом уме не будет мокать коннекшен или квери билдер, слишком большая завязка на текущую реализацию выходит).
В целом проблема решается извне. То есть у нас должен быть отдельный компонент — Finder, который уже занимается выборками объектов и т.д. Словом нужно всего-лишь "добавить классов" и система станет уже намного более поддерживаемой.
Сейчас ваше решение тянет на одноразовое а стало быть использовать его я бы никому не рекомендовал, с учетом того какое огромное количество качественных решений доступно в packagist.
caballero
06.05.2016 15:26я уже писал — статические методы — по сути фабричные непосредственно к AR не относятся и с точки зрения академического подхода — должны быть вынесены в отдельный класс. Но с практической точки зрения не вижу смысла.
Но не все. Например метод find никак не вынести потому что он по имени класса определяет с какой сущностью надо работать. ПО мне так удобнее чем каждый раз указывать параметр.
Что касается моков — да, с академической точки зрения должны быть моки, покрытие тестами и т.д. Но здесь бОльшую часть работы выполняет ADODB. Если убрать слой работы с БД то «мокать» останется только ассоциативный массив и магичские методы что имеет мало смысла.
Попросту говоря там нечего тестить будет вообще.
То есть тут лучше тестировать комплексно вместе с реальными сущностями и БД.
Сейчас ваше решение тянет на одноразовое
Вообще то могу показать ссылки на сайты в инете сделанные с таким решением. Не понимаю что вы имеете ввиду.
В конце концов задача статьи, как принято на Хабре, описать идею а не продать код.
огромное количество качественных решений
это как правило копии ORM с ява и.нет платформ.
Покажите решение где не нужно например мапить поля таблицы БД на классы, следить за типами и выполнять кучу другой работы которую мое решение попросту автоматизирует. Я не переизобретаю то что уже есть.
теперь посмотрим на ваш случай:
На этот случай как раз смотреть на надо. В реальном фреймворке есть отдельный класс синглтон https://github.com/leon-mbs/zippy/blob/master/zcl/db/db.php
Здесь я просто сложил в кучу на скорую руку чтобы не распылятся.
Разумеется можно вынести коннект, вынести статику и т.д.
Ну выносите кому надо — я просто предложил концепцию которой уже давно пользуюсь.
Почему вместо сути все концентрируются на том сответствует ли решение академическому подходу, читал ли я Фаулера и что использование представлений — отстой?
Fesor
06.05.2016 17:14с академической точки зрения
Ох уж эта академическая точка зрения. Я не ее пытаюсь объяснить, а юзкейсы для статики. То что для каждого правила есть исключения, вызванные желанием сэкономить на качестве — это нормально. Но только делая все это нужно делать акцент на том, что подобные решения увеличивают технический долг. Если проект простой — то не страшно, а если он будет расти и развиваться, может наступить момент когда все это сделает разработку болью. Сложность именно понять что когда применять, потому новичкам лучше сразу давать пожеще, с большим количеством правил и ограничений, и по мере постижения смысла этих правил и ограничений расслаблять хватку.
Попросту говоря там нечего тестить будет вообще.
Именно для этого AR и придумывали. Этот паттерн идеален для проектов, где логика идеально ложится на реляционную базу, и сущности тонкие. По сути это комбинация Domain Object + Row Data Gateway, правда люди об этом забыли и именно domain object частенько выкидывают, юзая AR-модельки как анемичные модели, тупые структурки данных.
Вообще то могу показать ссылки на сайты в инете сделанные с таким решением.
А говнокода, знаете ли, более чем хватает. Так что на любую даже самую стремную задумку примерчик с продакшена найти не так уж и сложно будет.
В конце концов задача статьи, как принято на Хабре, описать идею а не продать код.
Вас могут читать впечатлительные PHP-ники, которые наберутся этого и потом их будет долго переучивать. Любая точка зрения достойна существования, и ваш подход имеет место применяться в реальных проектах, но только если знаешь что делаешь. Большинство PHP-ников обитающих на хабре — не знает что хорошо а что плохо, а стало быть нужно четко прописывать допущения для решения. что мол "логики мало, хорошо на реляционку ложится, для прототипов".
Покажите решение где не нужно например мапить поля таблицы БД на классы, следить за типами и выполнять кучу другой работы которую мое решение попросту автоматизирует. Я не переизобретаю то что уже есть.
Легко. Вот первое что выдал поиск: https://github.com/j4mie/paris Так же если память не изменяет, реализация AR в Yii и Laravel делает ровно то же самое.
caballero
06.05.2016 18:39юзая AR-модельки как анемичные модели, тупые структурки данных.
если уж на то пошло Entity еще более тупые структуруки… Что не мешает их юзать в EJB3 например.
может наступить момент когда все это сделает разработку болью
во первых, подавляюзее число проектов именно простые. Это и есть решение которое экономит время на относительно простых проектах.
Таких проектах которых рядовому разрабу приходится клепать десятками.
А уж если что создаст проблеммы на больших проектах то \то как раз модные нынче ORM потому что во-первых создается неоптимальная структуру хранилища, во вторых еще более неоптимальные SQL запросы. В некоторых БД созданных через code first со временем сделать сложную выборку по большим данным даже нативный SQL не помогает без денормализации.
Опять же речь о PHP. В яве не так критично вытащить список ненужных ща Entity через связи- они останутся некоторое время в памяти и могут быть подхвачены вместо обращения к БД (во всяком случае в контейнерах EJB3). В PHP подобные решения мягко говоря не оправданы.
Вот первое что выдал поиск.
части фреймворков, не в счет. Пикси — надстройка над громоздким ORM со всеми вытекающими последствиями.
lair
06.05.2016 18:45+2если уж на то пошло Entity еще более тупые структуруки…
Ну уж нет. Entity — это термин из DDD, и предполагает он вполне себе конкретную вещь, а именно — объект, наделенный идентичностью (отличной от его значимых атрибутов) и жизненным циклом. DTO — не entity.
Fesor
06.05.2016 20:08+2Это и есть решение которое экономит время на относительно простых проектах.
Я могу предложить вам решение еще проще — тупо юзать mongodb + rest интерфейс к ней. И вообще никаких проблем и необходимости писать бэкэнд. Ну и опять же можно ваять мидлвэры с бизнес ограничениями если проект будет усложняться. Главное что бы все объекты бизнес-транзакции в один документ укладывались.
А уж если что создаст проблеммы на больших проектах то \то как раз модные нынче ORM потому что во-первых создается неоптимальная структуру хранилища, во вторых еще более неоптимальные SQL запросы.
Вы просто не умеете их готовить.
1) ORM нужны только для задач, в рамках которых у нас есть простая бизнес транзакция с небольшим количеством объектов. Скажем если вам репорт нужно сагрегировать — SQL. А еще можно просто дублировать информацию в том виде, который будет удобен для запросов. Есть миллион различных вариантов как работать с данными, не нужно зацикливаться на одном и говорить что это плохо во всех случаях просто потому что большинство оверюзят подход.
2) ORM ответственны только за мэппинг релейшенов между объектами. Все остальное — это другие паттерны. Причем "генериться" хранилище только в случае data mapper и вы строго можете контролировать этот процесс. В остальных случаях хранилище проектируете вы.
3) ORM это абстракция. Любая абстракция имеет свой оверхэд. Потому, в документации к таким решениям как Hibernate например четко прописано что "ребята, это текущая абстракция". То есть если мы хотим скорость разработки и выразительность языка — мы можем юзать абстракцию на полную. А там где нужна производительность — мы можем смешать все с нативным SQL.
Например если у нас в проекте есть сложное взаимодействие между бизнес сущностями — мне намного удобнее и проще описать это все в виде взаимодействия сущностей, протестить, и потом прицепить дата мэппер что бы подключить базу. Но если мне нужно будет делать какие-то сложные выборки этих объектов на чтение — то я просто сделаю SQL запрос и замэплю результат не на сущность а сразу на DTO.
В PHP подобные решения мягко говоря не оправданы.
у нас с вами видимо разные задачи. В моем случае более чем оправдано, как минимум потому что скорость разработки вырастает на порядок. А во вторых, решения вроде Doctrine имеют огромную кучу оптимизаций внутри, которые неплохо справляются.
части фреймворков, не в счет. Пикси — надстройка над громоздким ORM со всеми вытекающими последствиями.
То что я скинул — это самодостаточная библиотека. Причем ут пикси если библиотека называется "париж"?
Mendel
06.05.2016 17:24+2~~Вам уже 100500 раз сказали, что если отбросить всю конкретику вашего решения, то "суть" капитанская и несколько устарела.
"Идея — очевидная, спасибо Кеп" — это одна мысль. Повторять ее смысла нет.
"Ваша реализация отстой, особенно бац1, бац2, бац" — может быть бесконечно разнообразным, потому что бац1, бац2, и бац3 может быть одним из множества замечаний. Вот большинство и идет по второму пути.~~
Нет, конечно всё не так. Просто все тупые, и только один вы знаете сакральные истины и по достоинству оцениваете гениальную сущность подхода и самого кода.caballero
06.05.2016 17:44если нет возражений по существу то аргументы типа «устарела» и «отстой» оставьте при себе.
Решение работает — экономит нехилое количество времени кодирования и строк кода. Для того оно и сделано а не для того чтобы мерятся пиписками кто современнее и навороченее.
Fesor
06.05.2016 20:12Решение работает — экономит нехилое количество времени кодирования и строк кода.
Это так же не по существу, вы же не предоставляете никаких подтверждений этим словам.
В рамках маленьких проектов оно будет хорошо показывать себя. В рамках проектов чуть сложнее бложика — уже могут быть проблемы. С тем же успехом можно было бы просто отказаться от подобного и использовать старый добрый table data gateway, который еще проще и намного более гибкий.
Mendel
04.05.2016 16:09+8Эм… лживый заголовок, о чем понимаешь только к середине статьи. В середине статьи понимаешь что статья на самом деле называется «мой HelloWorld для библиотеки ADODB (слой абстракции построителя запросов)».
После прочтения статьи понимаешь что про ADODB тоже ничего толком нет и даже чтобы понять функции ADODB нужно идти на сайт и вычитывать документацию. Содержание статьи «Это круто что есть такая библиотека».
Эм…
Обертка над PDO — один класс на пару методов.
Обертка над ответом PDO для абстракции реализующая итератор и АrrayAccess и инстансцирование сущностей в классы (опционально). Еще один класс. На два экрана.
Построитель запросов для основных операций. Еще один класс (ну два, если операций много).
Базовый класс типа вашего Entity только с Итератором и АrrayAccess, ну и еще по строчке добавить чтобы фильтровать по списку разрешенных полей, и еще пять строк чтобы у нас уже методы getИмяПоля() и setИмяПоля() работали.
Собственно всё. Пишется быстро, работает как часы. Дальше уже ORM идет с валидациями и связями. Которые кстати тоже не экзотика а суровая необходимость. О чем статья?
caballero
04.05.2016 16:17-6о чем понимаешь только к середине статьи.
по моему вы не поняли ни к середине ник концу ни с обратной стороны.
чтобы понять функции ADODB нужно идти на сайт и вычитывать документацию
для применения данного решения вообще не надо понимать функции ADODB.
это как пример вашего «понимания»
Дальше уже ORM идет с валидациями и связями. Которые кстати тоже не экзотика а суровая необходимость.
Нету такой необходимости в ПОДАВЛЯЮЩЕМ большинстве проектов.
О чем статья?
Жаль что вы не поняли о чем. Возможно из-за заангажированости.
Mendel
04.05.2016 16:35Жаль что вы не поняли о чем. Возможно из-за заангажированости.
==
Ржу. Если я не понял смысла вашей статьи, то или его там нет, или я плохо понимаю, или вы плохо объясняете. Но поскольку я заангажирован, то вариант что смысла нет и что вы не умеете объяснять конечно не рассматриваются.
Перечитал статью. Или я слепой, или кроме упоминания ADODB и примитивного класса наподобии моего базового класса (см ниже) я в статье не нашел.
<?php /** * Класс позволяющий работать с массивом как с объектом. * Ну или с объектом как с массивом, кому как нравится.... * Строго говоря предназначен для наследования, но не абстрактный, ибо * в принципе может быть использован самостоятельно. */ class arrayComponent extends component implements ArrayAccess, Iterator, Countable { protected $fields = []; protected $_position; // public function __construct($config = NULL) { parent::__construct($config); $this->rewind(); } // public function offsetSet($offset, $value) { if (is_null($offset)) { $this->add($value); } else { $this->__set($offset, $value); } } public function offsetExists($offset) { return isset($this->fields[$offset]); } public function offsetUnset($offset) { unset($this->fields[$offset]); } public function offsetGet($offset) { $exist = (isset($this->fields[$offset]) AND $this->allowed($offset)); return $exist ? $this->__get($offset) : null; } // public function rewind() { reset($this->fields); $this->_position = key($this->fields); } public function current() { return $this->fields[$this->_position]; } public function key() { return $this->_position; } public function next() { next($this->fields); $this->_position = key($this->fields); } public function valid() { return array_key_exists($this->_position,$this->fields); } // public function count() { return count($this->fields); } /** * Проверяет разрешено ли такое поле * Всегда возвращает true. Нужно чтобы потомки могли его переопределять. * @param type $name * @return boolean */ protected function allowed($name) { return true; } /** * Магический метод проверяющий что устанавливаемое поле является разрешенным * и сохраняющий его значение в соответствующее поле * в противном случае вызывает родительский метод который подключает * сеттер или выдает ошибку * @param string $name имя устанавливаемого поля * @param mixed $value значение */ public function __set($name,$value) { if($this->allowed($name)) { $this->fields[$name] = $value; } else parent::__set($name,$value); } /** * Магический метод проверяющий наличие имени свойства в списке разрешенных, * в случае отсутствия вызываем родителя который ищет геттер * Если поле разрешено, то возвращаем его из массива разрешенных полей * @param string $name имя читаемого поля * @return mixed */ public function __get($name){ if($this->allowed($name)) { return $this->fields[$name]; } else return parent::__get($name); } //Добавим в наш массив новый элемент. Если без индекса нельзя, то переопределим метод на запрет public function add($data) { $this->fields[] = $data; } }
caballero
04.05.2016 16:43вы не поняли что суть моего класса не работа с массивом а работа с записями БД.
А забота ADODB корректно запихнуть данные массива в БД.
Mendel
04.05.2016 16:48И?
Работа с базой:
<?php /** * Инициирует ПДО, выполняет запрос с параметрами. * Результат возвращает в виде dbResult. * dbResult - итератор */ class db extends component { protected $pdo; protected $dsn = 'mysql:host=localhost;dbname=test'; protected $user = 'root'; protected $password = ''; protected $options = [PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES \'UTF8\'']; protected $queryClass = 'mysqlQuery'; public function __construct($config = NULL) { parent::__construct($config); $this->pdo = new PDO($this->dsn, $this->user, $this->password, $this->options); // бросаем исключения при проблемах $this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } public function insId() { return $this->pdo->lastInsertId(); } protected function prepareParam($param) { $result = []; foreach($param as $key=>$value) { $key = ':' . (string) $key; $value = (string) $value; $result[$key] = $value; } return $result; } public function go($sql, $param=NULL) { $statement = $this->pdo->prepare($sql, [\PDO::ATTR_CURSOR => \PDO::CURSOR_SCROLL]); $statement->setFetchMode(\PDO::FETCH_ASSOC); if($param) { $param = $this->prepareParam($param); $executeResult = $statement->execute($param); } else { $executeResult = $statement->execute(); } return new dbResult([ 'statement'=>$statement, 'ok'=>$executeResult ]); } // Вернем построитель запросов характерный для данной базы public function query($config) { $class = $this->queryClass; return new $class($config); } }
«Запихивание» данных в наш массив/класс:
<?php /** * Итератор по результатам запроса. * публичное свойство ОК содержит булево значение удачности запроса. * Метод one - сахар для запросов когда ожидается только один резульат. */ class dbResult extends component implements Iterator { protected $key; protected $current; protected $statement; public $ok; public $modelName; public function current() { return $this->current; } public function next() { $this->key++; } public function key() { return $this->key; } public function valid() { $this->current = $this->getRecord($this->key); if (false === $this->current) { // (!array()) == True return false; } else return true; } // Получаем отдельную запись, и если это уместно - выполняем манипуляции после загрузки protected function getRecord($key) { $record = $this->statement->fetch( \PDO::FETCH_ASSOC, \PDO::FETCH_ORI_ABS, $key ); if($record AND $this->modelName) { $model = model($this->modelName); $model->load($record); return $model; } else return $record; } public function rewind() { $this->key = 0; } public function one() { $this->rewind(); $this->valid(); $obj = $this->current(); $this->statement->closeCursor(); return $obj; } }
Чтобы дальше не играть в подкидного «генерация запросов»:
<?php class mysqlQuery extends mysqlQueryCore { public function insert($data) { $data = $this->filterField($data); $this->param = $data; $sql = 'INSERT '.$this->table().' SET '.$this->fields2set($data); $this->sql = $sql; return $this; } public function update($data, $where, $whereParam=NULL) { $data = $this->filterField($data); $this->param = $data; $where = $this->where($where, $whereParam); $sql = 'UPDATE '.$this->table().' SET '.$this->fields2set($data).' WHERE '.$where; $this->sql = $sql; return $this; } public function delete($where, $whereParam=NULL) { $this->param = []; // reset old request $where = $this->where($where, $whereParam); $sql = 'DELETE FROM '.$this->table().' WHERE '.$where; $this->sql = $sql; return $this; } public function select($data, $where, $whereParam=NULL) { $this->param = []; // reset old request if(is_array($data)) { $data = $this->quoteFieldList($data); } $where = $this->where($where, $whereParam); $sql = 'SELECT '.$data.' FROM '.$this->table().' WHERE '.$where; $this->sql = $sql; return $this; } public function count($where, $whereParam=NULL) { $data = 'COUNT(1) as cnt'; return $this->select($data, $where, $whereParam); } public function limit($limit, $offset=NULL) { $this->sql = $this->sql . $this->limitSql($limit, $offset); return $this; } public function order($order) { $this->sql = $this->sql . $this->orderSql($order); return $this; } }
Ну и вспомогательный код для последнего класса:
<?php class mysqlQueryCore extends component { protected $table; protected $allowed=[]; public $param=[]; public $sql=''; public function reset() { $this->sql = ''; $this->param = []; } public function where($data, $param = NULL) { if($param) { foreach($param as $key=>$value) { $this->param['where_'.$key] = $value; } return $data; } if(true === $data) $data = '1=1'; if(is_int($data)) { $this->param['where_id'] = $data; $where = $this->quoteId('id') .'=:where_id'; return $where; } if(is_array($data)) { $where = $this->array2where($data); return $where; } if(is_string($data)) { return $data; } return FALSE; } protected function array2where($data) { $data = $this->filterField($data); $mylist = []; foreach($data as $key=>$value) { $mylist[] = $this->quoteId($key) .'=:where_'.$key; $this->param['where_'.$key] = $value; } return implode(' AND ', $mylist); } public function orderSql($order) { if($order{0}=='!') { $field = substr($order,1); $field = $this->quoteId($field); $result = ' ORDER BY '.$field.' DESC'; } else { $field = $order; $field = $this->quoteId($field); $result = ' ORDER BY '.$field.' ASC'; } return $result; } public function limitSql($limit,$offset=NULL) { $result = ' LIMIT '; if($offset) $result .= intval($offset).','.intval($limit); else $result .= intval($limit); return $result; } public function quoteFieldList($fields) { // $allowed = array_flip($this->allowed); $allowed = $this->allowed; $mylist = []; foreach($fields as $value) { if(isset($allowed[$value])) $mylist[] = $this->quoteId($value); } return implode(' , ', $mylist); } // $fields = ['name'=>'namedata','value'=>'valuedata'] // return: `name` = :name, `value` = :value public function fields2set($fields) { $mylist = []; foreach($fields as $key=>$value) { $mylist[] = $this->quoteId($key) .'=:'.$key; } return implode(' , ', $mylist); } public function quoteId($id) { return "`".$id."`"; } public function filterField($data) { // $allowed = array_flip($this->allowed); $allowed = $this->allowed; if (is_object($data) OR is_array($data)) { $result = []; foreach ($data as $key => $value) { if
Mendel
04.05.2016 16:49Кода здесь примерно как слов в вашей статье, зато смысла немного побольше.
ПС: За ляпы и стиль сильно не бить, до ревью и рефакторнга еще не добрался, сам знаю что кривенько :)caballero
04.05.2016 16:59половину того что вы написали решает ADODB и решает их гораздо лучше. В первую очередь проверяет структуру таблицы и формирует запрос только с теми полями что там есть что исключает ошибки при неправильном свойстве или свойстве не предназначеном для записи в БД. Плюс ADODB сама расставляет кавычки, формирует правильные форматы дат (причем в зависимости от типа БД и диалекта SQL то есть обеспечивает переносимость) и так далее.
Я не понимаю зачем бы я повторял эту функциональность в своем решении.Mendel
04.05.2016 17:14А зачем расставлять кавычки? PDO уже отменили?
Зачем проверять структуру? Тяжелая наркомания и не более.
Если я неправильно написал имя свойства, то я хочу увидеть исключение, которое схоронится в лог и выдаст красивую 500-ошибку юзеру, а не проигнорирует какой-то пункт в моем запросе и повалит или базу или логику прав доступа.
Формирование запросов под разные БД — да, не дописал пока. Но то такое. Работа измеряется в часах.
Форматирование даты? Мелочь и вкусовщина. У меня этим занимается ORM, ибо это именно его уровень ответственности, и ему знать какие форматы даты предпочтительнее в разных случаях. Я лишь указываю структуру базы, полей связей и т.п.
И кстати фильтрация по доступным полям таки есть, см. метод protected function allowed($name).
НО всё это фигня. Вы так и не ответили на вопрос — так о чем статья то? О том что при наличии библиотек которые за тебя уже написали (кривых библиотек, да, ибо раз оно без PDO то deprecated по определению), уже написанные функции можно не писать? Или о том что «решает ADODB и решает их гораздо лучше»? Ну решает. Ну есть такая библиотека. Ну Есть в пхп методы __гет() и __сет(). Дальше что? О чем статья?caballero
04.05.2016 18:13А зачем расставлять кавычки? PDO уже отменили?
mysqli_connect тоже не отменили.
При прямой работе с PDO нужно вставлять значения полей параметрами. То есть писать кучу кода как запрос так и параметры.
В моем решении НИЧЕГО писать не надо. Умножьте ничего (и соответственно ноль ошибок) на сотни мест в проекте где надо обращатся к БД и получите не одну человеко-неделю.
О том что при наличии библиотек которые за тебя уже написали (кривых библиотек, да, ибо раз оно без PDO то deprecated по определению),
Вообще то ADODB может работать и через PDO — какой вариант укажешь так и работает.
Если я неправильно написал имя свойства, то я хочу увидеть исключение
а я хочу писать в моей сущности свойства которые не хранятся в БД (мало ли какая бизнес логика в бизнес сущности а не просто копии строки БД) и чтобы не было изза этого никаких исключений.
Разница просто в «я хочу» не более того.
НО всё это фигня. Вы так и не ответили на вопрос — так о чем статья то?
И не должен — но то есть текст статьи.
Mendel
04.05.2016 19:27Не хотите сохранять — не сохраняйте. Есть геттеры/Сеттеры, сценарии и много чего еще.
Отказ от параметров в запросах с написанием оных руками (иначе зачем «множество раз»? У меня вон и CRUD и массовый и единичный, и условия по массиву и по единичной записи — уже готовые генерятся) — это путь либо в wet либо в дырявое решето.caballero
04.05.2016 20:34Есть геттеры/Сеттеры, сценарии и много чего еще.
конечно. Мир велик и разнобразен. Суть том что в моем решении -«этого много чего» просто не нужно.
Отказ от параметров в запросах с написанием оных руками
пример того чего не нужно — ни параметров ни написания их руками.
Mendel
04.05.2016 16:56+1И кстати забавно не то, что вы не понимаете какие-то простые вещи которые вам повторяют. Все писали велосипеды, и все капитанили. У меня у самого схожих по тупости статей было навалом. Смешит ваш подход — все тупые, и не могут понять мою гениальную мысль :)
caballero
04.05.2016 17:01-5Пока что не поняли вы один и то потому что не собираетесь ничего понимать.
michael_vostrikov
04.05.2016 18:44+1А вы хотите чтобы вам 10 человек одно и то же написали? Ну я, например, тоже не понял. У вас нет ничего нового ни в идее, ни в реализации. Генерировать SQL из ассоциативного массива это первая мысль, которая появляется при изучении работы с БД в PHP, а маппинг таблиц на сущности есть практически в любом фреймворке.
kovalevsky
04.05.2016 17:02+4А над какими проектами Вы работали, где, в подавляющем большинстве, нет необходимости в валидации и связях? Можно посмотреть или школьники уже сломали?
caballero
04.05.2016 17:12-1Я не говорил что валидация не нужна. Я не вижу какие тут проблемы с валидацией.
Что касается связей — я имел ввиду не то что существуют логические связи в бизнес-логике а то что необходимо каждый раз вытаскивать все эти связи в виде гроздьев объектов на страницу контекст которой будет разрушен через долю миллисекунды и в следующую милисекунду пять посылать в Бд запрос с кучей джойнов (причем неоптимальный запрос — ORM не такие умные ) и создавать в памяти десятки если не сотни не нужных в данный момент объектов.
Это имеет смысл в яве где объекты сущностей могут находится в памяти ява-машины и быть повторно использованными. Но речь конкретно о PHP а не о теоретизировании насчет общих принципов работы с БД.
lair
04.05.2016 17:15необходимо каждый раз вытаскивать все эти связи в виде гроздьев объектов на страницу контекст которой будет разрушен через долю миллисекунды
А никто и не говорит про "каждый раз". Но это с завидной регулярностью оказывается нужно в бизнес-логике, и вот тогда адекватные агрегаты оказываются намного проще для использования, чем несвязанные классы.
caballero
04.05.2016 17:26-1А никто и не говорит про «каждый раз». Но это с завидной регулярностью оказывается нужно в бизнес-логике,
Это смотря как писать бизнес-логику. У меня есть проект учетной системы с кучей сущностей и ни разу не понадобилось вытаскивать какие то связи. Просто такова особенность PHP — каждый раз страница формируется по новой посему обьективно нужно вытаскивать только то что отображается на данным момент. Поэтому нет никакой «завидной регулярности» и быть не может. По крайней мере до тех пор пока в PHP не появится возможность персистентного хранения объектов в памяти.
lair
04.05.2016 17:44Это смотря как писать бизнес-логику.
Ну да, тут вам никто, включая Эванса, не указ.
У меня есть проект учетной системы с кучей сущностей и ни разу не понадобилось вытаскивать какие то связи.
Завидую вам, что тут еще скажешь.
Просто такова особенность PHP — каждый раз страница формируется по новой посему обьективно нужно вытаскивать только то что отображается на данным момент.
Ну например, надо вытащить список кастомеров с количеством и общей суммой заказов для каждого кастомера. Как это сделать, сохранив объектную модель, и не имея связей?
Mendel
04.05.2016 17:18Вот. Вот теперь сразу стало всё понятно. Вот этого комментария и не хватало чтобы понять статью. Без комментария «я не знаю как работают ORM, зачем там связи и что такое жадная отложенная и прочие виды загрузок» было не понятно что и зачем тут написано :)
caballero
04.05.2016 17:31чтобы понять статью достаточно было последнего абзаца асчет перфекционистов.
Для 99.9% проектов которые пишет каждый день обычный трудяга -програмист нафиг не нужны никакие ни «жадные» ни «щедрые» виды связей.
Это просто не актуально. Глупо усложнять решение тем что будет использоватся в одном случае из тысячи. Для этого есть другие готовые решения.
andrewnester
04.05.2016 17:45+2классно вы статистику по проектам то выдаёте :) что-то у меня обратная совсем статистика
caballero
04.05.2016 17:57серьезно? 99.9% не могут функционировать без ORM типа доктрины, загрузки связей, ленивой загрузки, кеширования и прочего?
Ну возмите статистику по посещаемости — у какого процента сайтов средняя загрузка хотя бы сотня обращений к Бд в секунду. Или по другому — сотни тысяч посещений в сутки.
andrewnester
04.05.2016 18:06Могут, но если проект долгий, вы почувствуете разницу в сложности управления связей руками и на уровне ORM
А касательно кеширования и ленивой загрузки — во всех проектах для быстродействия мы делали это. Неважно с или без ORMcaballero
04.05.2016 18:20не почувствую потому что нет необходимости ни в каком управлении. Это проблема тех кто создает связи ради применения ORM
А если проект длительный и постоянно переделывается да так чтобы не свалился продакшен то там неизбежно возникает такой бардак и столько костылей что зачастую разруливается только нативными SQL. И не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
andrewnester
04.05.2016 18:24+1классическая связь почти для любого проекта.
User, который связан с UserProfile, UserPreferences, UserACL. Всем этим так или иначе надо управлять. и это идёт из требований.
И не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
мы с вами наверное совершенно на разных проектах работаем, потому что у нас теория вписывается в практику. понятно, что есть неидеальные моменты, но все эти книги как раз таки написаны, основываясь на практике.caballero
04.05.2016 18:46-1User, который связан с UserProfile, UserPreferences, UserACL. Всем этим так или иначе надо управлять.
Не вижу зачем тут нужен именно ORM. Да и не во всех проектах есть ACL и тому подобное.
мы с вами наверное совершенно на разных проектах работаем
на каких проектах мы работаем не имеет значения и не относится к сути дела.
Я не предлагаю замену doctrine или еще чего.
Речь о том чтобы упростить разработку обычных сайтов — не порталов и не соцсетей. Сайтов где не критична скорость и нет сотен таблиц в БД.
И таких сайтов подавляющее большинство.
Ок. Не 99.9%. Возмем принцип Парето — 80% — это тоже до фига.
andrewnester
04.05.2016 18:50покажите где я написал про ORM или доктрину, на них свет клином не сошёлся. я говорю в целом про решение связанное с работой с БД.
Речь о том чтобы упростить разработку обычных сайтов — не порталов и не соцсетей. Сайтов где не критична скорость и нет сотен таблиц в БД.
ну в таком случае ваше решение тоже не самое простое, зачем beforeSave, afterSave и тд?caballero
04.05.2016 19:07не надо не используйте. Но события жизненного цикла должны быть.
Да и нету там ничего сложного — строчка кода в методе Save()
Но суть не в том сложное решение или нет. Суть в том чтобы упростить разработку.
Проблема аналогичных решений не в том что они сложные (в ADODB тоже кода до фига)
а в пороге вхождения и количестве танцев с бубном при использовании — маппинге, генерации всяких прокси и т.д. вместо просто сесть и писать прикладную бизнес логику. Которую потом и читать проще будет тем кто сопровождает.
andrewnester
04.05.2016 19:13вы сравниваете только своё решение и доктрину. это два разных полюса, чёрное и белое. а есть ещё серое.
https://github.com/analogueorm/analogue
https://github.com/j4mie/idiorm
https://github.com/jpfuentes2/php-activerecord
да и ещё много
Mendel
04.05.2016 19:33Могу еще проще пример привести:
После нормализации базы производители отделены от марок автомобилей.
Ну и хочу вывести во вью описание автомобиля. Хочу сделать во вью простое <?=$mark->brend()->name?> но не могу ибо связей нет, и приходится это городить уже в контроллере.caballero
04.05.2016 20:21а что мешает во вью машин выводить имя производителя?
Нужно програмировать от бизнес-логики а не от того могу ли я написать выражение с двумя стрелочками.
Mendel
04.05.2016 20:45Во вью в терминах MVC мешает здравый смысл. Во вью в терминах SQL мешает то, что его нет, и не предвидится. Вообще не рекомендуется ими злоупотреблять без особой необходимости.
caballero
04.05.2016 21:05Во вью в терминах MVC мешает здравый смысл.
ИМХО использование MVC в вебе вообще лишено здравого смысла (и проблемы работать с представлениями если они есть только это подтыверждают). Лично я предпочитаю компонентные решения.
Вообще не рекомендуется ими злоупотреблять без особой необходимости.
Вообще ничем в не рекомендуется употреблять без особой необходимости.
bromzh
04.05.2016 20:55А где это имя хранить в вашей архитектуре?
На уровне БД машины и производители лежат в разных таблицах. Т.е. нужен будет джойн. В коде это, соответственно, 2 класса-сущности.
Как с помощью вашей библиотеки получить список список авто и связанных с ними производителей?
Как поддерживать консистентность данных при этом?
Вообще, не знаю, что за проекты у вас, но в большинстве проектов, использующих реляционные БД так или иначе возникают потребности в связях (не хранить же всё денормализированным).
caballero
04.05.2016 21:13Как с помощью вашей библиотеки получить список список авто и связанных с ними производителей
не вижу зачем одновременно нужен список машин и список производителей со всеми полями.
Поле имени производителя выберется из представления и будет доступно в сущности авто (это же PHP с динамическими свойствами).
если вам понадобится просмотреть детальную инфу производителя это будет уже другая страница с другим контекстом к в котором можно загрузить целиком производителя как отдельную сущность.
использующих реляционные БД так или иначе возникают потребности в связях
Вы понимаете что логическая связь в БД и то что вы пытаетесь выбирать это не одно и тоже. Даже если связь в БД прописана констрейтами это ничего не меняет.
Попробуйте мыслить не категориями ORM а бизнес-логикой приложения — ORM вторичен его наличие или отсутсвтвие никак не влияет на архитектуру данных в БД.
lair
04.05.2016 21:20а что мешает во вью машин выводить имя производителя?
То, что теперь представление в БД (вы же его имеете в виду) напрямую зависит от того, какие данные нужны в UI. И каждое изменение в UI будет влиять на БД.
Нужно програмировать от бизнес-логики
Так то, что показано — и есть пример бизнес-логики (как ее понимает, скажем, Эванс).
lair
04.05.2016 18:27+3не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
То есть подождите, я правильно вас понял, вы считаете GoF, PoEAA, Code Complete и так далее — не работающей на практике теорией?
caballero
04.05.2016 18:35я считаю что есть практическое програмирование а есть люди которые любят кидать умняки и употреблять модные термины.
В свое время работал в почтовом ящике где проектировали системы управления для космоса ( а пока я ходил в детсад для SS-18 Satan).
И как-то то работало (и кое где до сих пор) без книг фаулера, макконела и банды четырех.
Ничего не имею против теории но она не заменит здравого смысла, прагматического подхода и прямых рук.
lair
04.05.2016 18:45Я, вроде бы, задал конкретный вопрос — перечисленные книги работают на практие или нет?
И как-то то работало (и кое где до сих пор) без книг фаулера, макконела и банды четырех.
Ключевое слово — "как-то". Речь идет не о том, что без них не работает, речь идет об увеличении качества/уменьшении расходов.
Mendel
04.05.2016 18:02В том то и дело. Даже в простеньких порталах на полтора десятка моделей уже без связей ад адовый, толстые контроллеры, и прочие антипаттерны. Вот прямо сейчас на скорую руку накостылили небольшой проект без связей, типа «потом добьем». Ад. Но дедлайн есть дедлайн. И слушать о том что «связи редко кому нужны» мне смешно :)
Черт, у меня контроллеры до 30 строк доходят потому что связей не хватает!!! расcaballero
04.05.2016 18:28Грамотно написанные модели не требуют чтобы их связывали по полтора десятка в одной выборке.
Посему их количество не имеет значения.
Делайте модели не от фонаря а чтобы они соответствовали бизнес-сущностям и не будет вопроса.
michael_vostrikov
04.05.2016 18:49+1Например, есть сущность «Заказ», у него есть поле user_id. Как вы выводите имя пользователя в списке заказов?
caballero
04.05.2016 19:18ну это же очевидно — пишется представление куда приджойнивается имя юзера — стандартный подход который и так должен применятся.
Таким образом это имя появится в объекте сущности
Заказ. Только нужно указать имя представления в анотации класса чтобы вибирать с него а не с таблицы.
Или по вашему вытаскивать юзеров целиком на каждую строку записи о заказе это правильней? А если к заказу еще есть манагер который отвечает за заказ? и их всех тащить вместо приджойнить имя манагера в представление?
Mendel
04.05.2016 19:37А что кроме * в select больше ничего нет?)
И что для каждого случая писать свой вью?))))
Мне кажется я понимаю откуда берутся люди пишушие такие вещи как опенкарт.caballero
04.05.2016 20:24В моем решении можно выбрать и произвольным запросом. И опять же ничего не надо програмировать.
Кроме того если вам нужны сложные выборки, например заказчик захотел витиеватый отчет) вам никакой ORM не поможет.
lair
04.05.2016 21:21-1В моем решении можно выбрать и произвольным запросом. И опять же ничего не надо програмировать.
То есть SQL-запрос писать не надо?
michael_vostrikov
04.05.2016 20:31+1А для страницы просмотра заказа делать другое представление со своими джойнами? И при добавлении/удалении полей делать
ALTER VIEW
для каждого?
Делается какой-нибудь методwith($relationName)
, который после основного запроса делает второй к таблице пользователей с условиемWHERE id IN ($userIDs)
, и заполняет связьuser
у всех$orders
. Без лишних джойнов.caballero
04.05.2016 20:43-1во первых представления в любом случае бэст практикс тем более что большинство сущностей которые надо приджойнить известны уже на стадии проектирования. Очевидно же что если есть заказ то там есть заказчики так или иначе понадобится посмотреть кто он.
во вторых выбирать джойнами одно поле или делать еще один запрос и выгребать из БД все данные по юзерам чтобы использовать только имя — не уверен что второй способ разумнее.
andrewnester
04.05.2016 21:06откуда у вас информация, что использование БД представлений — бэст практис?
caballero
04.05.2016 21:39-1вы это серьезно? Попробуйте сказать такое на sql.ru.
andrewnester
04.05.2016 21:51Мне авторитетнее в этом вопросе разработчик MySQL https://www.percona.com/blog/2007/08/12/mysql-view-as-performance-troublemaker/
caballero
04.05.2016 22:00То что в Mysql проблемы с представлениями — это проблемы Mysql. В промышленных серверах БД такой проблемы нет. Просто выбирайте подходящий инструмент.
Хотя и в Mysql это не проблема если речь не идет о высоконагруженном проекте о коих мы не говорим.
andrewnester
04.05.2016 22:03ок, что такое промышленные сервера БД?
И ответьте пожалуйста, почему представления это хорошо? Я спрашиваю абсолютно серьёзно, может я что-то упустил в познаниях БД.
Всё, что я могу сказать по представлениям — это вынос логики из кода в БД, так же как хранимые пооцедуры (если не ошибаюсь представления и реализовпны как хранимки), поэтому он должен быть обоснованcaballero
04.05.2016 22:24ок, что такое промышленные сервера БД
MSSQL, Oracle и иже с ними. Сервера для решений энтерпрайз уровня.
Сервера в разработку алгоритмов оптимизатора запросов вкладывают милионы долларов. Чего очевидно никто не вкладывает в mysql.
если не ошибаюсь представления и реализовпны как хранимки
Представления реализованы как SQL запросы.
это вынос логики из кода в БД, так же как хранимые пооцедуры
насчет ХП согласен. Представления никак не влият на бизнес-логику вам никто не мешает обращатся прямо к таблицам.
представления позволяют избежать написания сложных SQL запросов.
А запросы бывают не на один десяток строк — особенно в разного рода репортах.
Если даже удастся такое собрать в ORM то либо ляжет приложение либо ляжет сервер БД.
кроме того вьюхи это логический слой — вам может понадобится скоректировать структуру БД Обычное дело — проект растет набирает данных и сервер дохнет.
Если у вас вьюха вы меняете структуру таблиц и ваше приложение продолжает работать.
это реальные ситуации в каждом втором реальном проекте.
lair
05.05.2016 00:18Сервера в разработку алгоритмов оптимизатора запросов вкладывают милионы долларов.
… и эти оптимизаторы давно уже не делают различий между представлением и просто запросом.
Если даже удастся такое собрать в ORM то либо ляжет приложение либо ляжет сервер БД.
Почему это?
кроме того вьюхи это логический слой — вам может понадобится скоректировать структуру БД
Для этого уже давно придумали слой прикладного сервера.
lair
04.05.2016 21:22Очевидно же что если есть заказ то там есть заказчики так или иначе понадобится посмотреть кто он.
Нюансы скрываются в "так или иначе", которые очень быстро могут меняться с каждой версией требований.
michael_vostrikov
04.05.2016 21:30Представления иногда оправданы для десктопных приложений. В веб-приложениях они используются очень редко, потому что — а зачем?
michael_vostrikov
04.05.2016 21:41(* не дописал)
Пользователь, подключающийся к БД, один, программный код один, запрос с джойнами генерируется ORM либо пишется один раз, права пользователя приложения удобнее проверять кодом на серверном языке, а не в SQL.caballero
04.05.2016 21:51запрос с джойнами генерируется ORM либо пишется один раз
Только генерит SQL за который хочется руки повыдергивать. Я тоже напишу его один раз если он не вписывается в существующие представления.
права пользователя приложения удобнее проверять кодом на серверном языке, а не в SQL
А кто против? С этой точки зрения и констрейнты не нужны.
Но всякое бывает — мало ли какое еще приложение пользует ту же БД. А еще могут и руками напрямую полезть.
Никто не проектирует БД в зависимости от того какой ORM в приложении. Сначала проектируется грамотная структура хранилища а потом хоть ORM хоть чертом.
michael_vostrikov
04.05.2016 22:09+1Я тоже напишу его один раз если он не вписывается в существующие представления.
Только вы еще и запросы для представлений напишете, и поддерживать их будете.
Никто не проектирует БД в зависимости от того какой ORM в приложении
Никто не проектирует с точки зрения «а еще могут и руками напрямую полезть». Вернее, может кто-то и проектирует, но делать так не надо.
Проектирование БД и навешивание представлений на спроектированную БД — это не одно и то же. Наличие/отсутствие ORM на структуру таблиц не влияет. Вопрос в том, зачем использовать представления в веб-приложении.caballero
04.05.2016 22:36Никто не проектирует с точки зрения «а еще могут и руками напрямую полезть».
вобще то именно такие вещи и учитываются при проектировании — потому и ставятся констрейты.
Пусть это не прямо руки -пусть это просто другое приложение, десктопное, мобильное и т.д.
А в более менее сложных проектах без исправлений руками в БД вообще не обходится. Гладко бывает только в книгах по реляционной алгебре.
Вопрос в том, зачем использовать представления в веб-приложении.
Они используются БД а не в веб приложении.
Когда я проектирую БД я не думаю о там что там за приложеие. Я не знаю какое будет приложение завтра, будет оно веб или не веб. Сегодня веб завтра мобильное после завтра REST сервис. Мне потом БД переделывать? А если разные приложения одновременно?
Нет, я ее проектирую как положено проектировать хранилища данных.
Даже не понимаю смысла данной дискусии.
michael_vostrikov
04.05.2016 23:37+2Зачем переделывать БД? Таблицы одинаковые для любого приложения. Единственное это если вы вдруг решили из мобильного клиента коннектиться напрямую к базе, и хотите через представления ограничить права пользователю. По-моему, это слишком редкий случай, а вы там выше писали «ориентируюсь на большинство обычных задач которые нужны повседневно». А вообще в таких случаях для мобильного приложения делается API.
Кстати, не подскажете ссылки на материалы о том, что положено проектировать хранилища данных через представления?)caballero
04.05.2016 23:44Зачем переделывать БД? Таблицы одинаковые для любого приложения.
потому что это приходится делать НА ПРАКТИКЕ по объективным причинам.
И не потому что это кому то хочется.
подскажете ссылки на материалы о том, что положено проектировать хранилища данных через представления?)
как только покажете литературу где написано что положено использовать ORM.
lair
05.05.2016 00:20потому что это приходится делать НА ПРАКТИКЕ по объективным причинам.
Какие объективные причины требуют от вас переделывать таблицы с появлением нового приложения?
как только покажете литературу где написано что положено использовать ORM.
Ну так PoEAA же.
michael_vostrikov
05.05.2016 07:17Я нигде не писал, что положено использовать ORM. Даже наоборот: «Наличие/отсутствие ORM на структуру таблиц не влияет». Это верно для 99.9% случаев (ок, 80%). User — он в любом приложении User.
Ладно, литературу вы почему-то привести не желаете, может какой-нибудь пример из практики приведете?caballero
05.05.2016 09:37-1нет никакой литературы. Есть выработаные практикой принципы грамотной разработки хранилищ БД.
Так же как есть неписаные законы по написанию програмного кода, верстке и т.д.
И есть многолетная практика разработки (в том числе и мой личный опыт)
Литература — это для начинающих.
Опытный разработчик БД — сразу как создал таблицу, создает и вьюху на нее. Одни к одному, без джойнов. просто затем чтобы разработчик приложения сразу юзал вьюху а не влепил таблицу а через две недели перебивал код на представление.
Потому что все равно придется.
Ну а не придется для какой то таблицы наличие вьюхи никак не отразится на работе — ничего не теряем.
Но практика показывает обратное приходится менять структуру, (или как минимум типы полей). Причины — начиная от изменения требований к проекту заканчивая необходимостью
денормализации после того как Бд заполнена данными и долгих танцев с бубном с профайлером и изучением планов запросов чтобы выяснить узкое место.
Представте что вы пишете приложение на PHP. Вы можете написать код PHP (лии верстку HTML и CSS) так чтобы в процессе проекта его ни разу не пришлось коректировать? Сомневаюсь.
Точно так же и с БД.
Объяснять вам бесполезно -сами наберетесь опыта столкнувшишь с более менее серьезным проектом.
andrewnester
05.05.2016 10:03+1вот честно, приходилось работь и общаться с архитекторами БД и представления они использовали в случаях:
— спрятать какие-то данные(колонки) из запрсов
— спрятать тяжелые запросы, например, при генерации отчета
— для некоторых таблиц заведомо было известно, что их структура будет меняться, причем часто. Там тоже добавляли вью
Лично я вас просил указать статьи/книги и тд, где рассказано, почему обильное использование представлений хорошо, потому что мой опыт и опыт коллег это не доказывает. На мой взгляд, добавление вьюх на каждую таблицу добавляет лишней сложности, потому что теперь сопровождать надо не только таблицы, но и вьюMendel
05.05.2016 10:55Андрей, Вы что еще не поняли?
Человек ОПЫТНЫЙ, неопытных не слушает.
К сожалению у Леонида не указан возраст, но такая проблема часто встречается у разработчиков с возрастом. На нее Леонид уже неоднократно намекал, в частности "как-то же ракеты запускали без всяких этих ваших умных подходов".
В частности здесь мы видим подход который имел место лет пять назад, когда вьювы уже были более менее популярны, а про миграции еще мало кто знал. Вот человек познакомился с подходом и на нем и сидит. Аналогично и с остальными вопросами. И со связями и со всем остальным.
У меня был похожий период лет десять назад, с восьмибиток, бейсиков ассемблера и т.п. слазил тяжело.Нет, вроде Си и Паскаль знал, вроде другие языки были понятны, но подходы оставались те же.
Не, ну а чё? годами работало, чего менять то рабочий подход? :)
Потом вот точно так-же лет пять назад спорил тут на хабре и на серче на примерно похожие темы. Только я отрицал пользу таких паттернов как АктивФорм и им подобных. Я тогда навелосипедил свой "шаблонизатор" на конечных автоматах с тремя управляющими конструкциями и упорствовал что 640килобайт хватит каждому...
Тут или перерастет, или нет. Нам не повлиять :)
lair
05.05.2016 11:13В частности здесь мы видим подход который имел место лет пять назад, когда вьювы уже были более менее популярны, а про миграции еще мало кто знал.
Скорее — десять-пятнадцать. Пять лет назад мы уже всеми силами от представлений в БД избавлялись (наевшись, в частности, проблем с их поддержкой).
caballero
05.05.2016 11:33я сейчас а не пять лет назад сижу на проекте для англичан по логистике складов. Причем именно веб приложение. Даже в страшном сне не представляю как бы я работал тут с одними таблицами. Даже с учетом вьюх приходится на некоторые репорты наворачивать запросы по двадцать тридцать строк кода.
Сервер БД не в курсе какие вы там теперь продвинутые и шо там за приложения — пока рулят реляционные БД пока и останутся неизменными принципы разработки реляционных хранилищ.
Возможно если речь о Mysql то таки да, он оптимизирован под быстрые линейные выборки с одной таблицы и на джойнах работает не очень.
lair
05.05.2016 11:49Сервер БД не в курсе какие вы там теперь продвинутые и шо там за приложения — пока рулят реляционные БД пока и останутся неизменными принципы разработки реляционных хранилищ.
Внезапно, представления не входят в "неизменные принципы разработки реляционных хранилищ".
caballero
05.05.2016 11:20а мой опыт и опыт моих коллег доказывает.
возьмите хранилище какого нибудь серьезного проекта типа Oracle Apex и посмотрите.
Или как я уже рекоменовал зайдите на sql.ru где тусуются спецы по БД и заявите что представления отстой.
Я про хранимки такое сказал и то выгребал эпитеты в коментах целый день.
Еще раз — если в проекте больше пары десятков таблиц без представлений не обойтись. Либо выгребать каждый раз сотни ненужных объектов в приложение связывая по десятку таблиц в ORM что очевидно глупо.
lair
05.05.2016 11:51Либо выгребать каждый раз сотни ненужных объектов в приложение связывая по десятку таблиц в ORM что очевидно глупо.
Вообще-то, строго наоборот. Если я использую ad-hoc-запросы вместо представлений, я получу ровно те данные, которые мне нужны, а не те, которые в представление заложил автор.
Mendel
05.05.2016 12:02+1А кто вам сказал что вьювы и хранимки это отстой? Приведите цитату. :)
Вам говорят что это инструмент с помощью которого можно отстрелить себе ногу, поэтому использование оного требует обоснования. Плохи не хранимки или вьювы, плохи программисты которые не понимают когда они уместны а когда нет, и сующие их во все дыры.
Вьювы это хороший инструмент в частности для мягкой денормализации.
Когда возникает потребность в денормализации (денормализации уже нормализованной базы), то часто можно избежать реальной денормализации использовав только вьювы. Но это специфический вопрос, обычно требующийся для оптимизации, для сложных запросов и т.п.
Трезвые, современные разработчики пишут примерно так:
Бутстрапим структуру базы, бутстрапим CRUD и прочие базовые вещи кодогенерацией и ORM, правим структуру, допиливаем неучтенные моменты ТЗ, оптимизируем узкие места структуры или кода.
Подобная дорожная карта сокращает время разработки, сложность поддержки и количество ошибок — на порядок.
При этом 90% задач (не проектов, а грубо говоря "сервисов") обходятся вообще без оптимизации, и 90% из тех где оптимизация таки нужна — решаются средствами ORM вроде добавления инструкции для жадной загрузки или указания того какие поля тянуть а какие нет. Для остальных случаев уже идут прямые запросы (и то через построитель), вьювы и хранимки.
Я понимаю что вы из мира велосипедных ERP, где чуть другая специфика. Но частое изменение структуры которое лучше делать вьюа не миграцией да еще и запросы по два экрана это
как правило признак изначально плохой архитектуры.
Уверен что если вы почитаете литературу, посмотрите классику, например как денормализацию делает тот же 1С (да и вообще почитаете откуда взялось название 1С :) ), то 90% ваших сложностей исчезнет.lair
05.05.2016 12:12Бутстрапим структуру базы, бутстрапим CRUD и прочие базовые вещи кодогенерацией и ORM
Или наоборот. Я тут недавно писал некий мелкий проектик, так там БД появилась сильно после того, как половина прикладного слоя была написана и протестирована. Заодно ее структура к этому моменту была полностью понятна и очевидна.
caballero
05.05.2016 12:18-11С до 8 версии работала на DBF что не помешало ей стать успешным проектом. посему пример неудачный.
Кстати именно вьюхи позволяют прозрачно денормализовать БД не создавая разрабам приложений лишних сущностей.
Не вижу смысла дискусии о представлениях в контексте данной статьи…
Уверен на хабре если не было то рано или поздно будет соответствущая публикация и соответствующий холивар.
Для проектов которые архитектурю я больше подходят представления.
Можете считать меня ретроградом но я также считаю что MVC на вебе идиотизм. Ангуляры и прочие способы вытащить бизнес логику на клиента — еще больший идиотизм.
Про ORM в PHP, которые тупо портируют с явовского Hibernate или нетовского LINQ я уже говорил. Посему ваши аргументы что там как надо бустрапить не ко мне.
В своей карьере я уже повидал не один модный тренд в ИТ переживу и нынешние.
Mendel
05.05.2016 12:301С до 8 версии работала на DBF что не помешало ей стать успешным проектом. посему пример неудачный.
Да хоть на CSV. К чему это вообще? 1С во многом занял свою позицию в том числе и благодаря грамотной денормализации. В то время это еще не было типовым и очевидным. Посмотрите на их базовые сущности вроде "регистров" различных видов. Не утверждаю что это лучший подход, но один из удачных моментов позволяющих избегать тридцатиэтажных тяжелых запросов существующих в любой ERP. Мне пофиг что за движок у них на уровне СУБД. Да, в чем-то их абстракция в свой язык запросов поверх ORM который поверх нативного SQL — напоминает ваше злоупотребление вьювами, но я не о том…
А впрочем ладно. Всё сказано вашей последней фразой:
В своей карьере я уже повидал не один модный тренд в ИТ переживу и нынешние.
lair
05.05.2016 10:43Есть выработаные практикой принципы грамотной разработки хранилищ БД.
Которые не записаны ни в какой литературе? Не смешите меня.
Опытный разработчик БД — сразу как создал таблицу, создает и вьюху на нее.
Я видел много опытных разработчиков БД, и никто из них так не делает сейчас.
Одни к одному, без джойнов. просто затем чтобы разработчик приложения сразу юзал вьюху а не влепил таблицу а через две недели перебивал код на представление.
Вставки, обновления и удаления — тоже через представления?
Потому что все равно придется.
Странно, мне вот не приходилось последние годы. А главное, вы так говорите "перебивать код на представления", как будто это сложнее, чем исправить ровно одну строчку в приложении.
Ну а не придется для какой то таблицы наличие вьюхи никак не отразится на работе — ничего не теряем.
… кроме необходимости поддерживать два объекта БД вместо одного. Очень милое дело, ага.
lair
05.05.2016 00:19Они [представления] используются БД
Для чего?
Когда я проектирую БД я не думаю о там что там за приложеие.
Ну и зря.
Я не знаю какое будет приложение завтра, будет оно веб или не веб. Сегодня веб завтра мобильное после завтра REST сервис. Мне потом БД переделывать? А если разные приложения одновременно?
Вы правда никогда не слышали про трехзвенную или сервисную архитектуру?
lair
04.05.2016 22:12Только генерит SQL за который хочется руки повыдергивать.
Да ладно. Вы можете написать запрос, который выполняет поставленную задачу во всех случаях гарантированно и измеримо лучше?
Но всякое бывает — мало ли какое еще приложение пользует ту же БД.
Не "мало ли". Каждый слой приложения должен быть контролируем, иначе вы просто не сможете вносить предсказуемые изменения.
Никто не проектирует БД в зависимости от того какой ORM в приложении.
Правда? Никто-никто, никогда?
Mendel
04.05.2016 21:56+1Вопрос не в том что лучше, вопрос в том, что проще.
Если я могу передать модельку и не думать о том что для нее надо еще что-то тащить, потому что оно отлично достается по связям, то я так и сделаю.
Про вьювы как "бест практик" это вы отожгли да. На каждый чих по вьюву. Больше вьювов, красивых и разных… Вьювы нарушают структуру кода утягивая часть бизнеслогики в базу, что усложняет анализ кода другими разработчиками. Да и самим спустя время. Вьювы уместны тогда и только тогда когда выигрыш от них заметен. Или сокрытие чего-то, или производительность или еще что-то. Один из тех инструментов которыми как и статикой любят злоупотреблять новички.caballero
04.05.2016 22:07Вопрос не в том что лучше, вопрос в том, что проще.
ORM никаким каком не может относится к «проще».
не думать о том что для нее надо еще что-то тащить, потому что оно отлично достается по связям,
как раз связи и надо тащить.
Вьювы нарушают структуру кода утягивая часть бизнеслогики в базу, что усложняет анализ кода другими разработчиками.
Это в случае использования ORM при котором нужно понимать какие где таблицы.
Но ставить телегу впереди лошади, то есть ORM впереди проектирования архитектуры хранилища — это точно не бэст практикс.
lair
04.05.2016 22:12Это в случае использования ORM при котором нужно понимать какие где таблицы.
Это у вас кривой ORM. В нормальном нужно понимать, где какие сущности.
andrewnester
04.05.2016 18:53+1как раз-таки модель/сущность, соответствующая какому-то домену, особенно сложному и имеет наличие множества связей.
опережая ваш ответ, что в большинстве сайтов такое не надо — так и ведь сущность (Entity) как таковая там не нужная, достаточно просто реализовать Table Data Gateway
http://martinfowler.com/eaaCatalog/tableDataGateway.htmlcaballero
04.05.2016 19:10во первых бизнес сущности обьективны. Если юзер есть то он есть как его не назови.
Во вторых — суть идеи в том что тому кто работает с сущностью юзер в простом проекте — вообще ничего не надо реализовывать.
кроме отнаследоватся пустым классом и указат ему имя таблицы.
maxru
04.05.2016 17:12+41. Возьмём готовый DBAL из сотни файлов и прикрутим к нему ArrayAccess + Iterator, которые обзовём Entity. Ура, у нас получился ORM (ну, допустим) из одного класса.
2. Не забудем, что чтение из docblock — это долгая процедура и прикрутим хранилище метаданных с кешированием. Ура, у нас уже больше одного файла.caballero
04.05.2016 17:19Возьмём готовый DBAL из сотни файлов и прикрутим к нему ArrayAccess + Iterator, которые обзовём Entity. Ура, у нас получился ORM (ну, допустим) из одного класса.
да, представте себе получим. Но тут не совсем ORM, впрочем какая разница как его назвать. Назовите хоть нинзя-черепашкой.
Это решение которое позволяет резко упростить написание кода для работы с БД. Это для тех кому надо ехать а не шашечки.
Не забудем, что чтение из docblock — это долгая процедура
сколько часов занимает чтение? А то разрабы Symfony не в курсе.
прикрутим хранилище метаданных с кешированием
это забота ADODB посему просто прикручиваем одну строку в composer.json
nitso
04.05.2016 17:24+1сколько часов занимает чтение? А то разрабы Symfony не в курсе.
У doctrine/annotations как минимум есть кэш. Это к слову о чтении аннотаций.
А doctrine/orm генерирует proxy-файлы, в которых помимо нативного php-кода больше ничего нет.caballero
04.05.2016 17:34-1а у меня ничего никто не генерирует не плоит кучу прокси и прочего барахла. И в этом суть решения.
И пофиг сколько будет парсится комент — десять микросекунд или двадцать — не актуально для 99.9% реальных проектов.
andrewnester
04.05.2016 17:44с микросекундами вы слишком ошибаетесь, особенно если вернуть сотню Entity и для каждой распарсить аннотации
caballero
04.05.2016 17:58так не парсится для каждой это метаописание — парсится один раз когда формируется запрос к БД.
andrewnester
04.05.2016 18:11Вы видимо сами не разобрались как у вас класс работает, у вас на каждый вызов конструктора вызывается init() и в нем getMetadata(), которая не кешируется нигде.
Поправьте если не прав.caballero
04.05.2016 18:55-2да. Там инициализируется нулем ключевое поле. Но как показалла практика в реальных проектах обычно метод init переружается — датами там проинициализировать или типа того.
Про кеширование думал но пока не вижу что хуже — терять микросекунды на парсинге или терять память на кешировании.
Впрочем ни то ни другое не критично если проект не высоконагруженый. А для таких проектов вопрос быстродействия решается комплексно, в том числе понаписывается и кеширование.
maxru
04.05.2016 18:16+1> Это решение которое позволяет резко упростить написание кода для работы с БД. Это для тех кому надо ехать а не шашечки.
Для тех кому надо ехать уже давно придумали хотя бы и Doctrine ORM
> сколько часов занимает чтение? А то разрабы Symfony не в курсе.
Да почему же, в курсе, они же используют AnnotationReader от Doctrine
http://doctrine-orm.readthedocs.io/projects/doctrine-common/en/latest/reference/annotations.html
Ниже уже написали, как это работает.
> это забота ADODB посему просто прикручиваем одну строку в composer.json
Ну так прикрутите Doctrine ORM и не нужно будет ни одного файла.
nitso
04.05.2016 17:13+2Чтобы взять и использовать, не хватает поддержки сеттеров-геттеров для полей, как и в большинстве подобных библиотек. Допилить их не большая проблема, но видеть их из коробки — уже большое дело. Поддержка сеттеров и геттеров помимо типизации полей дает возможность легко допилить эмуляцию реляционных связей.
Это если закрыть глаза на:
- статические вызовы и переменные (
$class::
в эту же копилку) - отсутствие тестов (и не очень-то большую подготовленность к тестированию, честно говоря)
- малую распространенность ADODB
Ну а если бы вы положили все это в репозиторий, добавили README.md и composer.json, остальные участники помогли бы вам сделать 90% рутинной работы.
Было бы интересно посмотреть на сравнение (плюсы/минусы) вашей библиотеки и других существующих, вроде:
P.S. в чем преимущества ADODB перед, скажем, Doctrine DBAL?
caballero
04.05.2016 17:42-1Чтобы взять и использовать, не хватает поддержки сеттеров-геттеров для полей
Никто не запрещает писать гетеры сеттеры но суть решения в том чтобы ВООБЩЕ ничего не писать.
Поддержка сеттеров и геттеров помимо типизации полей дает возможность легко допилить эмуляцию реляционных связей
противоречит идее решения.
Было бы интересно посмотреть на сравнение
ну сравните. Я не знаю что делают это решения -пусть опишут их на Хабре. Но судя по коду мое проще как в реализации так и в использовании. Да и не ORM у меня и не построитель SQL.
в чем преимущества ADODB перед, скажем, Doctrine DBAL
в том что там есть нужный мне функционал, причем не требующих никаких дополнительных настроек.
nitso
04.05.2016 18:08Никто не запрещает писать гетеры сеттеры но суть решения в том чтобы ВООБЩЕ ничего не писать.
Не о том речь. Этого было бы достаточно, с условием поддержки остальных вызовов внутри вашего кода (из-за final сделать обертку без внедрения в ваш код невозможно):
public final function __set($name, $value) { if ($setter = $this->getSetter($name)) { $setter($name, $value); } else { $this->fields[$name] = $value; } }
Но нужно принимать во внимание этот подход, когда придумываете названия другим методам. Сейчас приличную часть кода в связи с этим было бы правильным переписать.
противоречит идее решения.
Не стоит противостояние гибкости реализации записывать в ключевые принципы.
Идея-то интересная. Реализуйте вы её не "как хочу", а, почитав вдумчиво вполне здравые комментарии, получился бы не просто какой-то набросок на gist'е, а полноценная библиотека, которая бы нашла своего пользователя. А пока только минусы ловите :(
caballero
04.05.2016 21:23-1Не о том речь. Этого было бы достаточно, с условием поддержки остальных вызовов внутри вашего кода (из-за final сделать обертку без внедрения в ваш код невозможно)
ну уберите финал — это же опенсорс
Но нужно принимать во внимание этот подход, когда придумываете названия другим методам. Сейчас приличную часть кода в связи с этим было бы правильным переписать.
любой програмист глядя на код другого програмиста скажет — я бы его переписал
Реализуйте вы её не «как хочу», а, почитав вдумчиво вполне здравые комментарии, получился бы не просто какой-то набросок на gist'е,
Это не набросок на гисте а немного упрощенный вариант неоднократно используемого решения. Код не идеален но это решение упрощает мне работу и меня это устраивает.
И таки да — речь об идее. Разве хабр не для этого? Готовые решения это в раздел Я пиарюсь.
А пока только минусы ловите
Ну понятно — есть люди которым не нравится если не по ихнему.
Я например не ставлю минусы за то что человек фанат ORM.
nitso
05.05.2016 11:39ну уберите финал — это же опенсорс
Challenge Acceptedcaballero
05.05.2016 11:46-1не знаю что это значит но проблема с финалом у вас а не у меня
Я описал идею. Для того и Хабр — для обмена идеями и опытом.
Мог описать на «пальцах» и никто бы не придирался к коду. Но это другая крайность.
Просто считайте что код это драфт который я набросал на салфетке в кафе для демонстрации идеи. И забудьте про финалы, моки и прочие несуществующие в контексте статьи проблемы.
caballero
04.05.2016 18:01-1Ну а если бы вы положили все это в репозиторий, добавили README.md и composer.json, остальные участники помогли бы вам сделать 90% рутинной работы.
не помогли бы — у каждого свой самый клевый велосипед.
nitso
05.05.2016 12:02P.S. До недавнего момента я упорно думал (не прочитав документацию по ссылкам, разумеется), что речь о MS ADO
Вчитавшись в документацию и коды, я не нашел никаких преимуществ ADODB(php) перед Doctrine/DBAL, кроме большего количества поддерживаемых БД (7 vs 25 с натяжкой).
Напротив: комбайн все-в-одном (это может быть и плюсом, и минусом), древняя архитектура с множеством legacy-кода, отсутствие современных стандартов и тестов, глобальные функции, отсутствие неймспейсов и т.д.
SamDark
04.05.2016 17:18+4По-моему, там не совсем entity по ссылке. Ну и именовать методы надо получше, а то видим
findCnt
, понимаем, что описочка вышла, букву пропустили. Только вот сходу не ясноu
пропустили илиo
...caballero
04.05.2016 17:49-1По описанию понятно суть, кто хотел понять. Терминология не принципиальна… Редко какое решение 100% соответствует терминам.
Ну и именовать методы надо получше, а то видим findCnt, понимаем, что описочка вышла, букву пропустили. Только вот сходу не ясно u пропустили или o...
Решение используется в куче моих проектов как то стремно что то переименовывать.
Кому надо возмет и переименует как захочет.
SamDark
04.05.2016 19:14+1Если уж хочется назвать как-то по своему, не вижу препятствий. Но, я бы всё-таки не переиспользовал уже существующие устоявшиеся термины, которые означают несколько иные решения. Путает.
Я уже делал эту ошибку и могу сказать, что порождаемые ей непонятки и дискуссии кушают кучу времени. Лучше превентивно ошибки избежать...
caballero
04.05.2016 20:32ну я не знаю какой тут термин. Скорее Entity чем что то другое.
Имеется класс соответсвующий бизнес-сущности, сущности представленой строкой в БД.
Есть какие-то тонкости? А в каких решениях именуемых Entity их нет?
andrewnester
04.05.2016 21:08во многих фреймворках это называется Active Record. Такое же название использует и Фаулер в PoEAA
caballero
04.05.2016 21:33да
с точки зрения архитектуры Active Record.
С точки зрения бизнес-логики — Entity. Кто сказал что AR не может быть Entity?
И с каких пор Фаулер стал истиной в последней инстанции.
andrewnester
04.05.2016 21:40+1просто довольно авторитетный человек
Просто то, что у вас — это классическая Active Record, сущность, которая знает как достать себя из БД и как себя сохранить
Не знаю зачем вы не стали использовать довольно устоявшийся термин
VolCh
09.05.2016 12:55+1С точки зрения проецирования бизнес-логики на реляционную БД, далеко не каждая запись в базе данных является бизнес-сущностью. Например, строка в таблице БД, представляющей собой табличную часть счёта (бизнес-сущности), не является бизнес-сущностью, она являются частью агрегата, принадлежащего бизнес-сущности (счёту). Строка табличной части счёта не имеет, как правило, никакой бизнес-идентичности, если даже в базе данных ей создан суррогатный первичный ключ типа id — строки заказа если и сравниваются между собой, то только по значению, если группируются, то тоже по одному или нескольким значениям, на идентификатор самой строки ссылки могут быть разве что в CRUD интерфейсах работы со счётом.
caballero
09.05.2016 15:12С точки зрения проецирования бизнес-логики на реляционную БД, далеко не каждая запись в базе данных является бизнес-сущностью
КО подтверждает.
Строка табличной части счёта не имеет, как правило, никакой бизнес-идентичности, если даже в базе данных ей создан суррогатный первичный ключ типа id
Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
Хотя часто бывают и не связанные с физическими сущностями записи — например бухгалтерские счета или проводки. Впрочем это не мешает использовать тот же механизм работы с БД. К примеру у меня в фреймворке компонент таблица сам управляет выборками из бд для сортировки и пагинации через универсальный интерфейс DataSource- ему все равно как оно там называется.
lair
09.05.2016 15:41+2Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
Нет. В заказе (как и счете на его выполнение) стоят не товары, а строки заказа/счета — это не сущность, а объект-значение, полностью определенный триадой "заказ-товар-количество" (и этот объект значение является частью агрегата "заказ", не имеющей никакого смысла за пределами этого агрегата).
Я, несомненно, видел системы, где было смоделировано иначе — заказ мог иметь несколько строк с одним и тем же товаром — и там, несомненно, такая строка была отдельной сущностью, но это либо было неаккуратностью, и всех такое поведение смущало, либо, напротив, специально заложенным поведением — к сожалению, для ситуаций, когда домен используют не по прямому назначению.
Mendel
09.05.2016 16:36Хм. Интересный вопрос кстати подняли.
Давайте возьмем для примера не «заказ» а «фитосанитарный сертификат».
Я немного упрощу предметную часть (да и не помню я ее уже в деталях).
Заказ является производным от сертификата (ну точнее сертификат производный от заказа, но не суть.
Значит сертификат выдается на партию товара.
Партия может быть как одного наиметования, так и нескольких.
Ехать как на одном транспортном средстве, так и на разных.
Может иметь неограниченное количество отправителей и неограниченное количество получателей.
10 отправителей и 10 получателей это редкий частный случай который встречается в довольно узком контексте большегрузных теплоходов (60тыс тонн пшеницы идет одним пароходом из Украины в Китай), но законом и международными соглашениями сценарий предусмотрен.
Сделать у сертификата фиксированные отправители и получатели это чисто предметная ошибка. Идем к части описания самой партии.
Партия описывается табличной частью.
У записей табличной части есть поля описывающие груз. Это не одно поле а несколько. Например «Номенклатура» == Пшеница (реально связь по ИД), «Свойство» == Озимая (текстовое, важно для анализов).
Есть количественные показатели, которые бывают разные — колво мест, единица измерения мест (ящики, палеты...) колво груза, единица измерения колва (тонны, кубометры, штуки) и т.п. Единицы измерения естественно связями на справочник. Тут более менее очевидно.
Далее описание «носителя» или формально «транспортного средства» к котрому относится машина, контейнер, прицеп, трюм/параход и т.п. Тут идет тип «носителя» (связью), номер т/с (текстом), если судно, то связь на «рейс» в котором указано ИД теплохода и дата прихода в порт, чтобы их отличать, название трюма можно в принципе совместить с полем «номер т/с».
Я тут опустил 80% связей и полей, чтобы не усложнять. И да, мы тут уже упростили техпроцесс условившись, что одновременно номер машины и номер контейнера мы не указываем, хотя такие вещи были на откуп локальных правил, и это еще та задача внедренца согласовать это с центром.
Теперь вопрос номер раз — делаем ли мы тут фильтрацию на уникальность, и если да, то уникальность чего? Составных ключей? В одном т/с может быть несколько видов груза, один вид груза может быть в разных т/с и даже в разных видах (убил ты того кто 10 вагонов пшеницы и 5 контейнеров пшеницы в совокупности считает одной партией, но что поделаешь — жизнь). Тут или сложная логика на уникальность, или излишняя нормализация с введением промежуточных сущностей объединяющих некоторые поля.
Чрезмерная оптимизация структуры неизбежно нарвется на сайдэффекты.
Большегрузы я уже упоминал. Там к примеру есть нюансы типа того что загружать трюмы нужно одновременно, и разделить их по отправителю или получателю (чего требовали теоретики из министерства) немного нереалистично.
(Для понимания масштаба — представьте себе грузовик с 20тонн зерна. Добавьте ему еще прицеп на 20 тонн. Теперь представьте вереницу из 1500 таких машин (по факту будет 2000 машин ибо часть всё равно не будет сцепками). Это всё грузится в один пароход, и уходит под одним сертификатом.
Еще из сайд-эффектов: я уже пару лет не работаю в этой сфере. Насколько я знаю опять поменялось министерство, главк, и местные структуры объединили и разъединили, так что я даже и не знаю как называется контора где работают бывшие коллеги. Да и офис у них в процессе переезда. Но я уверен, что до сих пор существует отчет, который уже неизвестная мне организация каждый квартал направляет в неизвестное мне министерство. В котором тонны упорно складываются с кубометрами. Потому что составители формы не учли, что существует номенклатура (древесина) которая может считаться как в тоннах так и в кубометрах (обычно единица одна на номенклатуру).
Еще из сайдэффектов — при отправке образцов через DHL экспедиторы DHL везут в лабораторию больше зерна, чем отправляется клиенту в виде образцов (селекционеры самолетом через DHL шлют по 2кг образов, а у лаборатории минимальная партия на отбор 3кг, ну или как-то так). И не сайдэффект, а просто экзотика — курьер DHL возит моим коллегам флешку для нашего флоппинета. Это было оптимальным решением потому что интернет в аэропорту монополистами загнан в цену 100$ за 500килобит, и других операторов не пускают, а безпроводной глушится помехами. Ну и да, грузы в основном DHL, так что мне не сложно было с ними договориться)
Так вот, два вопроса — стоит ли накладывать ограничения на записи в табличной части документа, за которое вы выступали в своем комментарии? Я считаю что не стоит, кроме как когда оно именно надо (т.е. по умолчанию не делать, но если техпроцесс клиента может вызвать ошибки, то либо склеивать дубли, либо запрещать неуники).
Вопрос второй — создавать ли отдельные модельки и считать ли их полноценными бизнессущностями для таких служебных подзаписей? Тут я в размышлениях…lair
09.05.2016 16:53+1стоит ли накладывать ограничения на записи в табличной части документа, за которое вы выступали в своем комментарии?
Я вам честно скажу: предметная область явно сложнее, чем то, что я могу понять из короткого описания, не рисуя модель, а на рисование модели время, все-таки, жалко, да и уточняющих вопросов придется задать слишком долго. Пока что у меня есть ощущение, что "партия" — это отдельная сущность, имеющая свой жизненный цикл, а, значит, надо ее идентифицировать отдельно (и в этом случае уникальность уже не обязательна).
Вопрос второй — создавать ли отдельные модельки и считать ли их полноценными бизнессущностями для таких служебных подзаписей?
Модели создавать надо всегда (у value object тоже есть модель). А вот считать ли полноценными сущностями — это тот же вопрос, что и выше.
Mendel
09.05.2016 17:43Ну вы достаточно однозначно и недвусмысленно ответили, и без рисования структуры. Ваш ответ коротко звучит как «исходя из необходимости предметной области» :) Дальше уже прикладной уровень, не теоретический.
lair
09.05.2016 17:45+1Ну так разделение "сущность — объект-значение" всегда определяется только и исключительно предметной областью. Другое дело, что есть какие-то широкоизвестные контексты (типа того же заказа с его деталями), которые всегда обсуждаются, как пример, и для них есть типовые решения.
caballero
09.05.2016 17:22даже не представляю какая тут проблема.
записи в таблице — однозначно бизнес сущности. То что к ним приатачены разные атрибуты (как скалярные так возможно и ссылки на другие сущности) сути дела не меняет.
Если товар может повторятся — значит составной ключ — какие проблемы.
создавать ли отдельные модельки
в этом и удобство реализации классов с динамическими параметрами — один и тот же класс можно использовать для разных таблиц. А вот в .NET для этого используются анонимные классы — ну подход тот же не плодить (ыв смсле не обьявлять явно) стопицот моделей на каждый чих.
lair
09.05.2016 17:35+1А вот в .NET для этого используются анонимные классы — ну подход тот же не плодить (ыв смсле не обьявлять явно) стопицот моделей на каждый чих.
Это в каких случаях?
(я уж молчу про то, что в .net есть динамические типы)
Mendel
09.05.2016 17:57один и тот же класс можно использовать для разных таблиц
Ходил таким путем. Сейчас у меня в основной ветке своего движка именно такой подход, где структура данных, различные обработчики для различных событий и полей прописываются в едином конфиге (или в распределенном, не суть), и обрабатываются одним классом, кроме небольшого количества моделей у которых специфика не укладывается в конфиг и требует отдельного класса.
Но на практике столкнулся с существенным набором проблем, и на следующий большой рефакторинг архитектуры (изменение средней цифры АПИ) запланирован уход от такого подхода.
Буду пробовать для типовых моделей что-то вроде
eval("class $className extend $parentName {}");
Ожидаю что издержки такого подхода будут меньше выигрыша от восстановления штатного дерева наследований, штатных интструментов определения родственных связей, типхинтинга и т.п.
Хотя если говорить об основной мысли, что бОльшую часть бизнеслогики можно перекинуть в конфиг, то это да, этот подход в целом себя оправдывает на небольших и средних проектах. До больших пока не добирался.lair
09.05.2016 18:00Хотя если говорить об основной мысли, что бОльшую часть бизнеслогики можно перекинуть в конфиг,
О, это тема для еще одного длинного и прекрасного холивара.
Mendel
09.05.2016 18:25Ну так давайте похоливарим, чего бы и нет? Должна быть какая-то польза от этого топика? :)
Из не совсем канонических подходов у меня валидация живет сразу в модели, но это удобно и часто встречается. Также у меня на атрибутах модели идут правила — правила это не только валидация, но и beforSave и afterLoad (для каждого правила, применяемого к тем атрибутам для которых они указаны. У правила могут быть как для всех событий методы так и только для одного). Естественно сценарии.
Ну и в языковом модуле однозначная связь с именами полей идет, т.е. у языковых констант есть «неймспейсы» связанные с разными классами, в том числе и с моделями. Что тоже по умолчанию для небольших вещей в конфиге идет.
Далее — у каждой модели есть свой построитель запросов, в моем случае с учетом некоторого сахара я его называю box. Это что-то впроде finder`a. Он в большинстве случаев как раз одним классом делается, его редко когда переопределяют (хотя да, ему можно указать какой конекшн для базы и тп.)
В нем обычно переопределяем только дефолтные лимиты, и направления сортировки.
Ну и связи конечно же, по аналогии с yii1.
Большинство вопросов реально решается написанием специфического правила, и связями.
С ростом проекта конечно у половины моделек появляются свои методы, и одним crud не отделаться, но на начальном этапе можно и без кода жить.
Из плюсов — список разрешенных для текущего сценария полей всегда можно взять в конфиге, не играясь с зависимостями классов, что упрощает построители (и не требует дибильных запросов структуры базы каждый раз, а потом кеширования этих запросов как делают довольно часто)). Можно вешать снаружи всякие генераторы миграций, активрекорды и т.п. не засовывая их в мясо модели.
Очень удобно когда есть структура которую можно увидеть в частности для RBAC. В частности у меня в yii1 был приятный модуль который создавал дефолтные правила для CRUD и отдельное правило для owner если у модельки есть связь на юзера. (Например право редактировать комментарии и право редактировать СВОИ комментарии). Закрывает много рутинной работы. Ну и конечно же очевидный плюс с простотой бутстрапинга — конфиги поправил, и уже работоспособные модельки…
Из минусом — некоторый отход от кошерного SOLID с излишней централизацией ответственностей.caballero
09.05.2016 18:38-1Ну так давайте похоливарим, чего бы и нет? Должна быть какая-то польза от этого топика?
Холивар тут уже неделю идет — пока никакой пользы нет. Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
Из не совсем канонических подходов у меня валидация живет сразу в модели, но это удобно и часто встречается.
Удобно и разумно — дна из причин выкинуть «канонические» подходы, придуманные людьми, которые уже давно не программируют а пишут книги о программировании — так просто денег больше платят.
но и beforSave и afterLoad
у меня такие же методы, но зачем какие то правила на атрибутах — просто пропишите все правила здесь в одном месте.
все что ниже цитировать не стал — нечто заумное и запутаное (я так понимаю «канонический» поход). SAP хотите переплюнуть по количеству гемороя в проекте?
lair
09.05.2016 18:42Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
У вас есть маппинг связей с ленивой/жадной загрузкой?
VolCh
09.05.2016 20:10Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
Вы просто написали свою библиотеку ORM. Примитивную, правда (маппинг полей на базу 1:1, связей нет и т. д.), но ORM.caballero
09.05.2016 20:41это не ORM. Хотя делает то что и ORm и даже больше, но не требует никакого маппинга и длиннючих строк со стрелками чтобы сформировать в конечном итоге sql запрос.
Так я учился писать сложные запросы в конце 90х. Создавал их билдером в Access97 потом смотрел что получилось и переносил в код.
Вообще, декларативный подход в работе с хранилищами данных красиво смотрится на бумаге но очень хреново на практике.
lair
09.05.2016 23:37+1Хотя делает то что и ORm и даже больше, но не требует никакого маппинга
Правильно написанный ORM тоже не требует маппинга
длиннючих строк со стрелками чтобы сформировать в конечном итоге sql запрос.
Ну да, лучше написать SQL-запрос руками, и руками же следить, чтобы в нем не было ошибок. Намного надежнее.
VolCh
10.05.2016 19:50Как это не требует маппинга? Она осуществляет маппинг 1:1, свойство прямо маппится на столбец.
Mendel
09.05.2016 18:49Холивар тут уже неделю идет — пока никакой пользы нет.
С вами холивар и не может быть конструктивным, вы с высоты своего опыта не слышите оппонентов.
Удобно и разумно — одна из причин выкинуть «канонические» подходы
Сначала человек учится как правильно, потом человек начинает понимать почему именно так правильно, потом человек может осознанно нарушать правила осознавая к чему это приводит и какова цена. Потом человек учится писать правила. Так устроена наша жизнь. Только такой подход работает в массовом процессе. Когда нужен гарантированный результат. И не важно пишешь ты код, печешь пироги или изготовляешь горшки, строишь дороги или разводишь дрозофилл — всегда одно и тоже. Опускание одного из этапов ВСЕГДА ведет к одному и тому-же результату…
у меня такие же методы, но зачем какие то правила на атрибутах — просто пропишите все правила здесь в одном месте.
Потому что можно один раз написать правило TimestampDate и просто указывать его имя у нужных полей. А он уже будет создавать объект инкапсулирующий классы языка отвечающие за время, или просто конвертировать (например по переданным ему в настройках той же записи или в настройках соответствующей глобальной компоненты) строковое представление даты в таймштамп при сохранении и разворачивании строки из таймштампа при чтении по шаблону из того же конфига, уже с настройками локали, таймзоной и т.п.
Засовывать это в события — слишком «мокро».
lair
09.05.2016 18:42Из не совсем канонических подходов у меня валидация живет сразу в модели
Что значит "сразу в модели"? Вы это считаете конфигурацией или программированием?
Также у меня на атрибутах модели идут правила
Аналогично, это конфигурация или нет?
Он [построитель запросов] в большинстве случаев как раз одним классом делается
Я вижу слово "класс", из которого делаю вывод, что вы тоже о программном коде говорите.
Так что я пока не очень понял, где же у вас бизнес-логика в конфигурации.
На самом деле, большая часть холивара упирается в то, сколько инструментов статического контроля есть для языка, на котором вы пишете, и какие у него возможности рефлексии.
Скажем, проблема
список разрешенных для текущего сценария полей всегда можно взять в конфиге, не играясь с зависимостями классов, что упрощает построители (и не требует дибильных запросов структуры базы каждый раз, а потом кеширования этих запросов как делают довольно часто))
Мне вообще не понятна, потому что в .net-мире ORM лишний раз в структуру БД не лазит — ибо зачем, схему можно построить по метаданным классов.
… а подход
Можно вешать снаружи всякие генераторы миграций, активрекорды и т.п. не засовывая их в мясо модели.
… давно является подходом по-умолчанию, потому что горький опыт показывает, что модель должна иметь минимум зависимостей от DAL. Но при этом все это пишется (можно писать) в коде, а не в конфигурации.
Mendel
09.05.2016 19:06Можно писать в коде, да. Но это лишняя связность. Нужно делать некий интерфейс доступа к информации о структуре в модели. Или через рефлексию, или оставив какие-то методы у модели (часто статические) чтобы внешние модули могли узнать ее структуру.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования. Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней. Или в конфиге руками или в админке. Держать структуру в одном месте это дает определенный набор гибкости, как в организационном плане, так и в бутстраппинге/прототипировании.
Если задача простая, то ее может сделать опытный гумманитарий с аналитическим складом ума (да, такие существуют) и опытом хоть какого-то управления проектами (линейный руководитель, мелкий предприниматель и т.п.) Они сравнительно дешевы, и что важнее — лучше общаются с конечными пользователями. Не требуется лишняя прослойка переводчика с языка бизнеса на язык кодера.
Если задача нетривиальная, то с ней нужно как-то поиграться, поредактировать, увидеть вживую…
Да, можно прототипировать, рисовать схемы и т.п. Это хороший рабочий подход. Но иногда хочется написать MVP и покрутить его вживую, и без единой строчки кода :) Да так, чтобы потом его не выбрасывать а сразу использовать в проекте.
Очень удобна в этом плане кодогенерация, например в том же Yii. Но тут есть больное место — изменять ты можешь это только пока не вносил больших реальных изменений в сгенеренный код. Иначе мержинг кодогенерации и ручного кода вызовет слишком много издержек…
Чтобы было понятно, приведу пример кусочка конфига моделей для проекта где структура базы и некоторые архитектурные решения остались в наследство.
return [ 'search' => [ 'rules' => [ 'default' => [ 'q' =>[['string']], ], ], ], 'contact' => [ 'rules' => [ 'default' => [ 'name' =>[['string', 'maxlen'=>64], ['default', 'value'=>'Имя НЕ указано']], 'email' =>['email','required'], 'phone' =>[['string', 'maxlen'=>64], ['default', 'value'=>'Телефон НЕ указан']], 'subject' =>[['string', 'maxlen'=>64],'required'], 'message' =>[['string', 'minlen'=>10],'required'], ], ], ], 'subscription' => [ 'rules' => [ 'default' => [ 'id' => [['int']], 'name' =>[['string', 'maxlen'=>64], ['default', 'value'=>'Уважаемый клиент']], 'mail' => ['email','required'], 'hash' => [['string']], 'ip' => [['string']], 'date' => [['timestampDate'],['defaultCurrentTime']], ], ], ], 'services' => [ 'rules' => [ 'default' => [ 'id' => [['int']], 'url' => [['string']], 'title' => [['string']], 'text' => [['string']], 'desc' => [['string']] ], ], 'box' => [ 'limit' => 100, ], ],
В принципе примерно подобные «конфиги» встречаются во многих ORM, неканоничность у меня в основном в том, что обычно они размазаны по разным классам, а у меня лежат в одном месте.caballero
09.05.2016 19:37-1Можно писать в коде, да. Но это лишняя связность. Нужно делать некий интерфейс доступа к информации о структуре в модели.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования.
у вас телега впереди лощади
Сначала определяются связи (на уровне ТЗ) потом програмируются.
Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней.
да, типичная фантастика — аналитик потягает что то мышкой по экрану, прога прочитает связи и будет с ними работать.
Забудте. Во всяком случае пока у вас нет пару лимонов баксов и штата програмеров в пару сотен.
В принципе примерно подобные «конфиги» встречаются во многих ORM,
лишнее подверждение — ORM мастдай.
Вот вам код из реального (не самого большого) проекта по логистике (писал не я если что).
string sql = "DECLARE @TransportReturnOrdersID varchar(10), @ReceivingOn datetime, @sourceLocationName varchar(255), @OldsourceLocationName varchar(255), @targetLocationName varchar(255), @ItemName varchar(100), @OldItemName varchar(100), @EstimatedQuantity int, @ActualQuantity int, @Variance int, "; sql += "@SubTotalEstimatedQuantity int, @SubTotalActualQuantity int, @SubTotalVariance int, @TotalEstimatedQuantity int, @TotalActualQuantity int, @TotalVariance int "; sql += "SELECT @OldsourceLocationName = '', @OldItemName = '', @SubTotalEstimatedQuantity = 0, @SubTotalActualQuantity = 0, @SubTotalVariance = 0, @TotalEstimatedQuantity = 0, @TotalActualQuantity = 0, @TotalVariance = 0 "; sql += "CREATE TABLE #ReturnOrderItems(TransportReturnOrdersID varchar(10) Null, ReceivingOn datetime Null, Caption varchar(10) Null, sourceLocationName varchar(255) Null, targetLocationName varchar(255) Null, ItemName varchar(100) Null, EstimatedQuantity int not Null, ActualQuantity int not Null, Variance int not Null) "; sql += "DECLARE ReturnOrderItems CURSOR LOCAL FOR "; sql += "SELECT CAST(TransportReturnOrders.ID as varchar), ISNULL(TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn), ReturnOrderItems.sourceLocationName, ReturnOrderItems.targetLocationName, ReturnOrderItems.ItemDescription, SUM(ISNULL(ReturnOrderItems.quantityShipped, 0)) as EstimatedQuantity, SUM(ISNULL(ReturnOrderItems.quantityReceived, 0)) as ActualQuantity, SUM(ISNULL(ReturnOrderItems.quantityReceived, 0)-ISNULL(ReturnOrderItems.quantityShipped, 0)) as Variance "; sql += "FROM viewReturnOrderItems ReturnOrderItems INNER JOIN TransportReturnOrders ON (TransportReturnOrders.ID = ReturnOrderItems.TransportReturnOrdersID) AND (TransportReturnOrders.Status IN (@ReceivingStatus, @ReceivedStatus, @PreliminaryReceivedStatus)) "; sql += "WHERE (ISNULL(TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn) BETWEEN @startDate AND @endDate) "; if (ItemsID != 0) { sql += "AND (ReturnOrderItems.ItemsID = @ItemsID) "; } if (sourceLocationID != 0) { sql += "AND (ReturnOrderItems.sourceLocationID = @sourceLocationID) "; } else { if (!string.IsNullOrEmpty(username)) { var sqlAdd = new Managers.UserRole().GetLocationsForCustomerUser(username); if (!String.IsNullOrEmpty(sqlAdd)) { sql += "AND (ReturnOrderItems.sourceLocationID IN " + sqlAdd + ") "; } } } if (targetLocationID != 0) { sql += "AND (ReturnOrderItems.targetLocationID = @targetLocationID) "; } sql += "GROUP BY TransportReturnOrders.ID, ISNULL(TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn), ReturnOrderItems.sourceLocationName, ReturnOrderItems.targetLocationName, ReturnOrderItems.ItemDescription "; sql += "ORDER BY ReturnOrderItems.ItemDescription, ReturnOrderItems.sourceLocationName, ISNULL(TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn), ReturnOrderItems.targetLocationName "; sql += "OPEN ReturnOrderItems "; sql += "FETCH ReturnOrderItems INTO @TransportReturnOrdersID, @ReceivingOn, @sourceLocationName, @targetLocationName, @ItemName, @EstimatedQuantity, @ActualQuantity, @Variance "; sql += "WHILE @@FETCH_STATUS = 0 BEGIN "; sql += "IF (@OldsourceLocationName <> @sourceLocationName) OR (@OldItemName <> @ItemName) BEGIN "; sql += "IF @OldsourceLocationName <> '' BEGIN "; sql += "INSERT INTO #ReturnOrderItems(ReceivingOn, Caption, sourceLocationName, targetLocationName, ItemName, EstimatedQuantity, ActualQuantity, Variance) "; sql += "VALUES(Null, 'Sub Total', @OldsourceLocationName, '', '', @SubTotalEstimatedQuantity, @SubTotalActualQuantity, @SubTotalVariance) END "; sql += "SELECT @OldsourceLocationName = @sourceLocationName, @SubTotalEstimatedQuantity = 0, @SubTotalActualQuantity = 0, @SubTotalVariance = 0 "; sql += "IF (@OldItemName <> @ItemName) BEGIN "; sql += "IF @OldItemName <> '' BEGIN "; sql += "INSERT INTO #ReturnOrderItems(ReceivingOn, Caption, sourceLocationName, targetLocationName, ItemName, EstimatedQuantity, ActualQuantity, Variance) "; sql += "VALUES(Null, 'Total', '', '', @OldItemName, @TotalEstimatedQuantity, @TotalActualQuantity, @TotalVariance) END "; sql += "SELECT @OldItemName = @ItemName, @TotalEstimatedQuantity = 0, @TotalActualQuantity = 0, @TotalVariance = 0 END END "; sql += "INSERT INTO #ReturnOrderItems(TransportReturnOrdersID, ReceivingOn, sourceLocationName, targetLocationName, ItemName, EstimatedQuantity, ActualQuantity, Variance) "; sql += "VALUES(@TransportReturnOrdersID, @ReceivingOn, @sourceLocationName, @targetLocationName, @ItemName, @EstimatedQuantity, @ActualQuantity, @Variance) "; sql += "SELECT @SubTotalEstimatedQuantity = @SubTotalEstimatedQuantity + @EstimatedQuantity, @SubTotalActualQuantity = @SubTotalActualQuantity + @ActualQuantity, @SubTotalVariance = @SubTotalVariance + @Variance, "; sql += "@TotalEstimatedQuantity = @TotalEstimatedQuantity + @EstimatedQuantity, @TotalActualQuantity = @TotalActualQuantity + @ActualQuantity, @TotalVariance = @TotalVariance + @Variance "; sql += "FETCH ReturnOrderItems INTO @TransportReturnOrdersID, @ReceivingOn, @sourceLocationName, @targetLocationName, @ItemName, @EstimatedQuantity, @ActualQuantity, @Variance END "; sql += "CLOSE ReturnOrderItems "; sql += "DEALLOCATE ReturnOrderItems "; sql += "IF @OldsourceLocationName <> '' BEGIN "; sql += "INSERT INTO #ReturnOrderItems(ReceivingOn, Caption, sourceLocationName, targetLocationName, ItemName, EstimatedQuantity, ActualQuantity, Variance) "; sql += "VALUES(Null, 'Sub Total', @OldsourceLocatio
Mendel
09.05.2016 19:53+1Искренне сочувствую заказчику и надеюсь что такие динозавры скоро вымрут.
Не вникая в код (Вру, начал рефакторить) скажу что за такое отношение к оформлению кода, и т.п. человека нужно лишить прав подходить к компьютеру. Даже к «ворду».
VolCh
09.05.2016 20:21Очень похоже, что эта какая-то статистика. ORM в общем случае не предназначен для получения статистики средствами SQL — или реализуете статистику на уровне приложения, вытягивая из базы минимально необходимый граф объектов и обходя его, или реализуете статистику средствами SQL без маппинга на объекты бизнес-сущностей. Есть варианты типа создать класс типа OrderStat на который маппить средствами ORM результаты запросов какого-нибудь DBAL с агрегирующими функциями, но они очень быстро приходят к нативному SQL.
Mendel
09.05.2016 21:14В таких проектах обычно помогает грамотная денормализация. Например по аналогии с уже названной мной тут 1С — там есть сущности типа «регистр накопления», который по сути отдельная табличка которая сохраняет промежуточные значения суммированные за период (или в другом разрезе) данные. Всё это красиво обернуто в сущности языка (там предметно-ориентированный подход, но в ООП суть та же) и повешены соответствующие обработчики на родительские модели (или таблицы с тригерами?) чтобы они «двигали» значения в регистрах. Так и код читабелен остается, и скорость возрастает и много других плюсов.
Есть и другие подходы. Но в общем случае длинный и тяжелый запрос статистики это или денормализация, или просто декомпозиция.
Весь смысл тут в том, что нам кинули существенный кусок кода без форматирования, просто тупым куском запроса в адской обертке и внедрении какой-то логики генерации кода.
Это чистая "тетрадка Чуня". И для таких людей есть отдельный котел в аду.
Что забавно — этот человек говорит мне о «сложности поддержки и понимания кода» евал() из одной строчки…
michael_vostrikov
09.05.2016 23:00+1как вы себе представляете это с ORM?
Хм, а я представляю (да, мне делать нефиг)). Там запрос-то не сложный, 2 таблицы. Правда тут в основном Query Builder.
Скрытый текст$select = " TransportReturnOrders.ID, ISNULL(TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn) AS ReceivingOn, ReturnOrderItems.sourceLocationName, ReturnOrderItems.targetLocationName, ReturnOrderItems.ItemDescription, SUM(ISNULL(ReturnOrderItems.quantityShipped, 0)) as EstimatedQuantity, SUM(ISNULL(ReturnOrderItems.quantityReceived, 0)) as ActualQuantity, SUM(ISNULL(ReturnOrderItems.quantityReceived, 0) - ISNULL(ReturnOrderItems.quantityShipped, 0)) as Variance "; $groupBy = " TransportReturnOrders.ID, ReceivingOn, ReturnOrderItems.sourceLocationName, ReturnOrderItems.targetLocationName, ReturnOrderItems.ItemDescription "; $orderBy = " ReturnOrderItems.ItemDescription, ReturnOrderItems.sourceLocationName, ReceivingOn, ReturnOrderItems.targetLocationName "; $query = ViewReturnOrderItems::find(); $query->select($select); $query->groupBy($groupBy); $query->orderBy($orderBy); $query->joinWith('TransportReturnOrders'); $statusList = [ Status::ReceivingStatus, Status::ReceivedStatus, Status::PreliminaryReceivedStatus ]; $query->where('IN', 'TransportReturnOrders.Status', $statusList); $dateExpr = new DBExpression('ISNULL( TransportReturnOrders.ExpectedDeliveryDate, TransportReturnOrders.ReceivingOn )'); $query->andWhere('BETWEEN', $dateExpr, $filter->startDate, $filter->endDate); if ($filter->ItemsID != 0) { $query->andWhere(['ReturnOrderItems.ItemsID' => $filter->ItemsID]); } if ($filter->sourceLocationID) { $query->andWhere([ 'ReturnOrderItems.sourceLocationID' => $filter->sourceLocationID ]); } else { if ($filter->username) { $locationList = new Managers.UserRole() .GetLocationsForCustomerUser($filter->username); $query->andWhere('IN', 'ReturnOrderItems.sourceLocationID', $locationList); } } if ($filter->targetLocationID != 0) { $query->andWhere(['ReturnOrderItems.targetLocationID' => @targetLocationID]); } $dbResult = $query->asArray()->findAll();
Mendel
09.05.2016 23:05+1Ай молодец!
И ведь что забавно — оба ваших варианта, хоть они и наследуют недочеты архитектуры изначальной системы — легко читать и реально поддерживать )
lair
09.05.2016 23:41как вы себе представляете это с ORM?
Как ни странно, я легко себе это представляю с ORM, причем тремя разными способами — из которых как минимум один будет существеннее читабельнее.
Другое дело, что с хорошей вероятностью это, как и многую другую аналитику, просто не надо делать на OLTP-базе, а собрать в промежуточное хранилище, в которое уже лазить удобным способом (в частности, через ORM, но поверх аналитической модели).
lair
09.05.2016 23:36Можно писать в коде, да. Но это лишняя связность.
Связность чего с чем?
Нужно делать некий интерфейс доступа к информации о структуре в модели. Или через рефлексию,
Я и сказал — выбор частично зависит от того, насколько в вашей платформе мощные механизмы рефлексии.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования. Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней. Или в конфиге руками или в админке.
Been there, done that. Это очень быстро становится очень дорого в поддержке — потому что они создают структуру, которая не прошла анализ и обдумывание. К сожалению, это реальный опыт, пять лет с такой платформой имел дело.
приведу пример кусочка конфига моделей
Это все хорошо ровно до того момента, пока вы не хотите к этому обратиться из кода. После этого ваше "одно место" превращается в N: конфиг + все те места, откуда вы обращаетесь — и вам надо поддерживать согласованность.
А теперь сравните это со статически типизованным языком, в котором вы объявили модель, как класс со свойствами. Теперь каждый раз, когда вы откуда-то обращаетесь к этим свойствам из кода, компилятор проверяет правильность этого обращения — вы не можете ошибиться в имени, вы не можете запихнуть числовое поле в редактор дат. У вас есть средства рефакторинга — переименовали поле, переменовались все обращения. И при этом вы все еще можете использовать автоматические построители интерфейса — они просто анализируют ваш класс и берут его метаданные для формирования формы/списка/чего угодно.
Mendel
10.05.2016 20:18Been there, done that. Это очень быстро становится очень дорого в поддержке — потому что они создают структуру, которая не прошла анализ и обдумывание. К сожалению, это реальный опыт, пять лет с такой платформой имел дело.
Здесь да, надо быть осторожным.
И «после сборки обработать напильником». Но львиную долю итераций по уточнению ТЗ можно делать без разработчиков. Чтобы прояснить мысли клиента нужно ему их визуализировать. Мелом на доске или дать пощупать MVP — просто разные подходы. Так пусть MVP делают без меня…
И при этом вы все еще можете использовать автоматические построители интерфейса — они просто анализируют ваш класс и берут его метаданные для формирования формы/списка/чего угодно.
Да, нативная работа любого IDE если делать максимально строго это существенный плюс. Хотя на практике половина современных фреймворков нормально не подсвечиваются без специальных для них плагинов. Но благодарю что обратили мое внимание на точку зрения IDE. Поищу или какую-то документацию по этим самым плагинам, или что-то подумаю с генерацией метакомментариев…lair
10.05.2016 20:54И «после сборки обработать напильником». Но львиную долю итераций по уточнению ТЗ можно делать без разработчиков. Чтобы прояснить мысли клиента нужно ему их визуализировать. Мелом на доске или дать пощупать MVP — просто разные подходы. Так пусть MVP делают без меня…
А вот здесь и ловушка: многие заказчики, получив MVP, говорят: а запускайте это в продакшн. И вот тут начинается...
Mendel
10.05.2016 21:04Да, это существенное замечание, спасибо. Есть о чем подумать.
lair
10.05.2016 21:05… некоторые злые и умные люди рекомендуют делать MVP в PowerPoint или аналогах.
VolCh
11.05.2016 08:50Как вариант делать не на мэйнстримовых языках, или на языках/платформах, которые точно не пойдут в продакшен. Например, если в ТЗ уже чётко прописано (причём не от балды, а заказчику показно, что это оптимальное сочетание), что язык PHP, фреймворк Symfony, база PgSQL и т. п., то сделать прототип на рельсах и sqlite :)
lair
11.05.2016 11:02Эта идея озвучивалась у того же МакКоннела. Основная проблема в том, что надо иметь специалиста в таком профиле.
caballero
09.05.2016 18:22-1Но на практике столкнулся с существенным набором проблем,
потому что вы либо кинулись в другую крайность либо выбрали неверную имплементацию. Не должно быть никакого конфигурирования вообще.
Буду пробовать для типовых моделей что-то вроде
eval()? В это серьезно?
восстановления штатного дерева наследований, штатных инструментов определения родственных связей
штатные инструменты надо полагать некая разновидность ORM. Ну так я и написал о неверном выборе.
бОльшую часть бизнеслогики можно перекинуть в конфиг
Не должно быть вообще никакой бизнес-логики ни в каких конфигах — десятки XML файлов это подход десятилетней давности и десятки дышащих на ладан проектов типа Дебет-Плюс.
До больших пока не добирался.
при таком подходе никогда не доберетесь.
Когда создаешь архитектуру — нужно руководствоваться здравым смыслом а не какими то теоретическими положняками — должны быть конфиги, должны быть модели, должны быть прописаны некие связи, бизнес-сущности в таблицах это не бизнес-сущности а некие новые абстракции (хотя здравомыслящий человек смотрит на счет или сертификат и видит там именно товары) и т.д.
.
.
Mendel
09.05.2016 18:32штатные инструменты надо полагать некая разновидность ORM. Ну так я и написал о неверном выборе.
Нет, штатные средства языка. Представляете? В пхп есть вполне сносный функционал ООП, представляете?))
при таком подходе никогда не доберетесь.
Смешно это слышать от вас :)
eval()? Вы это серьезно?
Серьезно, да. А что есть какие-то проблемы? Долго думал над этим, с коллегами советовался, но не нашел существенных недостатков. Есть один недостаток, связанный с тем что опкоды не кешируются, но не силен в низкоуровневой структуре пхп. По идее грамотная реализация наследования должна тут обойтись без заметных задержек. Но даже если и не так, то ожидаю не очень большой овер по производительности в сравнении с восстановлением штатного стека типхинтинга и наследования, что таки больше ограничивает выход на большие проекты чем скорость компиляции опкода.caballero
09.05.2016 18:55В пхп есть вполне сносный функционал ООП,
тогда непонятно что вы там намудрили — никогда не было никаких проблем изза ООП.
Смешно это слышать от вас
тем не менее это вы не можете добратся к большим проектам — ООП мешает?
Не надо ёрничать — судя по вашим постам, у вас вообще смутное представление о проектировании.
Серьезно, да. А что есть какие-то проблемы?
неудобен для чтения кода и отладки. Опасен — выполнит любую иньекцию
Непроизводителен — по сути вызывается еще один экземпляр компилятора со своим контекстом.
Если ошибка, то скрипт падает. и это никак не отловить
дальше ищите в гугле.
И эти люди рассказывают как надо писать код…
восстановлением штатного стека типхинтинга и наследования, что таки больше ограничивает выход на большие проекты чем скорость компиляции опкода.
Не знаю что сия конструкция значит — но эти проблемы исключительно и ваших архитектурных решений.
первый раз вижу чтобы eval как то решал некие проблема с наследованием.
Простите но вам на больших проектах в ближайшие лет пять делать нечего.
Mendel
09.05.2016 19:18ГЫ. А в вашем понимании «большой проект» это что? Ну так, чисто поржать.
неудобен для чтения кода и отладки.
Серьезно? Вам не понятен написанный мной код?)
Опасен — выполнит любую иньекцию
Мне сложно себе представить инъекцию добравшуюся до автозагрузчика классов, да еще и не первого. Нет это возможно, но… у если человек ТАК пишет код, то у него будут проблемы посерьезнее чем евал в автозагрузчике.
Непроизводителен — по сути вызывается еще один экземпляр компилятора со своим контекстом.
Вот это как раз и смущает, но опять же. Не на ваше же мнение в этом вопросе ориентироваться.
Коллеги отвечавшие в свое время за хайлоад в одном поисковике сказали чтобы я не параноил, что издержки незначительны. Это для меня не гарантия, но мнение которогму доверяю…
Если ошибка, то скрипт падает. и это никак не отловить
Если придумаете как может упасть такой код — дам шоколадку :)
Имя метода валидируется контекстом, имя родителя валидируется при создании конфига. Но даже если родитель будет битым — тут или внятное исключение при загрузке битого родителя, или… Черт, ну ок, убедили я протестирую поведение пхп на проблемном родителе. Не хочу его валидировать, это дорогая операция. Но не думаю что могут быть проблемы… Но да, начну с этого когда дойду до задачи. Я в принципе собирался это делать но не первым делом.
дальше ищите в гугле.
Искал, представляете :) Идея уже полгода в голове, куча обсуждений, обдумываний. Не нашел ничего.Mendel
09.05.2016 19:42Собственно проверил.
Тестовый кейс:
<?php class parentClass {} $className = 'test'; $parentName = 'parentClass'; eval("class $className extends $parentName {}"); $obj = new test(); var_dump($obj);
Тестировал на логически неверные и/или логически неверные значения имени класса и имени родителя.
Пхп как и ожидалось давал внятные интуитивно-понятные ошибки:
PHP Fatal error: Class 'parentClassq' not found in */obj.php(7): eval()'d code on line 1
PHP Parse error: syntax error, unexpected '$st' (T_VARIABLE), expecting '{' in */obj.php(7): eval()'d code on line 1
PHP Fatal error: Class 'test' not found in */obj.php on line 9
PHP Parse error: syntax error, unexpected '$', expecting '{' in */obj.php(7): eval()'d code on line 1
PHP Fatal error: Class 'test' not found in */obj.php on line 9
Что-то такое помню, что вроде как ошибки евалов, даже фатальные можно перехватывать, но точно не помню. Ну в смысле без обработчиков и т.п., на месте. Но оно и без того нормально выглядит, так что остается открытым только вопрос производительности.
lair
09.05.2016 17:12полностью определенный триадой "заказ-товар-количество"
Я неправ, конечно же. Определен этот объект парой "заказ-товар", а количество — это как раз полезная нагрузка.
VolCh
09.05.2016 20:08Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
В большинстве случаев табличная часть документов типа заказов, счетов, ордеров и т. п. не содержит товаров, а содержит ссылки на товары и дополнительную информацию типа количества и цены. По сути это реализация связи многие-ко-многим между заказами и товарами через джойн-таблицу с дополнительными полями. Эта связь между сущностями сама сущностью не является, вне контекста заказа она не имеет никакого смысла для бизнеса.Mendel
09.05.2016 21:17По опыту построения не одной тысячи различных клиентских запросов по таким данным скажу что данные сабтаблицы в огромном количестве запросов являются лучшими кандидатами для основной таблицы к которой уже джоиним или связями подтягиваем остальные, так что может это и «виртуальная частица», и не особо понятна ее сущность, но тут как раз вполне логично считать ее отдельной сущностью и т.п. Пусть и «на бумаге».
ChALkeRx
04.05.2016 18:17+2И лишь слегка ассиметрично стоят щербатые котлы…
У вас в примерах очень сильно плавает стиль кода в плане расстановки пробелов, это сразу в глаза бросается.ChALkeRx
04.05.2016 18:22+2Я, впрочем, тоже молодец — опечатался в слове «асимметрично», причём два раза. И отредактировать не успел.
caballero
04.05.2016 18:57Ну я не часто пишу тут статьи и не приспособился. В редакторе пишется одно а показывается другое причем в зависимости от браузера.
nitso
05.05.2016 11:48Забавно то, что на первой же странице документации ADODB упоминается AR:
ADOdb contains components for querying and updating databases, as well as an Object Orientated Active Record library, schema management and performance monitoring
Перевод:
Библиотека ADODB наряду с компонентами для поиска и обновления данных в содержит так же ООП Active Record компоненту, средства управления и мониторинга БД
@Автор, вы не пробовали использовать уже имеющиеся AR-возможности?caballero
05.05.2016 11:59я в курсе того что есть в ADODB — я пользую ее наверно уже лет 10.
AR который там меня не устроил. Не помню почему. Может быть и можно было отнаследоваться от него. Но в случае непоняток следую принципу что агрегация предпочтительнее наследования.lair
05.05.2016 12:01+1Но в случае непоняток следую принципу что агрегация предпочтительнее наследования.
… и поэтому создаете очередной базовый класс, от которого должны наследоваться все реализации.
skey
05.05.2016 12:49Чего вы набросились то? Ясно же, что это не продакшн решение для большого проекта, а подходит только для собственных разработок или небольших сайтов визиток. Т.е. для быстрого развертывания.
bohdan4ik
07.05.2016 18:13+3А потом приходит заказчик, говорит, что есть проект и тут чуть-чуть доделать нужно. Смотришь туда, а слёзки кровавые сами текут, и остановить это, не отказавшись от проекта, невозможно. А для быстрого развёртывания подходят и существующие AR решения, которые имеют более богатый функционал (что, как раз, добавляет скорости разработки) и проверены временем, и тестами.
И не набросились, а предоставили аргументированную критику.
Велосипедостроение это полезно, сам таким занимаюсь, но выносить это на публику и предлагать как единственно верное решение («связи не нужны», «если нужны — делай create view ...», «ORM в топку» @автор) — нехорошо.caballero
07.05.2016 19:18-1Если бы такая «аргументированная критика» была кода писались нынешние распространенные ORM то и никаких ORM бы не было (тогда хоть sql запросы умели бы писать без «построителей»).
Всё когда то начиналось с велосипедов.
И это не единственное верное решение а наиболее оптимальное.
Сочетает простоту реализации с мощью нативного SQL.
Впрочем ничего не мешает к моему решению впаралель прикрутить ORM — специально для тех кто не умеет написать строку кода пока IDE не подскажет ему функцию.
lair
07.05.2016 19:32И это не единственное верное решение а наиболее оптимальное.
По какому критерию?
Fesor
07.05.2016 22:15а другие подходвы вы курили? Чем вам DAO не угодил? По вашей логике намного более оптимальное решение — систему на DAO удобно мокать, мы работаем исключительно с SQL, причем оно полностью отделено от бизнес логики… Как-то выглядит намного более гибко. В чем тогда профит?
caballero
07.05.2016 23:25не знаю какую из релизаций DAO вы имеете ввиду но то что я видел, не понимаю что такого оно делает чего не делает мое решение. То же самое касается и ORM.
Вот ради интереса прикрутил idiorm по аналогии с Pixie к своему решению.
И в чем преимущество
Customer::getORM()->select('*')->where_equal('name','ЧП Петров')->find_many()
перед
кроме в три раза большей длины строки.Customer::find("name='ЧП Петров'");
По поводу моков — опять же непонятно что именно я ими бы тестил без БД.
lair
07.05.2016 23:32+1По поводу моков — опять же непонятно что именно я ими бы тестил без БД.
Внезапно, бизнес-логику.
michael_vostrikov
08.05.2016 09:28— А у вас «ЧП Петров» как из $_GET-параметров в запрос попадает? Руками каждый раз экранируете?
— А если надо «LIKE», а не "="?
— «select(*)» необязательно вызывать
— Вы сравниваете Query Builder и Active Record. Сравнивайте тогда уж с Active Record от того же автора. Там кстати связи есть, что тоже преимущество.caballero
08.05.2016 10:14Руками каждый раз экранируете?
это просто упрощенный пример.
в adodb есть специальная функция экранирования для всех типов БД
А если надо «LIKE», а не "="?
это просто sql че надо то и пишите
«select(*)» необязательно вызывать
монстрообразность выражения не намного уменьшается
Сравнивайте тогда уж с Active Record от того же автора.
Оно строится поверх ORM.
Про громоздкость, прожорливость и бессмысленность ORM в PHP я уже писал.
Мое решение рассчитано на тех кто имеет понятие как написать sql запрос.
могу привести аналогию — какие бы не городили визуальные редакторы — верстать веб страницы руками и проще и быстрее и главное эффективнее.
В этом и суть моего решения — сочетание мощи sql запросов с максимально простым способом работы с Entity
lair
08.05.2016 10:33это просто упрощенный пример.
в adodb есть специальная функция экранирования для всех типов БД
И как же выглядит полный пример, с учетом экранирования?
michael_vostrikov
08.05.2016 10:52+1в adodb есть специальная функция экранирования для всех типов БД
Какая разница, стандартная она или из adodb. Вызывать ее все равно надо каждый раз вручную.
это просто sql че надо то и пишите
Вы не поняли вопроса, он связан с первым. Функции типа where_equal() и where_like() разделяют имя поля и значение, чтобы одно заключить в обратные кавычки, а второе в плейсхолдер запроса. Сколько нужно написать кода при использовании вашей ORM и специальной функции экранирования adodb для генерации запроса с LIKE без SQL-инъекций? И во сколько раз он будет больше приведенного вами для idiorm?
Оно строится поверх ORM
А ваше требует ADOdb, в которой 218 файлов размером под 2 мегабайта. А тут рабочий файл всего 1, размером 90 кб.caballero
08.05.2016 14:38Вызывать ее все равно надо каждый раз вручную.
если в выражении то да. А если это свойство Entity — тогда adodb самспециальной функции экранирования adodb длягенерации запросаа проекранирует если поле в Бд — строккковое
это всего лишь DB::qstr()
А ваше требует ADOdb, в которой 218 файлов размером под 2 мегабайта.
половина этого кода — драйвера для различных БД
остальное тоже используется не полностью.
Fesor
08.05.2016 16:46половина этого кода — драйвера для различных БД
остальное тоже используется не полностью.
А можно просто юзать PDO.
caballero
08.05.2016 17:03-1а можно и pconnect()
если я пользую adodb уже лет 10 значит на то есть причины.Mendel
08.05.2016 18:34+3Не причины, а причинА. И вы ее уже озвучили: «я использую эту библиотеку уже 10 лет». Всё.
Это и причина почему ее используете вы, и причина почему ее не используют большинство :)
michael_vostrikov
08.05.2016 09:50… В реальном фреймворке есть отдельный класс синглтон https://github.com/leon-mbs/zippy/blob/master/zcl/db/db.php
… Объяснять вам бесполезно -сами наберетесь опыта столкнувшишь с более менее серьезным проектом
А у вас «ЧП Петров» как из $_GET-параметров в запрос попадает? Руками каждый раз экранируете?
Ради интереса посмотрел, как у вас параметры в запрос попадают. Никак вы их не экранируете.
На гитхабе у вас есть проект zippyerp, там ссылка на сайт проекта, там ссылка на демо.
Логин:test' OR userpass = 'admin
Пароль:admin
Нажать кнопку «Ввод» мышкой. (кстати, почему?)
https://github.com/leon-mbs/zippyerp/blob/master/src/www/system/pages/userlogin.php#L42
https://github.com/leon-mbs/zippyerp/blob/master/src/www/system/helper.php#L23
(и чуть ниже на 27 строке, прямое сравнение с переданным паролем).
Причем при регистрации у вас пароль шифруется, но при логине можно указать не сам пароль, а его хеш. Утекла база с хешами, и даже ломать не надо.Mendel
08.05.2016 10:02+1Вы фсё врёти. Это никаму не нужна. Хеши и соль придумали тлусы! У нормальных разработчиков, у которых уже есть опыт — базы не утекают!
caballero
08.05.2016 10:21-2это бета версия — мне важно было спроектировать учетную часть для демки. Остальное — пока чисто для галочки еще сто раз переделается.
там вообще юзер и пароль пока плейсхолдерами стоят.
Пример придирок к мелочам вместо возражений по существу.Fesor
08.05.2016 10:26это бета версия — мне важно было спроектировать учетную часть для демки.
Вы же понимаете, что взяв полноценные решения вы бы сделали намного больше и намного быстрее?
Mendel
08.05.2016 11:11+1Не понимает. Равно как и не понимает отличие беты от альфы а альфы от прототипа.)
caballero
08.05.2016 14:32какие решения? По залогиниванию?
Вообще то я всегда беру готовые решения там где они к месту.
caballero
08.05.2016 10:25-2, но при логине можно указать не сам пароль, а его хеш.
потому что оставлена возможность на период разработки вбивать пароль руками в БД. Поэтому проверяется как на хешированую версию так и один к одному.
Утекла база с хешами
КО утверждает что если кто то добрался напрямую к базе то остальное уже не имеет значения.
Fesor
08.05.2016 10:38Знаете, читаю я этот комментарий… и мне интересно о какой такой эффективности мы вообще говорим?
Mendel
08.05.2016 11:16+1О какой, о какой. О субъективной эффективности.
Все мы любим свои велосипеды потому что они нам лучше всего понятны.
Поэтому лично для ТС этот капитанский велосипед является эффективным.
А так то если это был вопрос, а не риторическое высказывание, то он уже задавался в этом тренде, и естественно был без ответа :)
caballero
08.05.2016 14:34в этом коментарии не вижу слова эфективность
Mendel
08.05.2016 15:19+1Леонид, не обижайтесь, но не видите его вы в связи с особым метаконгитивным искажением называемым в простонародье эффектом Даннинга-Крюгера. :)
caballero
08.05.2016 15:46Наконец то! А я уже думал никто так и не приведет универсальный на все случаи жизни «аргумент» — эхвект Даннинга-Крюгера.
Mendel
08.05.2016 15:52+1Как это никто? Вы тут половину обсуждения только его и приводите. Первое что нашел:
Объяснять вам бесполезно -сами наберетесь опыта столкнувшишь с более менее серьезным проектом.
usdglander
05.05.2016 15:14Хотел было прицепиться к несоответствию заголовка статье, а потом понял, что слово “фреймворк” взято в кавычки не случайно. :)
caballero
07.05.2016 23:27Оно взято в кавычки чтобы меньше придирались (хоть это и не помогло). Но по сути если прикинуть функционал — вполне себе фреймворк.
- статические вызовы и переменные (
WST
Бизнес-логика в doc-комментариях, оригинально :)
Хорошая работа, спасибо вам, а то по заголовку я уж было боялся, что снова увижу mysql_connect и иже с ним.
caballero
ну. это скорее декларативная разметка а не бизнес логика. Такое кстати применяется в Symfony везде. Единственная проблема — коменты может покурочить какой нибудь акселератор или шифровальщик кода, потому и оставлена возможность задавать маппинг на таблицу в коде.
Но конечно нормальные аннотации в PHP было бы круто.