Комментарии 239
Хорошая работа, спасибо вам, а то по заголовку я уж было боялся, что снова увижу mysql_connect и иже с ним.
Но конечно нормальные аннотации в PHP было бы круто.
Поскольку то, как именуются поля в БД и поля объекта, для компьютера значения не имеет, то нет никаких причин чтобы они не совпадали.
Зато для компиляторов имеет значение, т.к. есть такая штука, как зарезервированные слова. Что, если уже есть готовая таблица, в которой колонка названа class
или public
?
клёвые штуки…
слаботипизированность
уж слабая типизация ничего, кроме головной боли, обычно не несёт.
Насчет слаботипизации — это отдельная долгая дискусия которой уверен на хабре было полно. Опять же в PHP с каждой версией ввожить все больше контроля типов если это нужно. Но главное — отается и нетипизированые возможности. Кстати в той же яве появилось куча скриптовых языков типа Groovy именно для того чтобы сочетать мощь типизировного языка с более простом кодированием.
ну, человек должен думать когда задает имена полям как в коде так и в структуре БД.
А если БД уже есть? И нужно пристроится к готовой БД. Колонку переименовать тоже не всегда можно. Не во всех же случаях ты и код пишешь, и БД проектируешь.
На счёт типизации — вы явно путаете слабую типизацию с динамической. Ну да ладно.
Впрочем так и не понял зачем переименовывать поля в БД — суть в том что для данного решения пофиг какое там поле. Если кто то влепил имя поля как то противоречащее ключевым словам PHP — то напишете на это конкретное свойство гетер и сеттер в классе сущности и все дела.
Впрочем так и не понял зачем переименовывать поля в БД — суть в том что для данного решения пофиг какое там поле. Если кто то влепил имя поля как то противоречащее ключевым словам PHP — то напишете на это конкретное свойство гетер и сеттер в классе сущности и все дела.
Вот только сущности обычно связаны.
Попробуйте добавить туда хотя бы hasOne и hasMany. Боюсь, сразу окажется проще взять готовое решение типа Eloquent :-)
Но я не зря написал что решение отработано и все шишки уже набиты и грабли уже наступлены.
Нет никаких проблем со связями.
Прочитайте внимательно абзац с нативным SQL.
Кроме того в отличие от например явы, контекст PHP разрушается после отработки страницы — посему нет никакого практического смысла вытягивать гроздья взаимосвязанных сущностей.
Работать с базой данных может понадобиться не только в веб-приложениях, и даже если в веб-приложениях, то необязательно в таких, которые реализуют принцип «PHP is meant to die». Конечно, иных нынче мало, но ведь в будущем ситуация может измениться — вдруг разработчики фреймворков внезапно массово заинтересуются чем-то типа PHP-SGI или хотя бы просто задействуют какой-нибудь ReactPHP.
недавно столкнулся с проблемой выбора слоя для работы с БД для приложения построенного на основе ReactPHP
Из минусов:
- Нельзя конфигурировать конекшен к базе: для разных Entity — разные конекшены (если переопределить getConnect, то конекшен переопределиться для всех классов)
- Использование магических __get, __set (что если я присвою значение несуществующему в БД полю и вызову save() ?)
- Быстродействие (парсить мтаданные для каждой сущности одного класса не кажется быстрым)
- По самому коду очень много вопросов, например https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L274 а потом вы возвращаете false
И пару вопросов:
- Как вы решаете проблему валидации?
- Где вы держите бизнес-логику?
- Как тестируете сущности?
зачем использовать ADODB, если есть PDO
вообще то я написал почему именно ADODB.
Нельзя конфигурировать конекшен к базе: для разных Entity — разные конекшены
а нафига? проект с больше чем одной БД? Давайте оставим экзотику в стороне.
Использование магических __get, __set (что если я присвою значение несуществующему в БД полю и вызову save() ?)
как я писал выше — абсолютно ничего. Потому и ADODB.
https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L274
косяк несущественный но спасибо.
Как вы решаете проблему валидации?
в методе befireSave() — по сути это как бы событие жизненного цикла — срабатывает перед записью в БД. Если нужна валидация — переопределяете метод в сущности.
Где вы держите бизнес-логику?
смотря какую. Если бизнес-логика относится к бизнес-сущности (класс User к примеру) то в этом классе, разумеется.
Как тестируете сущности?
тестирование зависит от проекта в котором данное решение используется. не вижу какой то специфики.
вообще то я написал почему именно ADODB.
как я писал выше — абсолютно ничего. Потому и ADODB.
Так дело в том, что это наоборот не очень. Я глядя на код где устанавливается поле и потом сущность сохраняет ожидаю, что оно будет сохранено. Потому что не указано какие поля виртуальные, а какие реальные в БД.
Да и тянуть целую библиотеку ради возможности из массива сделать UPDATE/INSERT запрос тоже не вижу смысла, это пишется очень просто.
а нафига? проект с больше чем одной БД? Давайте оставим экзотику в стороне.
не такая уж и экзотика.
как вы тогда справляетсь с разными конфигурациями БД на живом, тестовм и локальном сервере?
тестирование зависит от проекта в котором данное решение используется. не вижу какой то специфики.
валидация сущности в beforeSave, он protected. Я хочу протестировать валидацию. Как мне это сделать без БД?
Я к тому, что класс жёстко завязан на БД и нельзя мокнуть её, а мокать БД в тестах — первое дело
косяк несущественный но спасибо.
https://gist.github.com/leon-mbs/7fb0a0881253a583017dadcdd8179325#file-entity-php-L96
а ещё вместо таких конструкций проще вызвать метод сразу со static::
как вы тогда справляетсь с разными конфигурациями БД на живом, тестовм и локальном сервере?
поменял адрес в конфиге и все дела.
Я к тому, что класс жёстко завязан на БД и нельзя мокнуть её, а мокать БД в тестах — первое дело
не хочу усложнять что можно не усложнять. Пока нормально обхожусь комплексным тестированием на уровне приложения.
а ещё вместо таких конструкций проще вызвать метод сразу со static::
не совсем понял но мне нужно имя как раз вызывающего класса-наследника.
я и не претендую на идеальное качество кода — это я писал не просто так а для фреймворка который тоже писал не для красоты. В этом случае хочется сразу писать конечные задачи а не возится с моками. Собственно на этих задачах я все и отлаживал. Это не по классике но это опенсорс, я за деньги не продаю.
Загляните для интереса в некоторые очень популярные проекты — там не то что моки и ORM там дикая смесь кода разметки и запросов к БД.
Но если людям нужно ехать а не шашечки то как то справляются.
Одной из распространенных архитектур для данного класса задач является паттерн Active Record. В частности, по этому шаблону строятся так называемые Entity (сущности), в том или ином виде использующиеся в ряде платформ, начиная от персистентных бинов в EJB3 заканчивая EF в .NET.
А ничего, что в EF никакого ActiveRecord нет, там честный ORM?
$thesameuser = User::load($user->user_id);
Статический метод? А тестировать как?
(про повторное использование подобной статики тоже вопрос открытый)
Статический метод? А тестировать как?
это к разработчикам PHP — зачем они ввели в язык статические методы которые оказывается невозможно тестировать.
вы как обычно придираетесь не по существу. Будем считать что вы уже отметились коментом и в этой статье тоже. Поставьте галочку что не пропустили и все.
это к разработчикам PHP — зачем они ввели в язык статические методы которые оказывается невозможно тестировать.
Нет, это конкретно к вам — потому что вы используете эти методы. Каждому инструменту своя область применения.
(Я так понимаю, по поводу EF ответа не будет)
Я так понимаю, по поводу EF ответа не будет
более бесполезного занятия чем дискутировать что такое EF в статье про PHP не нашли?
это к разработчикам 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.
Но не все. Например метод find никак не вынести потому что он по имени класса определяет с какой сущностью надо работать. ПО мне так удобнее чем каждый раз указывать параметр.
Что касается моков — да, с академической точки зрения должны быть моки, покрытие тестами и т.д. Но здесь бОльшую часть работы выполняет ADODB. Если убрать слой работы с БД то «мокать» останется только ассоциативный массив и магичские методы что имеет мало смысла.
Попросту говоря там нечего тестить будет вообще.
То есть тут лучше тестировать комплексно вместе с реальными сущностями и БД.
Сейчас ваше решение тянет на одноразовое
Вообще то могу показать ссылки на сайты в инете сделанные с таким решением. Не понимаю что вы имеете ввиду.
В конце концов задача статьи, как принято на Хабре, описать идею а не продать код.
огромное количество качественных решений
это как правило копии ORM с ява и.нет платформ.
Покажите решение где не нужно например мапить поля таблицы БД на классы, следить за типами и выполнять кучу другой работы которую мое решение попросту автоматизирует. Я не переизобретаю то что уже есть.
теперь посмотрим на ваш случай:
На этот случай как раз смотреть на надо. В реальном фреймворке есть отдельный класс синглтон https://github.com/leon-mbs/zippy/blob/master/zcl/db/db.php
Здесь я просто сложил в кучу на скорую руку чтобы не распылятся.
Разумеется можно вынести коннект, вынести статику и т.д.
Ну выносите кому надо — я просто предложил концепцию которой уже давно пользуюсь.
Почему вместо сути все концентрируются на том сответствует ли решение академическому подходу, читал ли я Фаулера и что использование представлений — отстой?
с академической точки зрения
Ох уж эта академическая точка зрения. Я не ее пытаюсь объяснить, а юзкейсы для статики. То что для каждого правила есть исключения, вызванные желанием сэкономить на качестве — это нормально. Но только делая все это нужно делать акцент на том, что подобные решения увеличивают технический долг. Если проект простой — то не страшно, а если он будет расти и развиваться, может наступить момент когда все это сделает разработку болью. Сложность именно понять что когда применять, потому новичкам лучше сразу давать пожеще, с большим количеством правил и ограничений, и по мере постижения смысла этих правил и ограничений расслаблять хватку.
Попросту говоря там нечего тестить будет вообще.
Именно для этого AR и придумывали. Этот паттерн идеален для проектов, где логика идеально ложится на реляционную базу, и сущности тонкие. По сути это комбинация Domain Object + Row Data Gateway, правда люди об этом забыли и именно domain object частенько выкидывают, юзая AR-модельки как анемичные модели, тупые структурки данных.
Вообще то могу показать ссылки на сайты в инете сделанные с таким решением.
А говнокода, знаете ли, более чем хватает. Так что на любую даже самую стремную задумку примерчик с продакшена найти не так уж и сложно будет.
В конце концов задача статьи, как принято на Хабре, описать идею а не продать код.
Вас могут читать впечатлительные PHP-ники, которые наберутся этого и потом их будет долго переучивать. Любая точка зрения достойна существования, и ваш подход имеет место применяться в реальных проектах, но только если знаешь что делаешь. Большинство PHP-ников обитающих на хабре — не знает что хорошо а что плохо, а стало быть нужно четко прописывать допущения для решения. что мол "логики мало, хорошо на реляционку ложится, для прототипов".
Покажите решение где не нужно например мапить поля таблицы БД на классы, следить за типами и выполнять кучу другой работы которую мое решение попросту автоматизирует. Я не переизобретаю то что уже есть.
Легко. Вот первое что выдал поиск: https://github.com/j4mie/paris Так же если память не изменяет, реализация AR в Yii и Laravel делает ровно то же самое.
юзая AR-модельки как анемичные модели, тупые структурки данных.
если уж на то пошло Entity еще более тупые структуруки… Что не мешает их юзать в EJB3 например.
может наступить момент когда все это сделает разработку болью
во первых, подавляюзее число проектов именно простые. Это и есть решение которое экономит время на относительно простых проектах.
Таких проектах которых рядовому разрабу приходится клепать десятками.
А уж если что создаст проблеммы на больших проектах то \то как раз модные нынче ORM потому что во-первых создается неоптимальная структуру хранилища, во вторых еще более неоптимальные SQL запросы. В некоторых БД созданных через code first со временем сделать сложную выборку по большим данным даже нативный SQL не помогает без денормализации.
Опять же речь о PHP. В яве не так критично вытащить список ненужных ща Entity через связи- они останутся некоторое время в памяти и могут быть подхвачены вместо обращения к БД (во всяком случае в контейнерах EJB3). В PHP подобные решения мягко говоря не оправданы.
Вот первое что выдал поиск.
части фреймворков, не в счет. Пикси — надстройка над громоздким ORM со всеми вытекающими последствиями.
если уж на то пошло Entity еще более тупые структуруки…
Ну уж нет. Entity — это термин из DDD, и предполагает он вполне себе конкретную вещь, а именно — объект, наделенный идентичностью (отличной от его значимых атрибутов) и жизненным циклом. DTO — не entity.
Это и есть решение которое экономит время на относительно простых проектах.
Я могу предложить вам решение еще проще — тупо юзать mongodb + rest интерфейс к ней. И вообще никаких проблем и необходимости писать бэкэнд. Ну и опять же можно ваять мидлвэры с бизнес ограничениями если проект будет усложняться. Главное что бы все объекты бизнес-транзакции в один документ укладывались.
А уж если что создаст проблеммы на больших проектах то \то как раз модные нынче ORM потому что во-первых создается неоптимальная структуру хранилища, во вторых еще более неоптимальные SQL запросы.
Вы просто не умеете их готовить.
1) ORM нужны только для задач, в рамках которых у нас есть простая бизнес транзакция с небольшим количеством объектов. Скажем если вам репорт нужно сагрегировать — SQL. А еще можно просто дублировать информацию в том виде, который будет удобен для запросов. Есть миллион различных вариантов как работать с данными, не нужно зацикливаться на одном и говорить что это плохо во всех случаях просто потому что большинство оверюзят подход.
2) ORM ответственны только за мэппинг релейшенов между объектами. Все остальное — это другие паттерны. Причем "генериться" хранилище только в случае data mapper и вы строго можете контролировать этот процесс. В остальных случаях хранилище проектируете вы.
3) ORM это абстракция. Любая абстракция имеет свой оверхэд. Потому, в документации к таким решениям как Hibernate например четко прописано что "ребята, это текущая абстракция". То есть если мы хотим скорость разработки и выразительность языка — мы можем юзать абстракцию на полную. А там где нужна производительность — мы можем смешать все с нативным SQL.
Например если у нас в проекте есть сложное взаимодействие между бизнес сущностями — мне намного удобнее и проще описать это все в виде взаимодействия сущностей, протестить, и потом прицепить дата мэппер что бы подключить базу. Но если мне нужно будет делать какие-то сложные выборки этих объектов на чтение — то я просто сделаю SQL запрос и замэплю результат не на сущность а сразу на DTO.
В PHP подобные решения мягко говоря не оправданы.
у нас с вами видимо разные задачи. В моем случае более чем оправдано, как минимум потому что скорость разработки вырастает на порядок. А во вторых, решения вроде Doctrine имеют огромную кучу оптимизаций внутри, которые неплохо справляются.
части фреймворков, не в счет. Пикси — надстройка над громоздким ORM со всеми вытекающими последствиями.
То что я скинул — это самодостаточная библиотека. Причем ут пикси если библиотека называется "париж"?
~~Вам уже 100500 раз сказали, что если отбросить всю конкретику вашего решения, то "суть" капитанская и несколько устарела.
"Идея — очевидная, спасибо Кеп" — это одна мысль. Повторять ее смысла нет.
"Ваша реализация отстой, особенно бац1, бац2, бац" — может быть бесконечно разнообразным, потому что бац1, бац2, и бац3 может быть одним из множества замечаний. Вот большинство и идет по второму пути.~~
Нет, конечно всё не так. Просто все тупые, и только один вы знаете сакральные истины и по достоинству оцениваете гениальную сущность подхода и самого кода.
Решение работает — экономит нехилое количество времени кодирования и строк кода. Для того оно и сделано а не для того чтобы мерятся пиписками кто современнее и навороченее.
Решение работает — экономит нехилое количество времени кодирования и строк кода.
Это так же не по существу, вы же не предоставляете никаких подтверждений этим словам.
В рамках маленьких проектов оно будет хорошо показывать себя. В рамках проектов чуть сложнее бложика — уже могут быть проблемы. С тем же успехом можно было бы просто отказаться от подобного и использовать старый добрый table data gateway, который еще проще и намного более гибкий.
После прочтения статьи понимаешь что про ADODB тоже ничего толком нет и даже чтобы понять функции ADODB нужно идти на сайт и вычитывать документацию. Содержание статьи «Это круто что есть такая библиотека».
Эм…
Обертка над PDO — один класс на пару методов.
Обертка над ответом PDO для абстракции реализующая итератор и АrrayAccess и инстансцирование сущностей в классы (опционально). Еще один класс. На два экрана.
Построитель запросов для основных операций. Еще один класс (ну два, если операций много).
Базовый класс типа вашего Entity только с Итератором и АrrayAccess, ну и еще по строчке добавить чтобы фильтровать по списку разрешенных полей, и еще пять строк чтобы у нас уже методы getИмяПоля() и setИмяПоля() работали.
Собственно всё. Пишется быстро, работает как часы. Дальше уже ORM идет с валидациями и связями. Которые кстати тоже не экзотика а суровая необходимость. О чем статья?
о чем понимаешь только к середине статьи.
по моему вы не поняли ни к середине ник концу ни с обратной стороны.
чтобы понять функции ADODB нужно идти на сайт и вычитывать документацию
для применения данного решения вообще не надо понимать функции ADODB.
это как пример вашего «понимания»
Дальше уже ORM идет с валидациями и связями. Которые кстати тоже не экзотика а суровая необходимость.
Нету такой необходимости в ПОДАВЛЯЮЩЕМ большинстве проектов.
О чем статья?
Жаль что вы не поняли о чем. Возможно из-за заангажированости.
==
Ржу. Если я не понял смысла вашей статьи, то или его там нет, или я плохо понимаю, или вы плохо объясняете. Но поскольку я заангажирован, то вариант что смысла нет и что вы не умеете объяснять конечно не рассматриваются.
Перечитал статью. Или я слепой, или кроме упоминания 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;
}
}
А забота ADODB корректно запихнуть данные массива в БД.
Работа с базой:
<?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(isset($allowed[$key])) $result[$key] = $value;
}
return $result;
}
else {
return FALSE;
}
}
public function table() {
return $this->quoteId($this->table);
}
}
ПС: За ляпы и стиль сильно не бить, до ревью и рефакторнга еще не добрался, сам знаю что кривенько :)
Я не понимаю зачем бы я повторял эту функциональность в своем решении.
Зачем проверять структуру? Тяжелая наркомания и не более.
Если я неправильно написал имя свойства, то я хочу увидеть исключение, которое схоронится в лог и выдаст красивую 500-ошибку юзеру, а не проигнорирует какой-то пункт в моем запросе и повалит или базу или логику прав доступа.
Формирование запросов под разные БД — да, не дописал пока. Но то такое. Работа измеряется в часах.
Форматирование даты? Мелочь и вкусовщина. У меня этим занимается ORM, ибо это именно его уровень ответственности, и ему знать какие форматы даты предпочтительнее в разных случаях. Я лишь указываю структуру базы, полей связей и т.п.
И кстати фильтрация по доступным полям таки есть, см. метод protected function allowed($name).
НО всё это фигня. Вы так и не ответили на вопрос — так о чем статья то? О том что при наличии библиотек которые за тебя уже написали (кривых библиотек, да, ибо раз оно без PDO то deprecated по определению), уже написанные функции можно не писать? Или о том что «решает ADODB и решает их гораздо лучше»? Ну решает. Ну есть такая библиотека. Ну Есть в пхп методы __гет() и __сет(). Дальше что? О чем статья?
А зачем расставлять кавычки? PDO уже отменили?
mysqli_connect тоже не отменили.
При прямой работе с PDO нужно вставлять значения полей параметрами. То есть писать кучу кода как запрос так и параметры.
В моем решении НИЧЕГО писать не надо. Умножьте ничего (и соответственно ноль ошибок) на сотни мест в проекте где надо обращатся к БД и получите не одну человеко-неделю.
О том что при наличии библиотек которые за тебя уже написали (кривых библиотек, да, ибо раз оно без PDO то deprecated по определению),
Вообще то ADODB может работать и через PDO — какой вариант укажешь так и работает.
Если я неправильно написал имя свойства, то я хочу увидеть исключение
а я хочу писать в моей сущности свойства которые не хранятся в БД (мало ли какая бизнес логика в бизнес сущности а не просто копии строки БД) и чтобы не было изза этого никаких исключений.
Разница просто в «я хочу» не более того.
НО всё это фигня. Вы так и не ответили на вопрос — так о чем статья то?
И не должен — но то есть текст статьи.
Отказ от параметров в запросах с написанием оных руками (иначе зачем «множество раз»? У меня вон и CRUD и массовый и единичный, и условия по массиву и по единичной записи — уже готовые генерятся) — это путь либо в wet либо в дырявое решето.
Что касается связей — я имел ввиду не то что существуют логические связи в бизнес-логике а то что необходимо каждый раз вытаскивать все эти связи в виде гроздьев объектов на страницу контекст которой будет разрушен через долю миллисекунды и в следующую милисекунду пять посылать в Бд запрос с кучей джойнов (причем неоптимальный запрос — ORM не такие умные ) и создавать в памяти десятки если не сотни не нужных в данный момент объектов.
Это имеет смысл в яве где объекты сущностей могут находится в памяти ява-машины и быть повторно использованными. Но речь конкретно о PHP а не о теоретизировании насчет общих принципов работы с БД.
необходимо каждый раз вытаскивать все эти связи в виде гроздьев объектов на страницу контекст которой будет разрушен через долю миллисекунды
А никто и не говорит про "каждый раз". Но это с завидной регулярностью оказывается нужно в бизнес-логике, и вот тогда адекватные агрегаты оказываются намного проще для использования, чем несвязанные классы.
А никто и не говорит про «каждый раз». Но это с завидной регулярностью оказывается нужно в бизнес-логике,
Это смотря как писать бизнес-логику. У меня есть проект учетной системы с кучей сущностей и ни разу не понадобилось вытаскивать какие то связи. Просто такова особенность PHP — каждый раз страница формируется по новой посему обьективно нужно вытаскивать только то что отображается на данным момент. Поэтому нет никакой «завидной регулярности» и быть не может. По крайней мере до тех пор пока в PHP не появится возможность персистентного хранения объектов в памяти.
Это смотря как писать бизнес-логику.
Ну да, тут вам никто, включая Эванса, не указ.
У меня есть проект учетной системы с кучей сущностей и ни разу не понадобилось вытаскивать какие то связи.
Завидую вам, что тут еще скажешь.
Просто такова особенность PHP — каждый раз страница формируется по новой посему обьективно нужно вытаскивать только то что отображается на данным момент.
Ну например, надо вытащить список кастомеров с количеством и общей суммой заказов для каждого кастомера. Как это сделать, сохранив объектную модель, и не имея связей?
Для 99.9% проектов которые пишет каждый день обычный трудяга -програмист нафиг не нужны никакие ни «жадные» ни «щедрые» виды связей.
Это просто не актуально. Глупо усложнять решение тем что будет использоватся в одном случае из тысячи. Для этого есть другие готовые решения.
Ну возмите статистику по посещаемости — у какого процента сайтов средняя загрузка хотя бы сотня обращений к Бд в секунду. Или по другому — сотни тысяч посещений в сутки.
А касательно кеширования и ленивой загрузки — во всех проектах для быстродействия мы делали это. Неважно с или без ORM
А если проект длительный и постоянно переделывается да так чтобы не свалился продакшен то там неизбежно возникает такой бардак и столько костылей что зачастую разруливается только нативными SQL. И не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
User, который связан с UserProfile, UserPreferences, UserACL. Всем этим так или иначе надо управлять. и это идёт из требований.
И не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
мы с вами наверное совершенно на разных проектах работаем, потому что у нас теория вписывается в практику. понятно, что есть неидеальные моменты, но все эти книги как раз таки написаны, основываясь на практике.
User, который связан с UserProfile, UserPreferences, UserACL. Всем этим так или иначе надо управлять.
Не вижу зачем тут нужен именно ORM. Да и не во всех проектах есть ACL и тому подобное.
мы с вами наверное совершенно на разных проектах работаем
на каких проектах мы работаем не имеет значения и не относится к сути дела.
Я не предлагаю замену doctrine или еще чего.
Речь о том чтобы упростить разработку обычных сайтов — не порталов и не соцсетей. Сайтов где не критична скорость и нет сотен таблиц в БД.
И таких сайтов подавляющее большинство.
Ок. Не 99.9%. Возмем принцип Парето — 80% — это тоже до фига.
Речь о том чтобы упростить разработку обычных сайтов — не порталов и не соцсетей. Сайтов где не критична скорость и нет сотен таблиц в БД.
ну в таком случае ваше решение тоже не самое простое, зачем beforeSave, afterSave и тд?
Да и нету там ничего сложного — строчка кода в методе Save()
Но суть не в том сложное решение или нет. Суть в том чтобы упростить разработку.
Проблема аналогичных решений не в том что они сложные (в ADODB тоже кода до фига)
а в пороге вхождения и количестве танцев с бубном при использовании — маппинге, генерации всяких прокси и т.д. вместо просто сесть и писать прикладную бизнес логику. Которую потом и читать проще будет тем кто сопровождает.
После нормализации базы производители отделены от марок автомобилей.
Ну и хочу вывести во вью описание автомобиля. Хочу сделать во вью простое <?=$mark->brend()->name?> но не могу ибо связей нет, и приходится это городить уже в контроллере.
Нужно програмировать от бизнес-логики а не от того могу ли я написать выражение с двумя стрелочками.
Во вью в терминах MVC мешает здравый смысл. Во вью в терминах SQL мешает то, что его нет, и не предвидится. Вообще не рекомендуется ими злоупотреблять без особой необходимости.
Во вью в терминах MVC мешает здравый смысл.
ИМХО использование MVC в вебе вообще лишено здравого смысла (и проблемы работать с представлениями если они есть только это подтыверждают). Лично я предпочитаю компонентные решения.
Вообще не рекомендуется ими злоупотреблять без особой необходимости.
Вообще ничем в не рекомендуется употреблять без особой необходимости.
А где это имя хранить в вашей архитектуре?
На уровне БД машины и производители лежат в разных таблицах. Т.е. нужен будет джойн. В коде это, соответственно, 2 класса-сущности.
Как с помощью вашей библиотеки получить список список авто и связанных с ними производителей?
Как поддерживать консистентность данных при этом?
Вообще, не знаю, что за проекты у вас, но в большинстве проектов, использующих реляционные БД так или иначе возникают потребности в связях (не хранить же всё денормализированным).
Как с помощью вашей библиотеки получить список список авто и связанных с ними производителей
не вижу зачем одновременно нужен список машин и список производителей со всеми полями.
Поле имени производителя выберется из представления и будет доступно в сущности авто (это же PHP с динамическими свойствами).
если вам понадобится просмотреть детальную инфу производителя это будет уже другая страница с другим контекстом к в котором можно загрузить целиком производителя как отдельную сущность.
использующих реляционные БД так или иначе возникают потребности в связях
Вы понимаете что логическая связь в БД и то что вы пытаетесь выбирать это не одно и тоже. Даже если связь в БД прописана констрейтами это ничего не меняет.
Попробуйте мыслить не категориями ORM а бизнес-логикой приложения — ORM вторичен его наличие или отсутсвтвие никак не влияет на архитектуру данных в БД.
а что мешает во вью машин выводить имя производителя?
То, что теперь представление в БД (вы же его имеете в виду) напрямую зависит от того, какие данные нужны в UI. И каждое изменение в UI будет влиять на БД.
Нужно програмировать от бизнес-логики
Так то, что показано — и есть пример бизнес-логики (как ее понимает, скажем, Эванс).
не надо мне про книги типа совершенный код, паттерны проектирования и прочую теорию которая НА ПРАКТИКЕ работает не далее написания ТЗ.
То есть подождите, я правильно вас понял, вы считаете GoF, PoEAA, Code Complete и так далее — не работающей на практике теорией?
В свое время работал в почтовом ящике где проектировали системы управления для космоса ( а пока я ходил в детсад для SS-18 Satan).
И как-то то работало (и кое где до сих пор) без книг фаулера, макконела и банды четырех.
Ничего не имею против теории но она не заменит здравого смысла, прагматического подхода и прямых рук.
Я, вроде бы, задал конкретный вопрос — перечисленные книги работают на практие или нет?
И как-то то работало (и кое где до сих пор) без книг фаулера, макконела и банды четырех.
Ключевое слово — "как-то". Речь идет не о том, что без них не работает, речь идет об увеличении качества/уменьшении расходов.
Черт, у меня контроллеры до 30 строк доходят потому что связей не хватает!!! рас
Посему их количество не имеет значения.
Делайте модели не от фонаря а чтобы они соответствовали бизнес-сущностям и не будет вопроса.
Таким образом это имя появится в объекте сущности
Заказ. Только нужно указать имя представления в анотации класса чтобы вибирать с него а не с таблицы.
Или по вашему вытаскивать юзеров целиком на каждую строку записи о заказе это правильней? А если к заказу еще есть манагер который отвечает за заказ? и их всех тащить вместо приджойнить имя манагера в представление?
И что для каждого случая писать свой вью?))))
Мне кажется я понимаю откуда берутся люди пишушие такие вещи как опенкарт.
Кроме того если вам нужны сложные выборки, например заказчик захотел витиеватый отчет) вам никакой ORM не поможет.
ALTER VIEW
для каждого?Делается какой-нибудь метод
with($relationName)
, который после основного запроса делает второй к таблице пользователей с условием WHERE id IN ($userIDs)
, и заполняет связь user
у всех $orders
. Без лишних джойнов.во вторых выбирать джойнами одно поле или делать еще один запрос и выгребать из БД все данные по юзерам чтобы использовать только имя — не уверен что второй способ разумнее.
Совершенно серьезно. У вас информация с sql.ru?
Хотя и в Mysql это не проблема если речь не идет о высоконагруженном проекте о коих мы не говорим.
И ответьте пожалуйста, почему представления это хорошо? Я спрашиваю абсолютно серьёзно, может я что-то упустил в познаниях БД.
Всё, что я могу сказать по представлениям — это вынос логики из кода в БД, так же как хранимые пооцедуры (если не ошибаюсь представления и реализовпны как хранимки), поэтому он должен быть обоснован
ок, что такое промышленные сервера БД
MSSQL, Oracle и иже с ними. Сервера для решений энтерпрайз уровня.
Сервера в разработку алгоритмов оптимизатора запросов вкладывают милионы долларов. Чего очевидно никто не вкладывает в mysql.
если не ошибаюсь представления и реализовпны как хранимки
Представления реализованы как SQL запросы.
это вынос логики из кода в БД, так же как хранимые пооцедуры
насчет ХП согласен. Представления никак не влият на бизнес-логику вам никто не мешает обращатся прямо к таблицам.
представления позволяют избежать написания сложных SQL запросов.
А запросы бывают не на один десяток строк — особенно в разного рода репортах.
Если даже удастся такое собрать в ORM то либо ляжет приложение либо ляжет сервер БД.
кроме того вьюхи это логический слой — вам может понадобится скоректировать структуру БД Обычное дело — проект растет набирает данных и сервер дохнет.
Если у вас вьюха вы меняете структуру таблиц и ваше приложение продолжает работать.
это реальные ситуации в каждом втором реальном проекте.
Сервера в разработку алгоритмов оптимизатора запросов вкладывают милионы долларов.
… и эти оптимизаторы давно уже не делают различий между представлением и просто запросом.
Если даже удастся такое собрать в ORM то либо ляжет приложение либо ляжет сервер БД.
Почему это?
кроме того вьюхи это логический слой — вам может понадобится скоректировать структуру БД
Для этого уже давно придумали слой прикладного сервера.
Очевидно же что если есть заказ то там есть заказчики так или иначе понадобится посмотреть кто он.
Нюансы скрываются в "так или иначе", которые очень быстро могут меняться с каждой версией требований.
Пользователь, подключающийся к БД, один, программный код один, запрос с джойнами генерируется ORM либо пишется один раз, права пользователя приложения удобнее проверять кодом на серверном языке, а не в SQL.
запрос с джойнами генерируется ORM либо пишется один раз
Только генерит SQL за который хочется руки повыдергивать. Я тоже напишу его один раз если он не вписывается в существующие представления.
права пользователя приложения удобнее проверять кодом на серверном языке, а не в SQL
А кто против? С этой точки зрения и констрейнты не нужны.
Но всякое бывает — мало ли какое еще приложение пользует ту же БД. А еще могут и руками напрямую полезть.
Никто не проектирует БД в зависимости от того какой ORM в приложении. Сначала проектируется грамотная структура хранилища а потом хоть ORM хоть чертом.
Я тоже напишу его один раз если он не вписывается в существующие представления.
Только вы еще и запросы для представлений напишете, и поддерживать их будете.
Никто не проектирует БД в зависимости от того какой ORM в приложении
Никто не проектирует с точки зрения «а еще могут и руками напрямую полезть». Вернее, может кто-то и проектирует, но делать так не надо.
Проектирование БД и навешивание представлений на спроектированную БД — это не одно и то же. Наличие/отсутствие ORM на структуру таблиц не влияет. Вопрос в том, зачем использовать представления в веб-приложении.
Никто не проектирует с точки зрения «а еще могут и руками напрямую полезть».
вобще то именно такие вещи и учитываются при проектировании — потому и ставятся констрейты.
Пусть это не прямо руки -пусть это просто другое приложение, десктопное, мобильное и т.д.
А в более менее сложных проектах без исправлений руками в БД вообще не обходится. Гладко бывает только в книгах по реляционной алгебре.
Вопрос в том, зачем использовать представления в веб-приложении.
Они используются БД а не в веб приложении.
Когда я проектирую БД я не думаю о там что там за приложеие. Я не знаю какое будет приложение завтра, будет оно веб или не веб. Сегодня веб завтра мобильное после завтра REST сервис. Мне потом БД переделывать? А если разные приложения одновременно?
Нет, я ее проектирую как положено проектировать хранилища данных.
Даже не понимаю смысла данной дискусии.
Кстати, не подскажете ссылки на материалы о том, что положено проектировать хранилища данных через представления?)
Зачем переделывать БД? Таблицы одинаковые для любого приложения.
потому что это приходится делать НА ПРАКТИКЕ по объективным причинам.
И не потому что это кому то хочется.
подскажете ссылки на материалы о том, что положено проектировать хранилища данных через представления?)
как только покажете литературу где написано что положено использовать ORM.
потому что это приходится делать НА ПРАКТИКЕ по объективным причинам.
Какие объективные причины требуют от вас переделывать таблицы с появлением нового приложения?
как только покажете литературу где написано что положено использовать ORM.
Ну так PoEAA же.
Ладно, литературу вы почему-то привести не желаете, может какой-нибудь пример из практики приведете?
Так же как есть неписаные законы по написанию програмного кода, верстке и т.д.
И есть многолетная практика разработки (в том числе и мой личный опыт)
Литература — это для начинающих.
Опытный разработчик БД — сразу как создал таблицу, создает и вьюху на нее. Одни к одному, без джойнов. просто затем чтобы разработчик приложения сразу юзал вьюху а не влепил таблицу а через две недели перебивал код на представление.
Потому что все равно придется.
Ну а не придется для какой то таблицы наличие вьюхи никак не отразится на работе — ничего не теряем.
Но практика показывает обратное приходится менять структуру, (или как минимум типы полей). Причины — начиная от изменения требований к проекту заканчивая необходимостью
денормализации после того как Бд заполнена данными и долгих танцев с бубном с профайлером и изучением планов запросов чтобы выяснить узкое место.
Представте что вы пишете приложение на PHP. Вы можете написать код PHP (лии верстку HTML и CSS) так чтобы в процессе проекта его ни разу не пришлось коректировать? Сомневаюсь.
Точно так же и с БД.
Объяснять вам бесполезно -сами наберетесь опыта столкнувшишь с более менее серьезным проектом.
— спрятать какие-то данные(колонки) из запрсов
— спрятать тяжелые запросы, например, при генерации отчета
— для некоторых таблиц заведомо было известно, что их структура будет меняться, причем часто. Там тоже добавляли вью
Лично я вас просил указать статьи/книги и тд, где рассказано, почему обильное использование представлений хорошо, потому что мой опыт и опыт коллег это не доказывает. На мой взгляд, добавление вьюх на каждую таблицу добавляет лишней сложности, потому что теперь сопровождать надо не только таблицы, но и вью
Андрей, Вы что еще не поняли?
Человек ОПЫТНЫЙ, неопытных не слушает.
К сожалению у Леонида не указан возраст, но такая проблема часто встречается у разработчиков с возрастом. На нее Леонид уже неоднократно намекал, в частности "как-то же ракеты запускали без всяких этих ваших умных подходов".
В частности здесь мы видим подход который имел место лет пять назад, когда вьювы уже были более менее популярны, а про миграции еще мало кто знал. Вот человек познакомился с подходом и на нем и сидит. Аналогично и с остальными вопросами. И со связями и со всем остальным.
У меня был похожий период лет десять назад, с восьмибиток, бейсиков ассемблера и т.п. слазил тяжело.Нет, вроде Си и Паскаль знал, вроде другие языки были понятны, но подходы оставались те же.
Не, ну а чё? годами работало, чего менять то рабочий подход? :)
Потом вот точно так-же лет пять назад спорил тут на хабре и на серче на примерно похожие темы. Только я отрицал пользу таких паттернов как АктивФорм и им подобных. Я тогда навелосипедил свой "шаблонизатор" на конечных автоматах с тремя управляющими конструкциями и упорствовал что 640килобайт хватит каждому...
Тут или перерастет, или нет. Нам не повлиять :)
В частности здесь мы видим подход который имел место лет пять назад, когда вьювы уже были более менее популярны, а про миграции еще мало кто знал.
Скорее — десять-пятнадцать. Пять лет назад мы уже всеми силами от представлений в БД избавлялись (наевшись, в частности, проблем с их поддержкой).
Сервер БД не в курсе какие вы там теперь продвинутые и шо там за приложения — пока рулят реляционные БД пока и останутся неизменными принципы разработки реляционных хранилищ.
Возможно если речь о Mysql то таки да, он оптимизирован под быстрые линейные выборки с одной таблицы и на джойнах работает не очень.
возьмите хранилище какого нибудь серьезного проекта типа Oracle Apex и посмотрите.
Или как я уже рекоменовал зайдите на sql.ru где тусуются спецы по БД и заявите что представления отстой.
Я про хранимки такое сказал и то выгребал эпитеты в коментах целый день.
Еще раз — если в проекте больше пары десятков таблиц без представлений не обойтись. Либо выгребать каждый раз сотни ненужных объектов в приложение связывая по десятку таблиц в ORM что очевидно глупо.
Либо выгребать каждый раз сотни ненужных объектов в приложение связывая по десятку таблиц в ORM что очевидно глупо.
Вообще-то, строго наоборот. Если я использую ad-hoc-запросы вместо представлений, я получу ровно те данные, которые мне нужны, а не те, которые в представление заложил автор.
А кто вам сказал что вьювы и хранимки это отстой? Приведите цитату. :)
Вам говорят что это инструмент с помощью которого можно отстрелить себе ногу, поэтому использование оного требует обоснования. Плохи не хранимки или вьювы, плохи программисты которые не понимают когда они уместны а когда нет, и сующие их во все дыры.
Вьювы это хороший инструмент в частности для мягкой денормализации.
Когда возникает потребность в денормализации (денормализации уже нормализованной базы), то часто можно избежать реальной денормализации использовав только вьювы. Но это специфический вопрос, обычно требующийся для оптимизации, для сложных запросов и т.п.
Трезвые, современные разработчики пишут примерно так:
Бутстрапим структуру базы, бутстрапим CRUD и прочие базовые вещи кодогенерацией и ORM, правим структуру, допиливаем неучтенные моменты ТЗ, оптимизируем узкие места структуры или кода.
Подобная дорожная карта сокращает время разработки, сложность поддержки и количество ошибок — на порядок.
При этом 90% задач (не проектов, а грубо говоря "сервисов") обходятся вообще без оптимизации, и 90% из тех где оптимизация таки нужна — решаются средствами ORM вроде добавления инструкции для жадной загрузки или указания того какие поля тянуть а какие нет. Для остальных случаев уже идут прямые запросы (и то через построитель), вьювы и хранимки.
Я понимаю что вы из мира велосипедных ERP, где чуть другая специфика. Но частое изменение структуры которое лучше делать вьюа не миграцией да еще и запросы по два экрана это
как правило признак изначально плохой архитектуры.
Уверен что если вы почитаете литературу, посмотрите классику, например как денормализацию делает тот же 1С (да и вообще почитаете откуда взялось название 1С :) ), то 90% ваших сложностей исчезнет.
Бутстрапим структуру базы, бутстрапим CRUD и прочие базовые вещи кодогенерацией и ORM
Или наоборот. Я тут недавно писал некий мелкий проектик, так там БД появилась сильно после того, как половина прикладного слоя была написана и протестирована. Заодно ее структура к этому моменту была полностью понятна и очевидна.
Кстати именно вьюхи позволяют прозрачно денормализовать БД не создавая разрабам приложений лишних сущностей.
Не вижу смысла дискусии о представлениях в контексте данной статьи…
Уверен на хабре если не было то рано или поздно будет соответствущая публикация и соответствующий холивар.
Для проектов которые архитектурю я больше подходят представления.
Можете считать меня ретроградом но я также считаю что MVC на вебе идиотизм. Ангуляры и прочие способы вытащить бизнес логику на клиента — еще больший идиотизм.
Про ORM в PHP, которые тупо портируют с явовского Hibernate или нетовского LINQ я уже говорил. Посему ваши аргументы что там как надо бустрапить не ко мне.
В своей карьере я уже повидал не один модный тренд в ИТ переживу и нынешние.
1С до 8 версии работала на DBF что не помешало ей стать успешным проектом. посему пример неудачный.
Да хоть на CSV. К чему это вообще? 1С во многом занял свою позицию в том числе и благодаря грамотной денормализации. В то время это еще не было типовым и очевидным. Посмотрите на их базовые сущности вроде "регистров" различных видов. Не утверждаю что это лучший подход, но один из удачных моментов позволяющих избегать тридцатиэтажных тяжелых запросов существующих в любой ERP. Мне пофиг что за движок у них на уровне СУБД. Да, в чем-то их абстракция в свой язык запросов поверх ORM который поверх нативного SQL — напоминает ваше злоупотребление вьювами, но я не о том…
А впрочем ладно. Всё сказано вашей последней фразой:
В своей карьере я уже повидал не один модный тренд в ИТ переживу и нынешние.
Есть выработаные практикой принципы грамотной разработки хранилищ БД.
Которые не записаны ни в какой литературе? Не смешите меня.
Опытный разработчик БД — сразу как создал таблицу, создает и вьюху на нее.
Я видел много опытных разработчиков БД, и никто из них так не делает сейчас.
Одни к одному, без джойнов. просто затем чтобы разработчик приложения сразу юзал вьюху а не влепил таблицу а через две недели перебивал код на представление.
Вставки, обновления и удаления — тоже через представления?
Потому что все равно придется.
Странно, мне вот не приходилось последние годы. А главное, вы так говорите "перебивать код на представления", как будто это сложнее, чем исправить ровно одну строчку в приложении.
Ну а не придется для какой то таблицы наличие вьюхи никак не отразится на работе — ничего не теряем.
… кроме необходимости поддерживать два объекта БД вместо одного. Очень милое дело, ага.
Они [представления] используются БД
Для чего?
Когда я проектирую БД я не думаю о там что там за приложеие.
Ну и зря.
Я не знаю какое будет приложение завтра, будет оно веб или не веб. Сегодня веб завтра мобильное после завтра REST сервис. Мне потом БД переделывать? А если разные приложения одновременно?
Вы правда никогда не слышали про трехзвенную или сервисную архитектуру?
Только генерит SQL за который хочется руки повыдергивать.
Да ладно. Вы можете написать запрос, который выполняет поставленную задачу во всех случаях гарантированно и измеримо лучше?
Но всякое бывает — мало ли какое еще приложение пользует ту же БД.
Не "мало ли". Каждый слой приложения должен быть контролируем, иначе вы просто не сможете вносить предсказуемые изменения.
Никто не проектирует БД в зависимости от того какой ORM в приложении.
Правда? Никто-никто, никогда?
Вопрос не в том что лучше, вопрос в том, что проще.
Если я могу передать модельку и не думать о том что для нее надо еще что-то тащить, потому что оно отлично достается по связям, то я так и сделаю.
Про вьювы как "бест практик" это вы отожгли да. На каждый чих по вьюву. Больше вьювов, красивых и разных… Вьювы нарушают структуру кода утягивая часть бизнеслогики в базу, что усложняет анализ кода другими разработчиками. Да и самим спустя время. Вьювы уместны тогда и только тогда когда выигрыш от них заметен. Или сокрытие чего-то, или производительность или еще что-то. Один из тех инструментов которыми как и статикой любят злоупотреблять новички.
Вопрос не в том что лучше, вопрос в том, что проще.
ORM никаким каком не может относится к «проще».
не думать о том что для нее надо еще что-то тащить, потому что оно отлично достается по связям,
как раз связи и надо тащить.
Вьювы нарушают структуру кода утягивая часть бизнеслогики в базу, что усложняет анализ кода другими разработчиками.
Это в случае использования ORM при котором нужно понимать какие где таблицы.
Но ставить телегу впереди лошади, то есть ORM впереди проектирования архитектуры хранилища — это точно не бэст практикс.
опережая ваш ответ, что в большинстве сайтов такое не надо — так и ведь сущность (Entity) как таковая там не нужная, достаточно просто реализовать Table Data Gateway
http://martinfowler.com/eaaCatalog/tableDataGateway.html
2. Не забудем, что чтение из docblock — это долгая процедура и прикрутим хранилище метаданных с кешированием. Ура, у нас уже больше одного файла.
Возьмём готовый DBAL из сотни файлов и прикрутим к нему ArrayAccess + Iterator, которые обзовём Entity. Ура, у нас получился ORM (ну, допустим) из одного класса.
да, представте себе получим. Но тут не совсем ORM, впрочем какая разница как его назвать. Назовите хоть нинзя-черепашкой.
Это решение которое позволяет резко упростить написание кода для работы с БД. Это для тех кому надо ехать а не шашечки.
Не забудем, что чтение из docblock — это долгая процедура
сколько часов занимает чтение? А то разрабы Symfony не в курсе.
прикрутим хранилище метаданных с кешированием
это забота ADODB посему просто прикручиваем одну строку в composer.json
сколько часов занимает чтение? А то разрабы Symfony не в курсе.
У doctrine/annotations как минимум есть кэш. Это к слову о чтении аннотаций.
А doctrine/orm генерирует proxy-файлы, в которых помимо нативного php-кода больше ничего нет.
И пофиг сколько будет парсится комент — десять микросекунд или двадцать — не актуально для 99.9% реальных проектов.
Поправьте если не прав.
Про кеширование думал но пока не вижу что хуже — терять микросекунды на парсинге или терять память на кешировании.
Впрочем ни то ни другое не критично если проект не высоконагруженый. А для таких проектов вопрос быстродействия решается комплексно, в том числе понаписывается и кеширование.
Для тех кому надо ехать уже давно придумали хотя бы и Doctrine ORM
> сколько часов занимает чтение? А то разрабы Symfony не в курсе.
Да почему же, в курсе, они же используют AnnotationReader от Doctrine
http://doctrine-orm.readthedocs.io/projects/doctrine-common/en/latest/reference/annotations.html
Ниже уже написали, как это работает.
> это забота ADODB посему просто прикручиваем одну строку в composer.json
Ну так прикрутите Doctrine ORM и не нужно будет ни одного файла.
Чтобы взять и использовать, не хватает поддержки сеттеров-геттеров для полей, как и в большинстве подобных библиотек. Допилить их не большая проблема, но видеть их из коробки — уже большое дело. Поддержка сеттеров и геттеров помимо типизации полей дает возможность легко допилить эмуляцию реляционных связей.
Это если закрыть глаза на:
- статические вызовы и переменные (
$class::
в эту же копилку) - отсутствие тестов (и не очень-то большую подготовленность к тестированию, честно говоря)
- малую распространенность ADODB
Ну а если бы вы положили все это в репозиторий, добавили README.md и composer.json, остальные участники помогли бы вам сделать 90% рутинной работы.
Было бы интересно посмотреть на сравнение (плюсы/минусы) вашей библиотеки и других существующих, вроде:
P.S. в чем преимущества ADODB перед, скажем, Doctrine DBAL?
Чтобы взять и использовать, не хватает поддержки сеттеров-геттеров для полей
Никто не запрещает писать гетеры сеттеры но суть решения в том чтобы ВООБЩЕ ничего не писать.
Поддержка сеттеров и геттеров помимо типизации полей дает возможность легко допилить эмуляцию реляционных связей
противоречит идее решения.
Было бы интересно посмотреть на сравнение
ну сравните. Я не знаю что делают это решения -пусть опишут их на Хабре. Но судя по коду мое проще как в реализации так и в использовании. Да и не ORM у меня и не построитель SQL.
в чем преимущества ADODB перед, скажем, Doctrine DBAL
в том что там есть нужный мне функционал, причем не требующих никаких дополнительных настроек.
Никто не запрещает писать гетеры сеттеры но суть решения в том чтобы ВООБЩЕ ничего не писать.
Не о том речь. Этого было бы достаточно, с условием поддержки остальных вызовов внутри вашего кода (из-за final сделать обертку без внедрения в ваш код невозможно):
public final function __set($name, $value)
{
if ($setter = $this->getSetter($name)) {
$setter($name, $value);
} else {
$this->fields[$name] = $value;
}
}
Но нужно принимать во внимание этот подход, когда придумываете названия другим методам. Сейчас приличную часть кода в связи с этим было бы правильным переписать.
противоречит идее решения.
Не стоит противостояние гибкости реализации записывать в ключевые принципы.
Идея-то интересная. Реализуйте вы её не "как хочу", а, почитав вдумчиво вполне здравые комментарии, получился бы не просто какой-то набросок на gist'е, а полноценная библиотека, которая бы нашла своего пользователя. А пока только минусы ловите :(
Не о том речь. Этого было бы достаточно, с условием поддержки остальных вызовов внутри вашего кода (из-за final сделать обертку без внедрения в ваш код невозможно)
ну уберите финал — это же опенсорс
Но нужно принимать во внимание этот подход, когда придумываете названия другим методам. Сейчас приличную часть кода в связи с этим было бы правильным переписать.
любой програмист глядя на код другого програмиста скажет — я бы его переписал
Реализуйте вы её не «как хочу», а, почитав вдумчиво вполне здравые комментарии, получился бы не просто какой-то набросок на gist'е,
Это не набросок на гисте а немного упрощенный вариант неоднократно используемого решения. Код не идеален но это решение упрощает мне работу и меня это устраивает.
И таки да — речь об идее. Разве хабр не для этого? Готовые решения это в раздел Я пиарюсь.
А пока только минусы ловите
Ну понятно — есть люди которым не нравится если не по ихнему.
Я например не ставлю минусы за то что человек фанат ORM.
ну уберите финал — это же опенсорс
![image](https://habrastorage.org/files/aec/802/d77/aec802d77d5f42c88df248944982a5fc.png)
Я описал идею. Для того и Хабр — для обмена идеями и опытом.
Мог описать на «пальцах» и никто бы не придирался к коду. Но это другая крайность.
Просто считайте что код это драфт который я набросал на салфетке в кафе для демонстрации идеи. И забудьте про финалы, моки и прочие несуществующие в контексте статьи проблемы.
Ну а если бы вы положили все это в репозиторий, добавили README.md и composer.json, остальные участники помогли бы вам сделать 90% рутинной работы.
не помогли бы — у каждого свой самый клевый велосипед.
Вчитавшись в документацию и коды, я не нашел никаких преимуществ ADODB(php) перед Doctrine/DBAL, кроме большего количества поддерживаемых БД (7 vs 25 с натяжкой).
Напротив: комбайн все-в-одном (это может быть и плюсом, и минусом), древняя архитектура с множеством legacy-кода, отсутствие современных стандартов и тестов, глобальные функции, отсутствие неймспейсов и т.д.
По-моему, там не совсем entity по ссылке. Ну и именовать методы надо получше, а то видим findCnt
, понимаем, что описочка вышла, букву пропустили. Только вот сходу не ясно u
пропустили или o
...
Ну и именовать методы надо получше, а то видим findCnt, понимаем, что описочка вышла, букву пропустили. Только вот сходу не ясно u пропустили или o...
Решение используется в куче моих проектов как то стремно что то переименовывать.
Кому надо возмет и переименует как захочет.
Если уж хочется назвать как-то по своему, не вижу препятствий. Но, я бы всё-таки не переиспользовал уже существующие устоявшиеся термины, которые означают несколько иные решения. Путает.
Я уже делал эту ошибку и могу сказать, что порождаемые ей непонятки и дискуссии кушают кучу времени. Лучше превентивно ошибки избежать...
Имеется класс соответсвующий бизнес-сущности, сущности представленой строкой в БД.
Есть какие-то тонкости? А в каких решениях именуемых Entity их нет?
с точки зрения архитектуры Active Record.
С точки зрения бизнес-логики — Entity. Кто сказал что AR не может быть Entity?
И с каких пор Фаулер стал истиной в последней инстанции.
Просто то, что у вас — это классическая Active Record, сущность, которая знает как достать себя из БД и как себя сохранить
Не знаю зачем вы не стали использовать довольно устоявшийся термин
С точки зрения проецирования бизнес-логики на реляционную БД, далеко не каждая запись в базе данных является бизнес-сущностью
КО подтверждает.
Строка табличной части счёта не имеет, как правило, никакой бизнес-идентичности, если даже в базе данных ей создан суррогатный первичный ключ типа id
Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
Хотя часто бывают и не связанные с физическими сущностями записи — например бухгалтерские счета или проводки. Впрочем это не мешает использовать тот же механизм работы с БД. К примеру у меня в фреймворке компонент таблица сам управляет выборками из бд для сортировки и пагинации через универсальный интерфейс DataSource- ему все равно как оно там называется.
Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
Нет. В заказе (как и счете на его выполнение) стоят не товары, а строки заказа/счета — это не сущность, а объект-значение, полностью определенный триадой "заказ-товар-количество" (и этот объект значение является частью агрегата "заказ", не имеющей никакого смысла за пределами этого агрегата).
Я, несомненно, видел системы, где было смоделировано иначе — заказ мог иметь несколько строк с одним и тем же товаром — и там, несомненно, такая строка была отдельной сущностью, но это либо было неаккуратностью, и всех такое поведение смущало, либо, напротив, специально заложенным поведением — к сожалению, для ситуаций, когда домен используют не по прямому назначению.
Давайте возьмем для примера не «заказ» а «фитосанитарный сертификат».
Я немного упрощу предметную часть (да и не помню я ее уже в деталях).
Заказ является производным от сертификата (ну точнее сертификат производный от заказа, но не суть.
Значит сертификат выдается на партию товара.
Партия может быть как одного наиметования, так и нескольких.
Ехать как на одном транспортном средстве, так и на разных.
Может иметь неограниченное количество отправителей и неограниченное количество получателей.
10 отправителей и 10 получателей это редкий частный случай который встречается в довольно узком контексте большегрузных теплоходов (60тыс тонн пшеницы идет одним пароходом из Украины в Китай), но законом и международными соглашениями сценарий предусмотрен.
Сделать у сертификата фиксированные отправители и получатели это чисто предметная ошибка. Идем к части описания самой партии.
Партия описывается табличной частью.
У записей табличной части есть поля описывающие груз. Это не одно поле а несколько. Например «Номенклатура» == Пшеница (реально связь по ИД), «Свойство» == Озимая (текстовое, важно для анализов).
Есть количественные показатели, которые бывают разные — колво мест, единица измерения мест (ящики, палеты...) колво груза, единица измерения колва (тонны, кубометры, штуки) и т.п. Единицы измерения естественно связями на справочник. Тут более менее очевидно.
Далее описание «носителя» или формально «транспортного средства» к котрому относится машина, контейнер, прицеп, трюм/параход и т.п. Тут идет тип «носителя» (связью), номер т/с (текстом), если судно, то связь на «рейс» в котором указано ИД теплохода и дата прихода в порт, чтобы их отличать, название трюма можно в принципе совместить с полем «номер т/с».
Я тут опустил 80% связей и полей, чтобы не усложнять. И да, мы тут уже упростили техпроцесс условившись, что одновременно номер машины и номер контейнера мы не указываем, хотя такие вещи были на откуп локальных правил, и это еще та задача внедренца согласовать это с центром.
Теперь вопрос номер раз — делаем ли мы тут фильтрацию на уникальность, и если да, то уникальность чего? Составных ключей? В одном т/с может быть несколько видов груза, один вид груза может быть в разных т/с и даже в разных видах (убил ты того кто 10 вагонов пшеницы и 5 контейнеров пшеницы в совокупности считает одной партией, но что поделаешь — жизнь). Тут или сложная логика на уникальность, или излишняя нормализация с введением промежуточных сущностей объединяющих некоторые поля.
Чрезмерная оптимизация структуры неизбежно нарвется на сайдэффекты.
Большегрузы я уже упоминал. Там к примеру есть нюансы типа того что загружать трюмы нужно одновременно, и разделить их по отправителю или получателю (чего требовали теоретики из министерства) немного нереалистично.
(Для понимания масштаба — представьте себе грузовик с 20тонн зерна. Добавьте ему еще прицеп на 20 тонн. Теперь представьте вереницу из 1500 таких машин (по факту будет 2000 машин ибо часть всё равно не будет сцепками). Это всё грузится в один пароход, и уходит под одним сертификатом.
Еще из сайд-эффектов: я уже пару лет не работаю в этой сфере. Насколько я знаю опять поменялось министерство, главк, и местные структуры объединили и разъединили, так что я даже и не знаю как называется контора где работают бывшие коллеги. Да и офис у них в процессе переезда. Но я уверен, что до сих пор существует отчет, который уже неизвестная мне организация каждый квартал направляет в неизвестное мне министерство. В котором тонны упорно складываются с кубометрами. Потому что составители формы не учли, что существует номенклатура (древесина) которая может считаться как в тоннах так и в кубометрах (обычно единица одна на номенклатуру).
Еще из сайдэффектов — при отправке образцов через DHL экспедиторы DHL везут в лабораторию больше зерна, чем отправляется клиенту в виде образцов (селекционеры самолетом через DHL шлют по 2кг образов, а у лаборатории минимальная партия на отбор 3кг, ну или как-то так). И не сайдэффект, а просто экзотика — курьер DHL возит моим коллегам флешку для нашего флоппинета. Это было оптимальным решением потому что интернет в аэропорту монополистами загнан в цену 100$ за 500килобит, и других операторов не пускают, а безпроводной глушится помехами. Ну и да, грузы в основном DHL, так что мне не сложно было с ними договориться)
Так вот, два вопроса — стоит ли накладывать ограничения на записи в табличной части документа, за которое вы выступали в своем комментарии? Я считаю что не стоит, кроме как когда оно именно надо (т.е. по умолчанию не делать, но если техпроцесс клиента может вызвать ошибки, то либо склеивать дубли, либо запрещать неуники).
Вопрос второй — создавать ли отдельные модельки и считать ли их полноценными бизнессущностями для таких служебных подзаписей? Тут я в размышлениях…
стоит ли накладывать ограничения на записи в табличной части документа, за которое вы выступали в своем комментарии?
Я вам честно скажу: предметная область явно сложнее, чем то, что я могу понять из короткого описания, не рисуя модель, а на рисование модели время, все-таки, жалко, да и уточняющих вопросов придется задать слишком долго. Пока что у меня есть ощущение, что "партия" — это отдельная сущность, имеющая свой жизненный цикл, а, значит, надо ее идентифицировать отдельно (и в этом случае уникальность уже не обязательна).
Вопрос второй — создавать ли отдельные модельки и считать ли их полноценными бизнессущностями для таких служебных подзаписей?
Модели создавать надо всегда (у value object тоже есть модель). А вот считать ли полноценными сущностями — это тот же вопрос, что и выше.
записи в таблице — однозначно бизнес сущности. То что к ним приатачены разные атрибуты (как скалярные так возможно и ссылки на другие сущности) сути дела не меняет.
Если товар может повторятся — значит составной ключ — какие проблемы.
создавать ли отдельные модельки
в этом и удобство реализации классов с динамическими параметрами — один и тот же класс можно использовать для разных таблиц. А вот в .NET для этого используются анонимные классы — ну подход тот же не плодить (ыв смсле не обьявлять явно) стопицот моделей на каждый чих.
А вот в .NET для этого используются анонимные классы — ну подход тот же не плодить (ыв смсле не обьявлять явно) стопицот моделей на каждый чих.
Это в каких случаях?
(я уж молчу про то, что в .net есть динамические типы)
один и тот же класс можно использовать для разных таблиц
Ходил таким путем. Сейчас у меня в основной ветке своего движка именно такой подход, где структура данных, различные обработчики для различных событий и полей прописываются в едином конфиге (или в распределенном, не суть), и обрабатываются одним классом, кроме небольшого количества моделей у которых специфика не укладывается в конфиг и требует отдельного класса.
Но на практике столкнулся с существенным набором проблем, и на следующий большой рефакторинг архитектуры (изменение средней цифры АПИ) запланирован уход от такого подхода.
Буду пробовать для типовых моделей что-то вроде
eval("class $className extend $parentName {}");
Ожидаю что издержки такого подхода будут меньше выигрыша от восстановления штатного дерева наследований, штатных интструментов определения родственных связей, типхинтинга и т.п.
Хотя если говорить об основной мысли, что бОльшую часть бизнеслогики можно перекинуть в конфиг, то это да, этот подход в целом себя оправдывает на небольших и средних проектах. До больших пока не добирался.
Хотя если говорить об основной мысли, что бОльшую часть бизнеслогики можно перекинуть в конфиг,
О, это тема для еще одного длинного и прекрасного холивара.
Из не совсем канонических подходов у меня валидация живет сразу в модели, но это удобно и часто встречается. Также у меня на атрибутах модели идут правила — правила это не только валидация, но и beforSave и afterLoad (для каждого правила, применяемого к тем атрибутам для которых они указаны. У правила могут быть как для всех событий методы так и только для одного). Естественно сценарии.
Ну и в языковом модуле однозначная связь с именами полей идет, т.е. у языковых констант есть «неймспейсы» связанные с разными классами, в том числе и с моделями. Что тоже по умолчанию для небольших вещей в конфиге идет.
Далее — у каждой модели есть свой построитель запросов, в моем случае с учетом некоторого сахара я его называю box. Это что-то впроде finder`a. Он в большинстве случаев как раз одним классом делается, его редко когда переопределяют (хотя да, ему можно указать какой конекшн для базы и тп.)
В нем обычно переопределяем только дефолтные лимиты, и направления сортировки.
Ну и связи конечно же, по аналогии с yii1.
Большинство вопросов реально решается написанием специфического правила, и связями.
С ростом проекта конечно у половины моделек появляются свои методы, и одним crud не отделаться, но на начальном этапе можно и без кода жить.
Из плюсов — список разрешенных для текущего сценария полей всегда можно взять в конфиге, не играясь с зависимостями классов, что упрощает построители (и не требует дибильных запросов структуры базы каждый раз, а потом кеширования этих запросов как делают довольно часто)). Можно вешать снаружи всякие генераторы миграций, активрекорды и т.п. не засовывая их в мясо модели.
Очень удобно когда есть структура которую можно увидеть в частности для RBAC. В частности у меня в yii1 был приятный модуль который создавал дефолтные правила для CRUD и отдельное правило для owner если у модельки есть связь на юзера. (Например право редактировать комментарии и право редактировать СВОИ комментарии). Закрывает много рутинной работы. Ну и конечно же очевидный плюс с простотой бутстрапинга — конфиги поправил, и уже работоспособные модельки…
Из минусом — некоторый отход от кошерного SOLID с излишней централизацией ответственностей.
Ну так давайте похоливарим, чего бы и нет? Должна быть какая-то польза от этого топика?
Холивар тут уже неделю идет — пока никакой пользы нет. Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
Из не совсем канонических подходов у меня валидация живет сразу в модели, но это удобно и часто встречается.
Удобно и разумно — дна из причин выкинуть «канонические» подходы, придуманные людьми, которые уже давно не программируют а пишут книги о программировании — так просто денег больше платят.
но и beforSave и afterLoadу меня такие же методы, но зачем какие то правила на атрибутах — просто пропишите все правила здесь в одном месте.
все что ниже цитировать не стал — нечто заумное и запутаное (я так понимаю «канонический» поход). SAP хотите переплюнуть по количеству гемороя в проекте?
Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
У вас есть маппинг связей с ленивой/жадной загрузкой?
Впрочсем для меня нет — раньше думал в ORM есть что то чего нет у меня — убедился что ничего.
Вы просто написали свою библиотеку ORM. Примитивную, правда (маппинг полей на базу 1:1, связей нет и т. д.), но ORM.
Так я учился писать сложные запросы в конце 90х. Создавал их билдером в Access97 потом смотрел что получилось и переносил в код.
Вообще, декларативный подход в работе с хранилищами данных красиво смотрится на бумаге но очень хреново на практике.
Хотя делает то что и ORm и даже больше, но не требует никакого маппинга
Правильно написанный ORM тоже не требует маппинга
длиннючих строк со стрелками чтобы сформировать в конечном итоге sql запрос.
Ну да, лучше написать SQL-запрос руками, и руками же следить, чтобы в нем не было ошибок. Намного надежнее.
Холивар тут уже неделю идет — пока никакой пользы нет.
С вами холивар и не может быть конструктивным, вы с высоты своего опыта не слышите оппонентов.
Удобно и разумно — одна из причин выкинуть «канонические» подходы
Сначала человек учится как правильно, потом человек начинает понимать почему именно так правильно, потом человек может осознанно нарушать правила осознавая к чему это приводит и какова цена. Потом человек учится писать правила. Так устроена наша жизнь. Только такой подход работает в массовом процессе. Когда нужен гарантированный результат. И не важно пишешь ты код, печешь пироги или изготовляешь горшки, строишь дороги или разводишь дрозофилл — всегда одно и тоже. Опускание одного из этапов ВСЕГДА ведет к одному и тому-же результату…
у меня такие же методы, но зачем какие то правила на атрибутах — просто пропишите все правила здесь в одном месте.
Потому что можно один раз написать правило TimestampDate и просто указывать его имя у нужных полей. А он уже будет создавать объект инкапсулирующий классы языка отвечающие за время, или просто конвертировать (например по переданным ему в настройках той же записи или в настройках соответствующей глобальной компоненты) строковое представление даты в таймштамп при сохранении и разворачивании строки из таймштампа при чтении по шаблону из того же конфига, уже с настройками локали, таймзоной и т.п.
Засовывать это в события — слишком «мокро».
Из не совсем канонических подходов у меня валидация живет сразу в модели
Что значит "сразу в модели"? Вы это считаете конфигурацией или программированием?
Также у меня на атрибутах модели идут правила
Аналогично, это конфигурация или нет?
Он [построитель запросов] в большинстве случаев как раз одним классом делается
Я вижу слово "класс", из которого делаю вывод, что вы тоже о программном коде говорите.
Так что я пока не очень понял, где же у вас бизнес-логика в конфигурации.
На самом деле, большая часть холивара упирается в то, сколько инструментов статического контроля есть для языка, на котором вы пишете, и какие у него возможности рефлексии.
Скажем, проблема
список разрешенных для текущего сценария полей всегда можно взять в конфиге, не играясь с зависимостями классов, что упрощает построители (и не требует дибильных запросов структуры базы каждый раз, а потом кеширования этих запросов как делают довольно часто))
Мне вообще не понятна, потому что в .net-мире ORM лишний раз в структуру БД не лазит — ибо зачем, схему можно построить по метаданным классов.
… а подход
Можно вешать снаружи всякие генераторы миграций, активрекорды и т.п. не засовывая их в мясо модели.
… давно является подходом по-умолчанию, потому что горький опыт показывает, что модель должна иметь минимум зависимостей от DAL. Но при этом все это пишется (можно писать) в коде, а не в конфигурации.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования. Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней. Или в конфиге руками или в админке. Держать структуру в одном месте это дает определенный набор гибкости, как в организационном плане, так и в бутстраппинге/прототипировании.
Если задача простая, то ее может сделать опытный гумманитарий с аналитическим складом ума (да, такие существуют) и опытом хоть какого-то управления проектами (линейный руководитель, мелкий предприниматель и т.п.) Они сравнительно дешевы, и что важнее — лучше общаются с конечными пользователями. Не требуется лишняя прослойка переводчика с языка бизнеса на язык кодера.
Если задача нетривиальная, то с ней нужно как-то поиграться, поредактировать, увидеть вживую…
Да, можно прототипировать, рисовать схемы и т.п. Это хороший рабочий подход. Но иногда хочется написать 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, неканоничность у меня в основном в том, что обычно они размазаны по разным классам, а у меня лежат в одном месте.
Можно писать в коде, да. Но это лишняя связность. Нужно делать некий интерфейс доступа к информации о структуре в модели.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования.
у вас телега впереди лощади
Сначала определяются связи (на уровне ТЗ) потом програмируются.
Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней.
да, типичная фантастика — аналитик потягает что то мышкой по экрану, прога прочитает связи и будет с ними работать.
Забудте. Во всяком случае пока у вас нет пару лимонов баксов и штата програмеров в пару сотен.
В принципе примерно подобные «конфиги» встречаются во многих 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', @OldsourceLocationName, '', '', @SubTotalEstimatedQuantity, @SubTotalActualQuantity, @SubTotalVariance) END ";
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 * FROM #ReturnOrderItems ";
и это после денормализации и десятка представлений, которые тут почему то считаются моветоном.
как вы себе представляете это с ORM?
А для заказчика это просто репорт Volume Returned.
Рад что предоставил вам место высказать свое мнение и описать свои проблемы не получивши минусов за один только eval (уже тут никто не ходит)
Удачи.
Не вникая в код (Вру, начал рефакторить) скажу что за такое отношение к оформлению кода, и т.п. человека нужно лишить прав подходить к компьютеру. Даже к «ворду».
Есть и другие подходы. Но в общем случае длинный и тяжелый запрос статистики это или денормализация, или просто декомпозиция.
Весь смысл тут в том, что нам кинули существенный кусок кода без форматирования, просто тупым куском запроса в адской обертке и внедрении какой-то логики генерации кода.
Это чистая "тетрадка Чуня". И для таких людей есть отдельный котел в аду.
Что забавно — этот человек говорит мне о «сложности поддержки и понимания кода» евал() из одной строчки…
как вы себе представляете это с 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();
А дальше просто программным кодом без всякого SQL.
$splittedDbResult = [];
foreach ($dbResult as $row) {
$splittedDbResult[$row['ItemName']][$row['sourceLocationName']][] = $row;
}
foreach ($splittedDbResult as $itemName => $locationData) {
$TotalEstimatedQuantity = 0;
$TotalActualQuantity = 0;
$TotalVariance = 0;
foreach ($locationData as $sourceLocationName => $locationRows) {
$SubTotalEstimatedQuantity = 0;
$SubTotalActualQuantity = 0;
$SubTotalVariance = 0;
foreach ($locationRows as $row) {
$result[] = $row;
$SubTotalEstimatedQuantity += $row['EstimatedQuantity'];
$SubTotalActualQuantity += $row['ActualQuantity'];
$SubTotalVariance += $row['Variance'];
}
$data = [
null, 'Sub Total', $sourceLocationName, '', '',
$SubTotalEstimatedQuantity, $SubTotalActualQuantity, $SubTotalVariance,
];
$result[] = $data;
$TotalEstimatedQuantity += $SubTotalEstimatedQuantity;
$TotalActualQuantity += $SubTotalActualQuantity;
$TotalVariance += $SubTotalVariance;
}
$data = [
null, 'Total', '', '', $itemName,
$TotalEstimatedQuantity, $TotalActualQuantity, $TotalVariance,
];
$result[] = $data;
}
как вы себе представляете это с ORM?
Как ни странно, я легко себе это представляю с ORM, причем тремя разными способами — из которых как минимум один будет существеннее читабельнее.
Другое дело, что с хорошей вероятностью это, как и многую другую аналитику, просто не надо делать на OLTP-базе, а собрать в промежуточное хранилище, в которое уже лазить удобным способом (в частности, через ORM, но поверх аналитической модели).
Можно писать в коде, да. Но это лишняя связность.
Связность чего с чем?
Нужно делать некий интерфейс доступа к информации о структуре в модели. Или через рефлексию,
Я и сказал — выбор частично зависит от того, насколько в вашей платформе мощные механизмы рефлексии.
Вообще основная цель тут вынести задачи не требующие программирования на уровень не требующий программирования. Аналитик/менеджер может набутстрапить структуру и даже поиграться с ней. Или в конфиге руками или в админке.
Been there, done that. Это очень быстро становится очень дорого в поддержке — потому что они создают структуру, которая не прошла анализ и обдумывание. К сожалению, это реальный опыт, пять лет с такой платформой имел дело.
приведу пример кусочка конфига моделей
Это все хорошо ровно до того момента, пока вы не хотите к этому обратиться из кода. После этого ваше "одно место" превращается в N: конфиг + все те места, откуда вы обращаетесь — и вам надо поддерживать согласованность.
А теперь сравните это со статически типизованным языком, в котором вы объявили модель, как класс со свойствами. Теперь каждый раз, когда вы откуда-то обращаетесь к этим свойствам из кода, компилятор проверяет правильность этого обращения — вы не можете ошибиться в имени, вы не можете запихнуть числовое поле в редактор дат. У вас есть средства рефакторинга — переименовали поле, переменовались все обращения. И при этом вы все еще можете использовать автоматические построители интерфейса — они просто анализируют ваш класс и берут его метаданные для формирования формы/списка/чего угодно.
Been there, done that. Это очень быстро становится очень дорого в поддержке — потому что они создают структуру, которая не прошла анализ и обдумывание. К сожалению, это реальный опыт, пять лет с такой платформой имел дело.
Здесь да, надо быть осторожным.
И «после сборки обработать напильником». Но львиную долю итераций по уточнению ТЗ можно делать без разработчиков. Чтобы прояснить мысли клиента нужно ему их визуализировать. Мелом на доске или дать пощупать MVP — просто разные подходы. Так пусть MVP делают без меня…
И при этом вы все еще можете использовать автоматические построители интерфейса — они просто анализируют ваш класс и берут его метаданные для формирования формы/списка/чего угодно.
Да, нативная работа любого IDE если делать максимально строго это существенный плюс. Хотя на практике половина современных фреймворков нормально не подсвечиваются без специальных для них плагинов. Но благодарю что обратили мое внимание на точку зрения IDE. Поищу или какую-то документацию по этим самым плагинам, или что-то подумаю с генерацией метакомментариев…
И «после сборки обработать напильником». Но львиную долю итераций по уточнению ТЗ можно делать без разработчиков. Чтобы прояснить мысли клиента нужно ему их визуализировать. Мелом на доске или дать пощупать MVP — просто разные подходы. Так пусть MVP делают без меня…
А вот здесь и ловушка: многие заказчики, получив MVP, говорят: а запускайте это в продакшн. И вот тут начинается...
… некоторые злые и умные люди рекомендуют делать MVP в PowerPoint или аналогах.
сделать прототип на рельсах и sqlite
Чем обусловлен выбор? Количеством гемов?
Эта идея озвучивалась у того же МакКоннела. Основная проблема в том, что надо иметь специалиста в таком профиле.
Но на практике столкнулся с существенным набором проблем,
потому что вы либо кинулись в другую крайность либо выбрали неверную имплементацию. Не должно быть никакого конфигурирования вообще.
Буду пробовать для типовых моделей что-то вроде
eval()? В это серьезно?
восстановления штатного дерева наследований, штатных инструментов определения родственных связей
штатные инструменты надо полагать некая разновидность ORM. Ну так я и написал о неверном выборе.
бОльшую часть бизнеслогики можно перекинуть в конфиг
Не должно быть вообще никакой бизнес-логики ни в каких конфигах — десятки XML файлов это подход десятилетней давности и десятки дышащих на ладан проектов типа Дебет-Плюс.
До больших пока не добирался.
при таком подходе никогда не доберетесь.
Когда создаешь архитектуру — нужно руководствоваться здравым смыслом а не какими то теоретическими положняками — должны быть конфиги, должны быть модели, должны быть прописаны некие связи, бизнес-сущности в таблицах это не бизнес-сущности а некие новые абстракции (хотя здравомыслящий человек смотрит на счет или сертификат и видит там именно товары) и т.д.
.
.
штатные инструменты надо полагать некая разновидность ORM. Ну так я и написал о неверном выборе.
Нет, штатные средства языка. Представляете? В пхп есть вполне сносный функционал ООП, представляете?))
при таком подходе никогда не доберетесь.
Смешно это слышать от вас :)
eval()? Вы это серьезно?
Серьезно, да. А что есть какие-то проблемы? Долго думал над этим, с коллегами советовался, но не нашел существенных недостатков. Есть один недостаток, связанный с тем что опкоды не кешируются, но не силен в низкоуровневой структуре пхп. По идее грамотная реализация наследования должна тут обойтись без заметных задержек. Но даже если и не так, то ожидаю не очень большой овер по производительности в сравнении с восстановлением штатного стека типхинтинга и наследования, что таки больше ограничивает выход на большие проекты чем скорость компиляции опкода.
В пхп есть вполне сносный функционал ООП,
тогда непонятно что вы там намудрили — никогда не было никаких проблем изза ООП.
Смешно это слышать от вас
тем не менее это вы не можете добратся к большим проектам — ООП мешает?
Не надо ёрничать — судя по вашим постам, у вас вообще смутное представление о проектировании.
Серьезно, да. А что есть какие-то проблемы?
неудобен для чтения кода и отладки. Опасен — выполнит любую иньекцию
Непроизводителен — по сути вызывается еще один экземпляр компилятора со своим контекстом.
Если ошибка, то скрипт падает. и это никак не отловить
дальше ищите в гугле.
И эти люди рассказывают как надо писать код…
восстановлением штатного стека типхинтинга и наследования, что таки больше ограничивает выход на большие проекты чем скорость компиляции опкода.
Не знаю что сия конструкция значит — но эти проблемы исключительно и ваших архитектурных решений.
первый раз вижу чтобы eval как то решал некие проблема с наследованием.
Простите но вам на больших проектах в ближайшие лет пять делать нечего.
неудобен для чтения кода и отладки.
Серьезно? Вам не понятен написанный мной код?)
Опасен — выполнит любую иньекцию
Мне сложно себе представить инъекцию добравшуюся до автозагрузчика классов, да еще и не первого. Нет это возможно, но… у если человек ТАК пишет код, то у него будут проблемы посерьезнее чем евал в автозагрузчике.
Непроизводителен — по сути вызывается еще один экземпляр компилятора со своим контекстом.
Вот это как раз и смущает, но опять же. Не на ваше же мнение в этом вопросе ориентироваться.
Коллеги отвечавшие в свое время за хайлоад в одном поисковике сказали чтобы я не параноил, что издержки незначительны. Это для меня не гарантия, но мнение которогму доверяю…
Если ошибка, то скрипт падает. и это никак не отловить
Если придумаете как может упасть такой код — дам шоколадку :)
Имя метода валидируется контекстом, имя родителя валидируется при создании конфига. Но даже если родитель будет битым — тут или внятное исключение при загрузке битого родителя, или… Черт, ну ок, убедили я протестирую поведение пхп на проблемном родителе. Не хочу его валидировать, это дорогая операция. Но не думаю что могут быть проблемы… Но да, начну с этого когда дойду до задачи. Я в принципе собирался это делать но не первым делом.
дальше ищите в гугле.
Искал, представляете :) Идея уже полгода в голове, куча обсуждений, обдумываний. Не нашел ничего.
Тестовый кейс:
<?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
Что-то такое помню, что вроде как ошибки евалов, даже фатальные можно перехватывать, но точно не помню. Ну в смысле без обработчиков и т.п., на месте. Но оно и без того нормально выглядит, так что остается открытым только вопрос производительности.
полностью определенный триадой "заказ-товар-количество"
Я неправ, конечно же. Определен этот объект парой "заказ-товар", а количество — это как раз полезная нагрузка.
Вообще то в большинстве случаев — табличная часть — это тоже сущности — например товары.
В большинстве случаев табличная часть документов типа заказов, счетов, ордеров и т. п. не содержит товаров, а содержит ссылки на товары и дополнительную информацию типа количества и цены. По сути это реализация связи многие-ко-многим между заказами и товарами через джойн-таблицу с дополнительными полями. Эта связь между сущностями сама сущностью не является, вне контекста заказа она не имеет никакого смысла для бизнеса.
У вас в примерах очень сильно плавает стиль кода в плане расстановки пробелов, это сразу в глаза бросается.
![image](https://habrastorage.org/files/27b/ab4/40f/27bab440f9ac42dfb2b299e8eaf88a2f.png)
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-возможности?
AR который там меня не устроил. Не помню почему. Может быть и можно было отнаследоваться от него. Но в случае непоняток следую принципу что агрегация предпочтительнее наследования.
И не набросились, а предоставили аргументированную критику.
Велосипедостроение это полезно, сам таким занимаюсь, но выносить это на публику и предлагать как единственно верное решение («связи не нужны», «если нужны — делай create view ...», «ORM в топку» @автор) — нехорошо.
Всё когда то начиналось с велосипедов.
И это не единственное верное решение а наиболее оптимальное.
Сочетает простоту реализации с мощью нативного SQL.
Впрочем ничего не мешает к моему решению впаралель прикрутить ORM — специально для тех кто не умеет написать строку кода пока IDE не подскажет ему функцию.
И это не единственное верное решение а наиболее оптимальное.
По какому критерию?
а другие подходвы вы курили? Чем вам DAO не угодил? По вашей логике намного более оптимальное решение — систему на DAO удобно мокать, мы работаем исключительно с SQL, причем оно полностью отделено от бизнес логики… Как-то выглядит намного более гибко. В чем тогда профит?
Вот ради интереса прикрутил idiorm по аналогии с Pixie к своему решению.
И в чем преимущество
Customer::getORM()->select('*')->where_equal('name','ЧП Петров')->find_many()
перед
Customer::find("name='ЧП Петров'");
кроме в три раза большей длины строки.По поводу моков — опять же непонятно что именно я ими бы тестил без БД.
По поводу моков — опять же непонятно что именно я ими бы тестил без БД.
Внезапно, бизнес-логику.
— А если надо «LIKE», а не "="?
— «select(*)» необязательно вызывать
— Вы сравниваете Query Builder и Active Record. Сравнивайте тогда уж с Active Record от того же автора. Там кстати связи есть, что тоже преимущество.
Руками каждый раз экранируете?
это просто упрощенный пример.
в adodb есть специальная функция экранирования для всех типов БД
А если надо «LIKE», а не "="?
это просто sql че надо то и пишите
«select(*)» необязательно вызывать
монстрообразность выражения не намного уменьшается
Сравнивайте тогда уж с Active Record от того же автора.
Оно строится поверх ORM.
Про громоздкость, прожорливость и бессмысленность ORM в PHP я уже писал.
Мое решение рассчитано на тех кто имеет понятие как написать sql запрос.
могу привести аналогию — какие бы не городили визуальные редакторы — верстать веб страницы руками и проще и быстрее и главное эффективнее.
В этом и суть моего решения — сочетание мощи sql запросов с максимально простым способом работы с Entity
это просто упрощенный пример.
в adodb есть специальная функция экранирования для всех типов БД
И как же выглядит полный пример, с учетом экранирования?
в adodb есть специальная функция экранирования для всех типов БД
Какая разница, стандартная она или из adodb. Вызывать ее все равно надо каждый раз вручную.
это просто sql че надо то и пишите
Вы не поняли вопроса, он связан с первым. Функции типа where_equal() и where_like() разделяют имя поля и значение, чтобы одно заключить в обратные кавычки, а второе в плейсхолдер запроса. Сколько нужно написать кода при использовании вашей ORM и специальной функции экранирования adodb для генерации запроса с LIKE без SQL-инъекций? И во сколько раз он будет больше приведенного вами для idiorm?
Оно строится поверх ORM
А ваше требует ADOdb, в которой 218 файлов размером под 2 мегабайта. А тут рабочий файл всего 1, размером 90 кб.
Вызывать ее все равно надо каждый раз вручную.
если в выражении то да. А если это свойство Entity — тогда adodb самспециальной функции экранирования adodb для
генерации запросаа проекранирует если поле в Бд — строккковое
это всего лишь DB::qstr()
А ваше требует ADOdb, в которой 218 файлов размером под 2 мегабайта.
половина этого кода — драйвера для различных БД
остальное тоже используется не полностью.
… В реальном фреймворке есть отдельный класс синглтон 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 строке, прямое сравнение с переданным паролем).
Причем при регистрации у вас пароль шифруется, но при логине можно указать не сам пароль, а его хеш. Утекла база с хешами, и даже ломать не надо.
там вообще юзер и пароль пока плейсхолдерами стоят.
Пример придирок к мелочам вместо возражений по существу.
, но при логине можно указать не сам пароль, а его хеш.
потому что оставлена возможность на период разработки вбивать пароль руками в БД. Поэтому проверяется как на хешированую версию так и один к одному.
Утекла база с хешами
КО утверждает что если кто то добрался напрямую к базе то остальное уже не имеет значения.
Знаете, читаю я этот комментарий… и мне интересно о какой такой эффективности мы вообще говорим?
Все мы любим свои велосипеды потому что они нам лучше всего понятны.
Поэтому лично для ТС этот капитанский велосипед является эффективным.
А так то если это был вопрос, а не риторическое высказывание, то он уже задавался в этом тренде, и естественно был без ответа :)
Entity “фреймворк” для PHP из одного класса