Pull to refresh

Comments 75

После AR Yii подобные решения кажутся не очень удобными и требующими большого допиливания.
Но пост решает поставленную задачу, так что спасибо.
С оглядкой на AR в Yii, который так же возвращает массив моделей при выборке нескольких записей, т.е. не подходит для больших выборок, порой приходится использовать и подобные решения. Собственно даже в Yii подобный итератор реализуется довольно просто, разница от приведенного кода будет не очень большой.
// не подходит для больших выборок
Вы про накладные расходы при создании обьектов? Если это правда будет проблемой есть DAO, который не менее удобен. Хотя это и не совсем то.
DAO и модели, если меня не подводит разум, совершенно разные вещи.
DAO обеспечивает общую абстракцию к БД, а не к конкретным таблицам и тем более логике моделей.
Я именно поэтому и добавил — 'Хотя это и не совсем то.'
Однако вы не ответили на вопрос. О какой ситуации вы говорите, когда AR с массивом моделей не подходит? И на каком количестве записей будет ощутима потеря производительности?
В Yii выборка самых-самых простых моделей, унаследованных от CActiveRecord, с 1-2 varchar полями, займет у вас порядка 20-30MB на массив из 10 записей.
Порядок потребления памяти к общему числу загруженных моделей полагаю вы можете сами оценить.
Потому даже сами разработчики Yii как правило не рекомендуют использовать массовую выборку в среднем более чем на 10 записях.
Именно так, при 1000+ записях может и 512мб не хватить
А часто ли надо 1000+ записей держать одновременно в памяти? Сделал запрос через DAO на 1000+, в цикле создал модель обработал и удалил. И память не течет и абстракция ломается только в момент одного единственного запроса.
Смотрите, все комменты топика на 1 странице, тащятся со связями (хотя бы чтобы определить плюсовали вы топик или нет). Вот пример когда будет дергаться куча моделей.
А если еще есть хуки типа afterFind, то через дао будет уже совсем неудобно.
можно обойти через NestedSet одним запросом. Если же все таки надо иметь 1000 моделей в памяти, то возможно проблема с архитектурой?
Очень интересно. Я только сейчас нашел, где мне реально могут понадобится большое количество AR моделей.
Yii: trunk
Количество моделей: 560
Столбцов: 12 (text, varchar, int, enum, timestamp)
Память: 5,450Кб

Я вывел интересующую меня информацию по 560 моделям, естественно без использования отношений. Никакого кеширования, сервер локальный не настроен совсем.

Так что если оставить за бортом рациональность подобного подхода(у меня например эта страница полностью кешируется — мне один раз памяти не жалко чтобы сохранить единообразие) — не так уж оно и течен.
С релэйшенами разрастается сильнее, а если еще пурифаером обработать, то еще больше памяти заберет.
Кто бы спорил, что один нормальный JOIN по индексам будет медленнее 560*[необходимое количество отношений] запросов. Для трех отношений(пост->(комменты, пользователь, рейтинг)) получается больше полутора тысяч запросов :D Не думаю что кто-то додумается до такого на production.

Я это к тому, что может и была проблема, но в последних версиях AR Yii вполне себе здорово вправляется с большими выборками. Если профайлер покажет проблему — да, стоит использовать DAO. Но предварительная оптимизация зло.
Хороший материал, идея очень понравилась. Действительно, для выборок в несколько тысяч строк часто приходилось отказываться от моделей и работать напрямую, такой подход снимает проблему. Спасибо за решение!
Решение хорошее, спасибо автору за его изложение, но хочу оговорить рамки его использования, это действительно — для тысяч записей, а когда речь идет о десятках миллионов записей, то он не применим. Именно на сверхбольших выборках обнаруживается, что MVC не решает задачу бизнес-логики в общем случае. MVC — это решение для среднего слоя и визуализации, и в каких-то случаях, в MVC может быть реализована бизнес-логика, чтобы не плодить слои ПО. Однако, в общем случае, должен быть отдельный слой бизнес-логики, и уже над ним MVC. Слой бизнес-логики может быть реализован как внутри СУБД на хранимых процедурах, так и в приложении, но для десятков миллионов возможен только один способ обработки — это реляционная алгебра и алгоритмический процедурный стиль без всяких объектов.
Вы правы. Модели предназначены именно для реализации комплексной бизнес-логики, а при выборки значительных размеров, как-правило речь идет о статистических данных, уже должны быть реализованы на более низком уровне.
На самом деле достаточно сделать один универсальный Итератор, а при его создании указать имя класса, в который оборачивать текущую запись БД. Ведь каких-то специфических задач PostIterator не решает (только стандартные next(), count() и т.д.). На крайняк — делаем потомка от ModelIterator и в нем реализовываем нужные плюшки. В общем, DRY.

И проблем со связями и прочей логикой не будет — всем занимается возвращаемая модель.

В целом, идея не нова, но актуальна.
Для DRY. Для таких случаев они, по-моему, идеально подходят.
Не используйте в коде, даже в качестве примера «mysql_*» функции. Один взгляд на код провоцирует преждевременные выводы.
там же mysqli_*, так что всё ок.
Было не всё, я поправлял.
Так и делал, но так как не планирую использовать другие базы кроме MySQL, то планирую использовать именно mysqli, потому что это даёт много плюсов.
Недавно был проект с Postgresql и в очередной раз убедился, что DBAL не спасает, если проект изначально не пишется мультибдшно.
Если честно, то с mysqli слабо знаком, как-то с mysql сразу на PDO перешёл. Расскажите о плюсах mysqli перед PDO.
пинг, мультистатмент и прочие специфические mysql вещи, особенно фишки mysql 4.1+
Пользуюсь PDO не потому, что потом планирую переводить проект на другую СУБД. А потому, что если захочу написать другой проект с другой СУБД, не хочу учить еще один интерфейс доступа к базе.
а в чём проблема, ничего переучивать не нужно имена почти полностью совпадают, не говоря уж о том, что обычно используются обёртки и класс для того же postgres может имплементировать тот же интерфейс.

Да и вообще это аргумент — я предпочитаю есть спагетти ложкой, потому что хочу есть суп тем же столовым прибором.

один час на переучивание это не большая жертва ради кучи плюсов специализированного инструмента.
При чем тут спагетти и суп? Я же не предлагаю PDO с NoSQL использовать )))
А какие такие плюсы у спец-инструментария?
Как бы там ни было, над PDO как правило используется еще не один слой абстракции. Например, какой-нибудь Zend_Db… так что от чистого PDO тоже мало остается )))
Потому что это вещи которые можно есть ложкой, но для спагетти вилка удобнее. Точно так же для mysql удобнее mysqli, нежели использовать DBAL типа PDO.

Например, очень удобно использовать mysqli_ping или mysqli_get_cache_stats
Между прочим, спагетти едят при помощи вилки и ложки одновременно ;)

У меня пока не было необходимости использовать эти функции. Да и вообще планирую отказываться от MySQL… К тому же подозреваю, что mysqli_get_cache_stats можно заменить запросом к БД.

Конечно, что использовать, личное дело каждого. Но не могу не заметить, что механизмы, использующие интерфейс PDO, есть во многих ЯП. Или даже вернее было бы сказать наоборот ))) Так что зная PDO мигрировать на другую платформу ЯП также будет проще ;)
>>Конечно, что использовать, личное дело каждого.
Я бы сказал не личное дело, а просто оно некоторым надо, некоторым нет. В данной статье как раз говорится об оптимизации работы, вот mysqli здесь выглядит более разумным, нежели использование DBAL.

Про переходы вот только не надо, это всё сказки. Я сам маялся с PDO, Query Builder и отказывался от кучи плюшек mysql, пока не понял, что мне это не нужно, иногда проще один день порефакторить код или почитать мануалы, чем целый год пользоваться не тем инструментом.
Мне так наоборот плюшки вроде:

$sth = $dbh->prepare('SELECT name, colour, calories
FROM fruit
WHERE calories <? AND colour = ?');
$sth->execute(array($calories, $colour));

кажутся необходимыми. Т.к. они обеспечивают безопасность кода защищая от инъекций. В общем я стремлюсь к стабильности в противовес небольшому выигрышу в скорости или удобстве, которое можно выделить в отдельный метод и использовать уже в своем интерфейсе.
Mysqli тоже умеет готовить запрос и даже более гибко чем PDO. Кстати, в этом месте в PDO был баг с кодировками, который относительно недавно исправили, там эмуляция prepare была
$stmt = $mysqli->prepare(«SELECT District FROM City WHERE Name=?»)) {
$stmt->bind_param(«s», $city);
$stmt->execute();
Этот метод о другом. Он не решает проблему с памятью, а просто откладывает ее. При большой выборке, когда произойдет обход всех записей, вы получите полную коллекцию в $this->_cache, которая по сути будет являться той же самой массовой выборкой.
Вы проводили тесты? Выгода от создания 1 объекта модели вместо 20 копеечная, так как вы заменили new Model() + setAttributes() на clone + setAttributes() + обвязку итератора. Получается, кода выполняется даже больше. clone точно так же, как и new, выделяет память. Где выигрыш?

Единственный выигрыш, если у вас в модели в конструкторе есть код, и он долго выполняется, clone будет быстрее new. Но если у вас модель долго создается, то это хреновая модель, скажем прямо.

А вообще, в PHP плохо с абстракциями. Я как-то мерял, использование плейсхолдеров в запросе добавляет в среднем 1мс на их подстановку + самая простая модель откушает еще несколько мс. Плюс время на загрузку файлов с классом. Причем это в написанном руками коде, стандартные PHP-ные ORM такой оверхед накидывают, что их вообще лучше не использовать. Дорогие запросы получаются. Если хочется быстро, то конечно, надо использовать PDO::fetch() + массивы, но это ужасно и неудобно. Вот хоть разорвись теперь.

P.S И почему во всех примерах аттрибуты модели хранятся каждый в отдельном свойстве? Тяжелое наследие Явы? Если их хранить в массивчике $attributes, операции fromArray() и toArray() превращаются в 1 строчку кода, и все остальное тоже становится удобнее.
(Спасибо что прочитали P.S. в статье.) — Данная статья не про то, как надо писать ORM, где и как хранить атрибуты. Речь лишь о способе использования моделей с массовой выборкой.

Тесты проводил.
— Выгода от создания 1 объекта модели вместо 20 копеечная, так как вы заменили new Model() + setAttributes() на clone + setAttributes() + обвязку итератора. Получается, кода выполняется даже больше. clone точно так же, как и new, выделяет память. Где выигрыш?
— Поясните пожалуйста как вы у вас получается что исполнение кода 20-ти моделей у вас меньше, чем исполнение кода одной модели с установкой атрибутов?
Как я и привел в первой реализации, можно создавать вообще только одну модели (без клона), только если она не меняется, если же меняется, то клон всегда быстрее самого new.

В остальном же ваш комментарий чем-то можно резюмировать «Объекты и ORM — зло, пишите нативно». Точно такая же работа происходит в любом языке программирования, везде есть свои затраты и все это прекрасно понимают. Про «золотую пулю» также было сказано в статье, что если вам надо просто — пишите нативно, нужен сложный поддерживаемый код — можете использовать модели.
К тому же, опять же как уже было сказано в статье, всегда за такими участками кода, так как любые обработки из БД с большой выборкой часто потребляют хорошую часть ресурсов — желательно иметь слой кеша.

Если у вас есть какие-то другие «правильные» решение, хотелось бы их услышать.
Я не понимаю, откуда у вас берется выгода. В классическом варианте с массивом моделей, допустим, мы делаем 100 раз new Model() и 100 раз setAttributes(). Хорошо. В вашем варианте мы делаем 1 раз new Model(), 100 раз clone Model, 100 раз setAttributes() + выполняется код итератора. Каким образом ваш вариант может быть выгоднее? Я не понимаю.

Я сделал простейший тест:

paste2.org/p/1898862

Действительно, небольшое преимущество есть — clone быстрее new на микросекунду или около того на моем железе. Но это же мелочь. На 20 моделях набежит 20 микросекунд. В то время как доступ через итератор (а не массив) наверняка съест все это преимущество.

Единственное, что я заметил — это то, что если модели не сохранять в массив, то памяти, конечно, расходуется меньше. И все.

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

Я проводил тесты, на объекте модели ArrayObject. При выборке 3000 объектов разница между одним изменчивым объектом и 3000 обычных есть, и огромна. Время генерации страницы меняется в 10-50 раз в зависимости от условий. 3000 не запускается функция __construct как минимум.
Дисклеймер: я проводил тесты не на том коде, который указан в посте.
У меня класс implements ArrayAccess, Iterator, Countable
У меня возникает только один вопрос. А точно объем данных лежащих в $this->_result сильно меньше, чем теже данных распиханные по ХХ объектам?

Т.е. хотелось бы услышать какой-то порядок цифр в духе «на выборке в 100 строк с 10 полями получили 100 объектов которые потребовали Х памяти, в то время как указанный метод, только Y».

P.S. На сколько я знаю раньше то, что у вас лежит в $this->_result просто интерпретатором не засчитывалось в используемую память. В результате хостер шареда мог получить интересную картину, когда скрипт на большой выборке память вроде как не жрет, но интерпретатор отламывал большие куски ОЗУ под хранение результатов выборки. В новых версиях это вроде как пофиксили, поэтому вопрос о том, на какой версии ставились эти опыты актуален.
Если посмотрите на код внимательнее, то увидите что в this->_result постоянно хранится только обычный массив атрибутов одной строки выборки, на основе которого по запросу выдается модель. Даже не модель.

Порядок эффективности сильно зависит от того, как вы реализуете модель (работа с атрибутами, объектами, методы и т.д.), потому конкретные тесты вы должно проводить уже на своем участке кода. Если же взять что-то известное, например Yii:
Без итератора: 12 моделей = 28MB потребление памяти для период массовой выборки, дальше пропорциональное увеличение примерно по 2МБ на каждую загруженную модель.
С итератором: 12 моделей = 7MB на модель и итератор, и увеличение числа записей на 1 пропорциональное одной записи в обычном массиве.

По скорости быстродействия итератор выигрывает при использовании clone за счет меньшего числа операций чем в массовой выборке. Если использовать new, то разница существенно ниже.
Судя по строке $this->_result = mysqli_query('SELECT * FROM posts') там находится mysqli result. Откуда там массив? Вот у меня и возникает вопрос, сколько памяти жрет эта структура. И было время, когда это количество памяти не учитывалось memory_get_usage. Не уверен, что это поправили в актуальной версии.
Извиняюсь, попутал с $this->_current_data.
В $this->_result действительно хранятся выбранные записи из базы, еще не преобразованные в массив, однако это самая минимальная жертва производительности и объему выделяемой памяти, и само собой, это гораздо менее накладно, чем хранить массив объектов.
Я тоже думаю, что хранятся они в структурах явно меньших, чем нативные структуры самого PHP. Но, еще раз повторюсь, на сколько меньших? Второй аспект это неучет это памяти через memory_get_usage, зафикшено ли это в наше время?

С Yii пример конечно показательный, 28МБ против 7МБ внушает, только в реальности на столько ли большая разница по ОЗУ? Я на этот аспект пытаюсь обратить внимание ничуть не оспаривая интересность приведенного подхода.
А у меня в фреймворке например, подход, который я назвал SMVC — решение, модель, отображение, контроллер. Модель это модель, а решение работает с базой данных, т.е. допустим:
class Users extends DBSolution // Users - решение
{
    public function add($username, $password) {
        $this->save(new User($username, $password)); // User - модель
    }

    public function listing() {
        $sql = $this->procedure()->select('*')->from($this, 'u');
        return $this->db->fetchAll($sql);
    }
}


Решение может как работать с бд, так и не работать. Может включать в себя что угодно, может абстрактировать одну таблицу (допустим в данном случае Users работает с таблицей users и в DBSolution есть метод __toString(), возвращающий имя класса в lowercase, поэтому я могу в dbal передать select('*')->from($this, 'u')), а может несколько. Может вообще весь проект быть в одном решении.

Модель может выглядеть так:
class User extends Model
{
    public $username, $password, $created;

    public function __construct($username, $password = null) {
        $this->username = $username;
        $this->created = date('Y-m-d H:i:sP');
        if ($password) $this->setPassword($password);
    }

    public function setPassword($password) {
        $this->password = Security::getHash($password);
    }
}


Также в абстрактном классе модели есть магический метод __Call для вызовов setVar и getVar
Простите, может быть я не совсем понял ваш пример, но если вам понадобится выбрать большое число записей, то каким образом вы решаете вопрос использования моделей для каждой записи? Просто в приведенном коде это не отображено.
Я люблю делать одностороннее моделирование — т.е. для задач создания использую модель, а для выборки использую dbal. Solution тут выполняет роль Дата Маппера, перемешанного с какими-то вспомогательными функциями. В отображении разбор результата, например операции listing, может выглядеть так:
  • {{ user.username }}
Хабрапарсер всё съел =)
        <ul>
            {% for user in users %}
            <li>{{ user.username }}</li>
            {% endfor %}
        </ul>
Судя по листингу, никакого доступа к именно модели данных тут не происходит, а перебирать поля из базы не является проблемой.
Возможна ли какая-то реализация во view с вызовом метода?
Например что-то вроде:
<ul>
            {% for user in users %}
            <li>{{ user.formattedUsername('first_name last_name') }}</li>
            {% endfor %}
</ul>
Решение — как аналог своих библиотек.
Тоже пользуюсь примерно таким же способом. В цикле перебирается массив записей внутри объекта, в результате каждый раз выдаётся один и тот же объект только с разными значениями в полях.

Кстати, это шаблон Приспособленец.
Не совсем понятно. Суть приспособленца в выносе из объектов свойств, которые варьируются и получается что объект всего один, но он может приспосабливаться к разным «ситуациям». А как может быть один и тот же объект, но с разными значениями в его полях?
Да, внешнее состояние объекта (значение полей таблицы базы данных) вынесено в массив, который перебирается. И данные в объект попадают только тогда когда нужна конкретная запись. Внутреннее состояние остаётся неизменным (таблица, список полей).
А как насчет того, чтобы итератору передавать только идентификатор результата БД? Т.е. в модели есть метод selectAll(), который возвращает итератор, который уже инкапсулирует доступ к БД. Хотя на самом деле все равно нужно писать кучу методов для разных случаев. Но вот эта штука будет полезна тогда, когда нужно выбрать все записи, но не все поля.
Кстати, встал вопрос. Легко ли переживает mysql множество запросов одновременно? Т.е. мне нужно несколько сущностей одновременно. На данный момент я последовательно выбираю каждую сущность в память, а затем передаю виду для представления. А если вот такими итераторами пользоваться?
Собственно не вижу проблемы в предложенной вами задаче, использование нескольких сущностей вполне обычное дело.
а как насчет нескольких проходов по результату? т.е. возвращаемся с помощью mysql_data_seek и начинаем все сначала?
ОРМ хорош для «простых» отношений… и для тех, кто не знает SQL.
если есть хоть какие-либо нагрузки — забиваем на ОРМ.
Я бы сказал — забиваем на решения из коробки :)
Приведенный же пример достаточно прост, но я бы так никогда не делал
>if ($post->isAuthOnly()) {
> // do something…
>}

Если уж мы используем Domain Object, то почему бы сразу doSomeThing не сделать методом этой модели? И в этом же методе проверять условие?

Просто вы простое условие выносите в модель, а то, что действительно должно быть в модели, оставляете в контроллере.
Не зная в чём заключается «do something», мы не можем утверждать где ему место. Если там происходит взаимодействие с контроллером и/или видами, то в модели ему не место.
Кстати, если бы вы читали соответствующую литературу ( www.books.ru/books/shablony-korporativnykh-prilozheniy-728454 ), вы бы знали, что описанные в статье подходы уже давно используются многими программистами. А так же знали, что данный подход является деградацией ООП.
Также вы неверно инкапсулировали логику в доменный объект, что ввело вас в еще большее заблуждение.

А смысл такого переноса логики прост — это повторное использование кода. Т.е. если вам в другом контроллере потребуется выполнить код doSomeThing над объектом, вам всего лишь нужно будет получить этот объект и выполнить метод. В приведенном выше случае придется копипастить код do something из контроллера в контроллер. А если вы будете вынуждены изменить этот код, его придется менять во всех контроллерах. А тут не далеко до ошибки, т.к. вспомнить все контроллеры, где этот код используется бывает крайне сложно.
Пожалуйста укажите ссылку на страницу в приведенной вами книге, которая описывает приведенные вами аргументы, книга сама по себе достаточно объемна и боюсь что вы можете искаженно трактовать ее через призму своего опыта.

Касательно «выноса условий в модель» вы совершенно неправы. Так как do something может содержать работу не только с данными самой модели, но и привлекать сторонние ресурсы (например другие модели, созданные в данном контроллере), которые нельзя инкапсулировать внутрь модели.

Также отдельно хотелось бы услышать что-то менее голословное касательно того, почему использования интерфейса итератора для работы с моделями является «деградацией ООП». То что это используется многими, ясно написано прямо в начале статьи "… продемонстрировать стандартные подходы к решению данной проблемы". Никто не претендовал на открытие Америки.
Вот именно, что книга достаточно объемна и указанием одной страницы не обойдешься. Если уж она находиться в вашем арсенале, стоит подробно ознакомиться с главой 9. В силу того, что книга описывает паттерны, а не конкретные реализации, естественно, что ее можно трактовать как угодно. Но я писал именно о том, что шаблон Domain Object Фаулер все же превозносит надо остальными.

Конечно, можно пользоваться разными подходами, но с увеличением количества логики предметной области неизбежно придется переносить ее обратно в Domain Object.

Я не пытаюсь докапываться и перемалывать каждое слово или фразу в статье. Просто хочу показать, что использование Doamin Object все же приоритетнее. Кстати на практике заметил, что долго создаются только те объекты, у которых сложные конструкторы (методы инициализации). Так что может стоит оптимизировать их.

Кстати, обычно, чтобы выполнить генерацию стандартной страницы, не нужно создавать много объектов. А на операции над данными всей таблицы (например) в любом случае уйдет какое-то время и оптимизировать тут на спичках какой смысл?

Инкапсулировать модели внутрь других моделей вполне себе можно. Либо с при помощи отношений, которые в той или иной мере должны быть реализованы в любой ORM, либо передавать их в качестве аргумента методу, который будет выполнять логику (в нашем случае doSomeThing( $otherDomain ) ).

А если метод doSomeThing полностью ведет обработку данных другой модели, проще перенести его в эту модель ;)

Почему использование итератора из примера зло уже ответил — это неоправданное «размазывание» логики и другого дублирования кода по контроллерам.
Использование Domain Object в данном случае может легко превратиться в «забивание гвоздей микроскопом», не говоря уже что вы советуете ее применить даже там, где ее использование часто излишне.
Например даже если вы, как предполагаете, сможете перенести всю дополнительную логику в методы модели, а все данные передавать через параметры метода, то в конце концов вы рискуете получить серьезный «говнокод» вроде
$model->method($param1, $model2, $item3, $controller4)
или же излишнюю унификацию вроде $model->method($all_possible_params);

Каждой логике должно быть своё место, и желание «инкапсулировать всё что можно» как раз часто и приводит к неправильному пониманию и применению ООП.

Вполне живой пример:
if ($user->isAuth()) {
// some profile data
// some statistics data
// display profile form
} else {
// some another html
// display login form
}

Если следовать вашей логике, то мы должны инкапсулировать это логику внутрь Domain Object и сделать что-то вроде
$user->displayForm();

Однако при таком подходе о нормальном MVC можно забыть.

Так что знать про такой хороший подход как Domain Object конечно хорошо, но и его стоит применять с умом.
Я не предлагал передавать множество параметров одному методу ни в коем случае. Это вы меня с кем-то перепутали. Ни слова не написал об этом. Речь шла об одном единственном объекте (а в реальности ссылке на него).

Так же мне кажется вы теряетесь в разнице между бизнес-логикой и представлением данных. И мне не понятно, в каком контексте должен использоваться ваш живой пример. Но по логике вещей метод displayForm() должен быть не частью модели, а, например, частью представления. Если это код вашего контроллера, то вы, похоже, смешиваете слои контроллера и представления, что тоже путь на темную сторону.

Прекратите утрировать и взгляните на вещи объективно.
Каждый раз, когда мне предлагают код вроде:

// Используем по назначению
$post_iterator = Post::getIterator()->selectAll();

foreach ($post_iterator as $post) {

if ($post->isAuthOnly()) {
// do something…
}
// do something…
}

или тот, что находится в комментарии выше, я задаю человеку вопрос, ставящий его в тупик: А вы теперь покажите мне Unit тест для кода do something.

Лично я не представляю, как это сделать.

Зато отлично представляю код для метода модели Post->doSomeThing().

И даже для метода Post->displayForm(). Только у меня бы это тогда было Post->getForm()->render() ;)
По моему, автор статьи прекрасно понимает о чем Вы. А деградация ООП — это не вина программиста, а неотъемлемая часть решения определенного класса задач. Когда нам нужен 1 экземпляр класса или десятки миллионов экземпляров — это приводит к деградации. Это значит, что задача не не ООП-шная в принципе и нужно решать ее в другой парадигме. Например, вводить слой бизнес-логики или решать задачу на уровне реляционной алгебры в хранимых процедурах. MVC же — это решение среднего слоя, и слоя визуализации.

Для поставленной в статье задачи, я бы рекомендовал:
1. Сделать абстрактный итератор (как предлагали выше) или вообще пользоваться массивом для хранения сущностей (сейчас поясню почему).
2. Разделить методы сущностей и методы над сущностями.
Приведу пример, имеем сущности: Vehicle (транспортное средство), Сhauffeur (водитель), VehicleService (станция техобслуживания) и VechicleFleet (парк транспортных средств). Так вот, сущность Vechicle возможно и имеет какие-то методы, например Turn, ChangeSpeed, Park, и т.д. но в нашей модели (а модель — это всегда упрощенное представление) не нужны эти методы, то Vechicle вполне может храниться в массиве, а вот если у насесть метод AssignСhauffeur, то этот метод может быть в статическом классе VechicleFleet, например VechicleFleet::AssignСhauffeur(Vechicle, Сhauffeur) или же нам нужно что-то починить VehicleService::Repair(Vechicle). Тогда Vechicle, Сhauffeur — это сущности, которые можно хранить в массивах и сами они это ассоциативные массивы своих атрибутов. А где же контроль типов, спросите вы. Ну все в порядке, В массив добавим Vechicle['_EntityClass']. Ну и нам нужен системный класс фабрика-репозиторий еще, чтобы осуществлять выборку из базы.
3. Кстати, вместо массивов можно тут и объекты применять, не суть, все равно массивы и объекты в PHP примерно одинаковы по внутренней реализации. Но массивы в некоторых случаях чуть быстрее, уже проводились и описывались на Хабре тесты.
4. Не скромно, но советую свою статью о смежной теме habrahabr.ru/blogs/webdev/117791/
У меня есть ощущение, что для реализации подобных итераторов совсем не обязательно плодить классы и объекты, а подобный подход легко реализовать и функциональным программированием. Зачем, ответьте мне тогда, городить здесь объекты? Если они являются ничем иным, как обертками к массивам и функциям? Где здесь ООП?

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

Впрочем, если подобные _крайние_ меры оптимизации необходимы, то проще уж действительно сделать скрипт, который вообще без каких-либо объектов обработает большой набор данных. Но и в этом случае не стоит забывать, что одна и та же логика проекта будет размазана по разным файлам и поддерживать придется их все.

Уж извините, не вижу другого смысла в этом подходе. А вижу только грядущие проблемы после его внедрения.
Вообще, статические классы используются только как логические группировки методов, можно сделать это и иначе, объединить функции бизнес-логики в неймспейсы. Вы абсолютно правы, ООП тут ни при чем, задачу можно решать и без него, но статья то поднимает проблематику обработки большой пачки объектов, поэтому я написал в этом стиле.
По статье соглашусь, что для каждого MVC свое. Но, предложенный в конце подход ИМХО утопичен и слишком сложен. Хотя бы даже из-за проблем тестирования кода JS. На сколько я знаю, таких мощных средств, как PHPUnit (Junit если угодно) пока еще нет. Ну во всяком случае они не так развиты. Хотя проскакивали тут определенные статейки habrahabr.ru/blogs/javascript/135979/

Я же стараюсь все максимально делать простым и прозрачным. Т.к. посте того, как решишь залезть в свой код через пол года, хочется понимать, что тут имел ввиду…
Sign up to leave a comment.

Articles