Как стать автором
Обновить

Комментарии 45

Интересное решение. Выложите исходный код фреймворка, если это возможно, на github.
Я собираюсь его немного переписать, исправив некоторые моменты, и после этого он там обязательно появится.
Да, идею я именно оттуда и взял (см. в начале статьи)
redbeanphp.com/ полноценная orm построенная на этом принципе
Если я вас правильно понял, под принципом вы имеете в виду CRUD (Create, Read, Update, Delete), он используется в подавляющем большинстве ORM и фреймворков. Если я неправильно понял, то поясните, что именно за принцип вы имели в виду.
текучий интерфейс
Посмотрел, бегло ознакомился, текучего интерфейса не нашел. Может быть просто в примерах нет, но на всякий случай уточню: текучий интерфейс — это когда метод объекта, проделав какие-то манипуляции, возвращает $this, позволяя строить длинные цепочки из действий, например:
$string->toLowerCase()->upperCaseFirst()->replace('_', ' ')->print();
Спасибо, что разъяснили. Я думал под текучестю имеется ввиду наличие контейнера и отсутсвие строгих полей объекта
их еще цепными методами называют (chained methods)
Поздравляю, вы изобрели active record :)
Верно, много где это используется. Например в Yii.
Текучий интерфейс это конечно же хорошо, но как тогда с валидацией данных?
Предположим используем конструкцию
$instance->set('name', 'Foo')->setDescription('Bar')->setBasePrice(32.95);

Представим, что на каждое из свойств есть особенный тип ограничения basePrice > 10, description > 10 символов, а name только на латинице.
Как модель проинформирует об ошибке? Бросит Exception, а потом мы в switch будем перебирать тип исключения чтобы отобразить пользователю? То же самое с create, save, remove, load Как быть в таком случае?

Второй аспект: выходит так, что базовая модель перенасыщена «магией» (аж 3 варианта ввода одних и тех же данных) + еще и регулярка на каждый _call, таким образом все остальные (дочерние) модели будут подобного толка. Магия очень дорога, по крайней мере пока.
Возможно стоит оставить 1 вариант для унификации ввода и облегчения жизни cpu?
Ведь подобная архитектура приведет к серьезному проседанию производительности. Может выйти так, что наш маленький фреймворк работает не на много быстрее большого, но оптимизированного в этом плане, а то и хуже.

Стоит ли обилие вариантов ввода 3х кратной потери производительности?
Спасибо за развернутый комментарий.

Да, определенные проблемы с производительностью могут иметь место. По большому счету, для этого есть прямой set() без регулярок. Если нужна дополнительная логика, то можно переопределить тот же метод setDescription() в самой модели, вызывая в конце set('description', $value) или parent::setDescription(). Но в целом, да, вы правы, здесь производительность жертвуется в угоду удобочитабельности и наглядности.

По поводу ошибок в данных я лично представляю себе только вариант с исключениями, руководствуясь тем, что если произошла критическая ошибка, то программа должна остановиться, выбросить исключение и «спросить» у программиста, что же делать в подобной ситуации. Для этого я создам три десятка исключений на каждую модель, наследующихся друг от друга, и в try… catch… catch… catch буду указывать, что нужно делать в подобной ситуации. Для некритических ошибок подошел бы вариант с неким подобием предупреждений (warnings), но на эту тему я пока, к сожалению, не думал.
Пример валидации данных из фреймворка Kohana, на мой взгляд это самый удобный вариант.
try
{
    $user = ORM::factory('User');
    $user->username = 'invalid username';
    $user->save();
}
catch (ORM_Validation_Exception $e)
{
     $errors = $e->errors();
}
Вопрос был про текучий интерфейс. Как раз и подразумевалось, что вариант подобный предложенному вами намного удобнее.
Если честно я не особо понимаю разницу в отлове исключения в предложенном мной варианте, и при использовании текучего интерфейса.
При сохранении просто делается валидация всех полей, и создается исключение определенного типа, в который вложены все ошибки в виде массива, как в примере выше.
он (программист) должен думать о логике работы приложения, а не об очередном UPDATE-запросе.

Для этого существуют ORM. Мой выбор Doctrine, но Propel также очень и очень хорош.

На мой взгляд, чтобы код легко читался, он (помимо, разумеется, стандартов кодирования и понятности алгоритмов) должен быть максимально приближен к естественному языку.

Чтобы код легко читался однозначно следует избегать использования «магических» методов. Даже не учитывая проблемы с производительностью (особенно в предложенном варианте с двумя регулярными выражениями внутри), магия усложняет чтение кода. В чем проблема написать get и set методы для нужных свойств модели? Большинство IDE позволяют это делать автоматически.
По поводу усложнения чтения кода я не соглашусь, потому что, на мой взгляд, куча проверок перед изменением записи (если использовать запись «сырых» данных, используя, например, $instance->price = 12.95) нисколько не облегчает чтение кода, и, кроме того, зачастую приводит к его дублированию. А если использовать генерируемые методы, то внешне (в логике приложения) это не будет отличаться от данного подхода ничем, кроме этих самых двух регулярок, которые, при наличии дополнительной логики при установке значения, не используются.

Этот механизм не претендует на то, чтобы заткнуть за пояс известные ORM. Это просто иллюстрация еще одного подхода, который, на мой взгляд ии по моему опыту, достаточно удобен в использовании.
если использовать запись «сырых» данных, используя, например, $instance->price = 12.95

Я и не рекомендую использовать публичные свойства. Причины (понятность, дублирование) вы сами указали.

Поясню почему я против «магии» в данном варианте. Текущая реализация мешает бизнес-логику (наши модели) с технической реализацией, что чревато в дальнейшем сложноподдерживаемым кодом и трудноуловимыми ошибками. Я стараюсь оставлять модели максимально чистыми, не подверженными технической реализации. Это позволит впоследствии изменить одну часть не затрагивая другую. Например поменять одну ORM систему на другую, или вообще отказаться от ORM в угоду производительности в «узких» местах.

Это конечно только мое мнение, и оно не претендует на истину в последней инстанции.
Вопрос, как мне кажется, только в том, какая часть логики хранится в модели и хранится ли вообще. Если чутко чувствовать и соблюдать эту грань, то даже при наличии определенного количества логики в моделях, можно избежать и сложноподдерживаемого кода и трудноуловимых ошибок. При этом, это в определенной мере позволит избавиться от дублирования кода в случае с разного рода валидацией данных.

При смене одной ORM на другую в любом случае придется переделывать бизнес-логику, либо нужно делать доступ к данным «точно такой же, как в %ORM-name%». Если вы имеете в виду, к примеру, переход с MySQL на PostgreSQL, то на этот случай предусмотрены адаптеры подключения к БД и переход в данном случае проходит вполне безболезненно, заменяя лишь класс, от которого наследуется модель.
При смене одной ORM на другую в любом случае придется переделывать бизнес-логику

Бизнес-логика не должна зависеть от ORM (если только не что-то вроде phpmyadmin пишем). При хорошей MVC архитектуре, переделывать при смене ORM (или вообще отказа от РСУБД и перехода на NoSQL, файлы или какое-нибудь облачное хранилище) нужно только контроллер.
Да, я скорее имел в виду контроллер. К сожалению, путаюсь в терминологии.
В данном случае, как я уже сказал, менять не нужно вообще ничего при условии, что названия свойств остаются теми же. В этой статье я лишь немного коснулся темы адаптеров, но вообще они позволяют легко и просто переключаться между работой с разнообразными хранилищами, будь то база данных, реляционная или нет, файлы, что угодно. Главное, чтобы оттуда можно было прочитать данные, туда можно было записать данные и оттуда можно было удалить данные.
Наоборот, вся бизнес логика должна быть в моделях, а то про что говорите вы называется ТТУК
Совершенно необязательно. Логика может быть в различных сервисах, а контроллер только дергает нужные. Чаще проще сделать маленький сервис с необходимыми зависимостями, чем пробрасывать зависимости в объект модели.
Хочу придраться к вашему конечному варианту: в реальном мире товар сам себя на полку не кладет. Не должно у товара быть метода save, как мне кажется. Для этого есть менеджер БД, который и должен его сохранять.
Если честно, не совсем понял логику. Вы имеете в виду, что должен быть некий централизованный объект, который умеет сохранять любую запись в базу? А если мне при сохранении именно товаров нужно сделать еще что-то, в лог запись добавить, например?
Вариант 0: слелать kju gj instance_of :)
Вариант 1: для каждого класса сущности сделать класс хранилища (репозитория)
Вариант 2: сделать для CRUD статические методы.

Как пример, посмотрите на Doctrine ORM. Сам объект строки только хранит данный.
Там вообще как такового объекта строки или записи нет. Объект мапится на БД, но правила мапинга вовсе не обязаны 1:1 к структуре БД относится.
Это просто 2 разных шаблона проектирования, тот что описывает автор называется Active Record, а то что предлагаете вы называется Data Mapper.
__call — это медленно! Где-то была статья недавно, не могу найти. Там производился анализ работы с вызовом методов через __call и обычным вызовом. А здесь, где-то в середине, есть рассуждение про орм в принципе, и почему это плохо. Про spl я вообще молчу…

он (программист) должен думать о логике работы приложения, а не об очередном UPDATE-запросе.

хороший программист должен думать обо всем.
Не все разрабатывают хайлоад. Хотя лично я __call тоже избегаю, но не из-за скорости, а из-за сложности чтения и поддержки.

P.S. Слушание подкастов напоминает мне сказки на грампластинках или радиоспектакли в СССР. В дестве мог минут 40 сконцентрироваться, сейчас нет, или заспыпаю, если глаза закрываю, или читать что-то начинаю и нить теряю.
Никто не спорит, что использование __call медленнее, а дополнительные системы вроде ORM тоже дают определенную нагрузку. Но на другой чаше весов скорость написания кода, удобство его поддержки и чисто субъективная понятность. Грубо говоря, можно все это написать на ассемблере, и это будет определенно быстрее обычных вызовов.

Хороший программист должен думать обо всем, но не все являются хорошими программистами. Зачастую лучше предоставить вот такую «автоматическую коробку передач», которая позволяет добраться из точки А в точку Б не требуя от программиста погружаться в дебри реализации. лично мне спокойнее на душе, когда я знаю, что мой коллега ничего не забудет, потому что я это уже предусмотрел в самой модели. Но я, разумеется, понимаю, что это нарушение заповеди «не храни логику в модели» :)
Но я, разумеется, понимаю, что это нарушение заповеди «не храни логику в модели» :)

Некоторую логику нужно хранить в модели. Насчёт логики хранения споры идут постоянно — уж очень народу нравится паттерн ActiveRecord и не нравится использовать его чисто как DTO.
А можно узнать про принцип «не храни логику в модели»? Я только слышал про обратный принцип, что вся логика работы должна быть в модели.
По MVC в модели должны быть только данные, а вся логика должна быть в контроллерах.
По моему вы ошибаетесь, приведу в пример выдержку из вики
Выдержка
Начинающие программисты (особенно в веб-программировании, где аббревиатура MVC стала популярна) очень часто трактуют архитектурную модель MVC как пассивную модель MVC. В этом случае модель выступает исключительно совокупностью функций для доступа к данным, а контроллер содержит бизнес-логику. В результате код моделей по факту является средством получения данных из СУБД, а контроллер представляет собой типичный модуль, наполненный бизнес-логикой, или скрипт в терминологии веб-программирования. В результате такого понимания MVC разработчики стали писать код, который Pádraic Brady, известный в кругах сообщества Zend Framework, охарактеризовал как ТТУК — «Толстые тупые уродливые контроллеры» (Fat Stupid Ugly Controllers):
Среднестатистический ТТУК получал данные из БД (используя уровень абстракции базы данных, делая вид, что это модель) или манипулировал, проверял, записывал, а также передавал данные в вид. Такой подход стал очень популярен потому, что использование таких контроллеров похоже на классическую практику использования отдельного php-файла для каждой страницы приложения.

Но в объектно-ориентированном программировании используется активная модель MVC, где модель — это не только совокупность кода доступа к данным и СУБД, а вся бизнес-логика. В свою очередь, контроллеры представляют собой лишь элементы системы, в чьи непосредственные обязанности входит приём данных из запроса и передача их другим элементам системы. Только в этом случае контроллер становится «тонким» и выполняет исключительно функцию связующего звена (glue layer) между отдельными компонентами системы.

phpStorm
У меня такой каши нет. Специально проверил.
И как на счёт кодогенерации?
Плюсы:
* Работает автодополнение (конечно, в Вашем случае тоже можно заставить работать автодополнение, но тут мы или вводим вручную или опять же генерируем)
* Используется минимум магических функций

Минусы:
* Нужно реализовать.
Кодогенерация — это один из выходов. Другой выход — это как раз использование магии. Оба подхода имеют свои плюсы и минусы. Плюс магического, в частности, в том, что не нет строгого набора данных.

Вот пример: есть, допустим, стороннее API и есть модель, которая с ним работает. API обновилось, добавилось новое свойство. Его можно использовать сразу же, не редактируя модель. Это полезно, когда разработкой фреймворка занимается один человек, а написанием бизнес-логики — другой.
Плюс магического, в частности, в том, что не нет строгого набора данных.

Это и плюс, и минус. В последнее время мои программы на PHP приобретают всё больше Java стиль — явные объявления типов где только можно, ассерты с instance_of где нельзя и т. п. Динамическими возможностями пользуюсь с большой неохотой, когда они приносят явное упрощение кода, потому как практически всегда они снижают читаемость и поддерживаемость.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории