Pull to refresh

Comments 51

Сейчас уже 2 часа, но я не жалею, что прочитал ваши статьи. Достаточно интересно, хотя я и болею Ruby=)

Спасибо!
Хочу от вас заразиться им, я уже высосал из PHP все что только можно. Вкусно, но мало :(
с чего бы вдруг? руби слишком сладкий, как на меня.
имхо. Эти языки нельзя сравнивать
Думаю, — высосали далеко не всё, а только надкусили.

Код из наследника Lego:
    return $this->fetch("lego_fotos.tpl");


Очень нехороший хардкод шаблона для контроллера.

Варианты решения:
1. Нужно определять в закрытом поле класса, например
    private $_template = 'lego_fotos.tpl';


2. Принять стандарты именования и вообще
больше не определять имя шаблона.
Пусть родитель (Lego) сам автоматом ориентируясь
по имени наследника, либо по пути, где он живёт
сам определяет шаблон.

PS. foto photo
на самом деле я согласен, что код далеко не виртуозный. в руби полно магии, но ТС не осилил даже магию php
Имхо, хардкод имени шаблона в контроллере допустим, как и магия, но мне по душе их комбинация — основанное на соглашениях именования дефолтное имя шаблона, плюс обязательная возможность его изменения на произвольной и в теле контроллера, и в конфигах, причём динамическая (для одних параметров/результатов один шаблон, для других — другой).

Магия позволяет уменьшить количество кода для типичных случаев «1 контроллер — 1 шаблон», возможность изменения — реализовывать более гибкую логику для нетипичных случаев или простыми способами уменьшать количество «копипаста» («1 контроолер — много шаблонов», «много контроллеров — один шаблон», и, теоретически, «много контроллеров — много шаблонов», на практике не встречал).

Если вопрос стоит «или/или», то предпочту хардкод в теле контроллера, чем неизменяемое имя шаблона где-то ещё. Удобства не должны мешать гибкости.
согласен. магию — в печь. как по мне, так PHP позволяет создавать слишком много магии в коде. если при этом код ещё и не документируется должны образом — то любой сколь-нибудь сложный проект обречён.
Я не столь однозначен, дефолтные значения и действия (в том числе динамически вычисляемые, в том числе по «соглашениям об именовании» — собственно «магия») удобны, если на их использование не уходит слишком много ресурсов и они хорошо документированы, причём не в коде (я не считаю, что самодокументированный код, изначально предназначенный для использования другими разработчиками, достаточен для эффективного использования). Но при этом всегда должен быть простой способ отказа от них, например, переопределение метода при наследовании.
Это не взаимоисключающие условия (имена по умолчанию и переопределение).
Я в курсе, но в некоторых продуктах либо одно, либо другое (справедливости ради, где есть магия практически всегда её можно переопределить)
А лучше не заморачиваться и принимать железные соглашения, которые не будут меняться в 99% случаев.
А что делать в 1% случаев? Я считаю, что всегда должна быть возможность переопределить дефолтное поведение без особых усилий, там более в таком действии, как выбор шаблона для отображения.
Все верно. Более того, я утрировал — это может быть в отдельных случаях куда более одного процента. В общем, в 1% это соглашение меняется :) И я имею именно то, что говорю — меняется соглашение, а не что-то где-то хардкодится (конкретно — уж точно не нужно хардкодить в контроллере имя шаблона).
Замена соглашений (особенно уже захардкоденных, типа «файл шаблона лежит в каталоге модуля, в подкаталоге templates, имя состоит из имени контроллера и имени его метода», особенно в продукте стороннего разработчика) — дело, вообще говоря, не тривиальное и по трудоемкости даже близко может не лежать к хардкодингу (и последующем сопровождении) в контроллере.

«Если вопрос стоит «или/или», то предпочту хардкод в теле контроллера, чем неизменяемое имя шаблона где-то ещё. Удобства не должны мешать гибкости.»

А почему «где-то» еще оно будет неизменяемое? Что может быть «неизменяемей» хардкода? Соглашения всегда куда проще поменять.
Конечно, что один человек собрал, другой завсегда разобрать сможет (с) и в этом смысле неизменяемого имени шаблона вообще быть не может, но вот штатных средств его изменить из контроллера вполне может и не быть.

Хардкод как раз самый изменяемый — пиши, что душе угодно (а потом поддерживай как хочешь :) ).

Соглашения на то и соглашения, что требуют непротивление сторон. Если, например, разработчик фреймворка не хочет соглашаться тем, что шаблоны модуля может лежать не только в каталоге модуля, то поменять малой кровью не получится.
«Хардкод как раз самый изменяемый — пиши, что душе угодно (а потом поддерживай как хочешь :)» — exactly :)

Отражает всю суть! О том и речь — сделать можно, но мы ведь говорим о «maintanable»-системах, не так ли? В этом смысле, хорошая архитектура — это уже само по себе соглашение, а тут уж каждый решает для себя.
Архитектура хорошая для 99% случаев может оказаться плохой в 1%. И тут я согласен, что каждый решает для себя — сделать костыль для 1% (хорошо этот костыль задокументировав и не только в коде) со всякими рефлексиями и прочими интроспекциями на первой итерации прототипа или изменять архитектуру приложения (пускай и полностью покрытого тестами) за день до дедлайна :)
Ага, и на меня чихните!
Статья хорошая и правильная.
Но возникает один вопрос, — если модули в проекты (цитирую автора) копипастятся,
то совершенно неудобной становится их поддержка.
Есть модуль, нашли в нём ошибку, либо решили доработать, — каким образом
вносить изменения во все проекты, куда он включен?

Я предлагаю такое решение:
  1. Модули хранятся в репозитории.
  2. На сервере для работы хранятся в одном месте, скажем
    /webhome/lib/modules
  3. Этот путь прописан в include_path.

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

Стандартный путь решения таких проблем: использование VCS и автоматического тестирования. Модули хранятся в репозитории VCS, проект тоже, модули в проект импортируются как внешние ссылки. При очередном обновлении рабочей копии проекта получаете свежую копию модулей, запускаете тесты. Если всё проходит хорошо — деплоите их на сервер; если нет, а разбираться с ошибками времени нет, то создаёте новую ветку из обновленной рабочей копии и постепенно с ними разбираетесь, продолжая параллельно работать над функционалом проекта в основной ветке со старыми модулями, переключаясь по необходимости. Закончив с исправлением ошибок совместимости мержите ветки и деплоите проект на сервер

Смысл только в том, что для начала неплохо, как минимум, избавиться от копипастинга.
А развивать и улучшать дальше можно практически бесконечно.
Тесты при таком хранении и использовании, конечно обязательны.
Лучше даже не «запускаете тесты», а «запускаются тесты и шлют вам на e-mail сведения об ошибках».
Ну, копипастинга не будет в случае использования VCS. Просто зачем создавать, развивать и улучшать велосипед, реализующий не основные функции проекта, а инфраструктурные, для которых есть универсальные специализированные :) решения, типа Subversion, Mercurial, Git и т. д.?

На email слать нужно в случае автоматического обновления, большим противником которого я являюсь. Получить автоматическое уведомления о выходе новой версии стороннего модуля — это да, удобно и никаких рисков (разве что рассчитывать на них, но по каким-то причинам не получить). Но вот само обновление — слишком велики риски, что что-то пойдёт не так (вплоть до проникновения вредоносного кода), лучше ручками посмотреть чэнжлоги и диффы, прикинуть как они могут повлиять на проект, обновиться, прогнать тесты, может написать новые и ручками инициировать деплой на продакшн. Уведомления по email в этом случае излишни.
Под «хранятся в репозитории» я и понимал системы контроля версий.
А-а, я подумал какую-то свою инфраструктуру реализовывать предлагаете. Но в любом случае, имхо, модули должны храниться и обновляться для каждого проекта отдельно, чтобы не получить ситуацию одновременной неработоспособности всех или нескольких проектов по одной причине.
А по возможности еще и привязывать к модулю номер версии для надежности.

В случае с компилируемыми языками еще и автоматически отслеживать номер версии сборки.
Привязку номера версии считаю излишней, пропадает большая часть смысла включения в проект внешних репозиториев, как мне кажется — за обновлениями придёся следить вручеую
Всмысле? Почему пропадает? Просто, к примеру, определенный модуль зависит от определенной версии самой системы, или от версии другого модуля.
А версия, автоматически прибавляемая по успешному завершению build-task/test-run, позволит в случае какого-то несовпадения, обработать ошибку, сделать простой degrade и записать в лог запись о несовпадении версий и необходимости обновления.

Как это связано с репозиториями, я не особо понимаю. Разъясните?

Далее, вы там выше пишете:
«Но в любом случае, имхо, модули должны храниться и обновляться для каждого проекта отдельно, чтобы не получить ситуацию одновременной неработоспособности всех или нескольких проектов по одной причине.»
Именно так, только зачем модули хранить? Можно просто подключать сборки последних версий и собирать (build) и тестировать свой проект именно с ними.

Впрочем, все, о чем я говорю, касается, прежде всего компилируемых языков (я сам работаю в основном с C#), для интерпретируемых может быть оптимальнее и другая схема.

Просто поддержка исходного кода одного и того же дополнительного модуля внутри разных проектов — это же будет жесть с синхронизацией, если в модуль вносятся изменения в одном месте, и надо применить их к другому проекту.
Угу, речь идёт прежде всего о интерпретируемых языках, где исходный текст модуля практически равносилен объектному коду «сборки». Обновлять сборку в глобальном хранилище (кажется так это у вас называется?) на продакшене без проверки совместимости всех приложений, по-моему, несколько опрометчиво, особенно если система привязки не позволяет привязываться к определенным версиям сборок хотя бы на этапе деплоя (по-моему именно поэтому очень многие Windows приложения предпочитают таскать все свои и чужие dll с собой в инсталляторе или линковаться статически, а не пользоваться как бы системными (теми, которые установили другие приложения в Windows/System32), устанавливая нужные как системные в случае отсутствия, во многом убивая саму концепцию разделяемых библиотек — dll hell и всё такое). Но в модуле может выявиться и в новой версии исправлена, например, критическая ошибка и обновление хотя бы части приложений должно быть осуществлено as soon as possible, но не все приложения проходят тесты с новой версией. При централизованном хранении и использования «сборки» модуля на продакшене мы можем или обновить все приложения, получая неработоспособность части из них, или не обновлять ни одно, пока не будет полной работоспособности всех. При распределенном (каждому приложению свой экземпляр модуля) мы можем немедленно обновить часть приложений, заработавшую сразу, и доводить остальные до совместимости с новой версией модуля уже более спокойно.

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

Жести с синхронизацией не будет, если административно (в смысле приказом, ЦУ или ещё как) обязать разработчиков не править модули внутри других проектов, а править их только в репозитории самого модуля или вообще писать багрепорты и прочие фичереквесты тем разработчикам, которые за эти модули отвечают (дополнительно можно ограничить эту возможность технически, права доступа, ограниченный круг лиц, имеющих право делать коммит/пуш и т. п.), фактически заставляя относиться к модулям с момента их выделения из репозитория проекта в отдельный репозиторий как к абсолютно сторонним библиотекам.

Многие проблемы бы решила стандартная возможность ОС/фреймворка/VCS/IDE/CI/… автоматически осуществлять привязку к максимально новой версии модулей/библиотек из тех, для которых проходят автоматические тесты, но стандартной возможности даже ручной привязки скриптов к разным версиям модулей/библиотек на PHP, афаик, нет. Свои велосипеды придумать можно, например хранить разные версии библиотек по путям типа /usr/local/lib/myframework/v.X.X.X или /usr/local/lib/myframework/bld.XXXXXXXX (примерный аналог «Program Files/Common Files/myframework/...»и при коммите/пуше/деплое гонять тесты для всех новых ревизий фреймворка, заменяя строки в конфигах (а при большом «везении» во всех файлах проекта, включая html шаблоны) по маске, и деплоя в итоге c той ревизией с которой тесты ещё проходят… Можно, но лично у меня не тот уровень проектов, чтобы реализовывать такое с нуля имело бы смысл, по-моему, даже возможность ручной привязки, не говоря об автоматической. Пока проще использовать модули/библиотеки для каждого приложения отдельно, привязываясь по путям вида `docroot`/../lib/myframework, храня и модифицируя их в отдельном репозитории. За счёт единого репозитория модуля получаем некоторые плюсы глобального хранилища (в частности избегаем жести с синхронизацией), за счёт локальности модуля в рабочей копии получаем возможность привязываться к определенной версии при разработке и деплое (по умолчанию используя свежайшую) и даже его локального исправления без внесения в репозиторий модуля, правда способ нерекомендуемый из-за тяжести поддержки дальнейших обновлений.

уф… что-то много букв получилось, хорошая практика для освоения слепой печати вышла :) и стирать жалко
Ну тут нельзя не плюсануть такой развернутый ответ :) Правда мы уже углубляемся в детали топика, с последующим переходом в оффтопик. Мысль я понял, все дельно, кроме прочего зависит от конкретного проекта и контекста (ОС/framework/VCS/IDE/Build/CI).
Хорошая статья, но, как и прошлая, написана с опозданием года на 3.
Позвольте покритиковать детали, а не суть:

По использованию __DIR__ очевидно, что вы пишете скрипт для php5.3 only. Так почему не используете неймспейсы для имён классов?

Примечание: некоторые файлы проекта начинаются с '.' (точки) для того, чтобы при сортировке по имени они были сверху

Плохой подход, т.к. с точки начинаются имена скрытых файлов и директорий в никсах (например, .htaccess) и программистам это станет излишней головной болью

$_SERVER['DOCUMENT_ROOT']
Использование DOCUMENT_ROOT — плохая практика, т.к. он может быть неправильно настроен и из-за этого сломается весь ваш скрипт

Методы getJavascripts() и getStylesheets() — копипаста, неплохо было бы отрефакторить.

Зачем временные переменные $csses и $jses в методе getHeaderBlock()?

Всё граммотно подметил
особенно про .* фаилы
	// просмотр одной фотографии в полном размере
	function action_showone($foto_id){
		$f = new tbl_fotos($foto_id);
		Output::assign("foto", $f);
		$ret = $this->fetch("showone.tpl");

		// КОММЕНТАРИИ. Фотогафию можно комментировать
		$c = new comments_controller("foto_comments", "tbl_fotos", $f->getId());
		$c->run();
		return $ret.$c->getOutput(); //склеиваем выводы двух модулей
	}

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

Названия action_mainbar и action_sidebar не очень удачные, т. к. указывают на место в выводе, а не на логику. Думаю, action_show_list и action_show_list_as_block более удачные, так как указывают на то, что функции делают. Также action_index и action_mainbar дублируют друг друга (первая, по сути, частный случай второй). А можно вообще все три объединить в одну функцию action_show_list с параметром show = all|page|block. Хардкодного SQL будет меньше, кстати, откуда он вообще появился? Вроде же есть какой-то класс tbl_fotos, судя по названию должный абстрагировать работу с таблицей, а судя по коду абстрагирующий работу с одной фото.

«Статическое» включение css и js всего модуля в отображении одного блока — необходимость или просто лень было делать «динамическое», только реально используемых?

Присоединяюсь к вопросу насчёт неймспейсов.
>> Подобные вызовы должны, имхо, производиться из шаблонов.
Безусловно можно передать объект комментариев в шаблонизатор, и там уже, в нужном месте вставить {$comments->getOutput()}. Это позволит дизайнерам менять расположение комментов и хотя бы добавить футер после комментариев. (если есть желание дать им такую возможность).

>>Названия action_mainbar и action_sidebar не очень удачные
Cогласен не очень красива, это пример реализации модуля, к модулю все-таки требования на предмет красоты меньше, чем к самому движку. В модулях частенько несовершенные решения

>Безусловно можно передать объект комментариев в шаблонизатор

Можно производить и вызов comments_controller из шаблона (то есть вставить в шаблон не только $comments->getOuput(), но и $comments->new comments_controller; $comments->run();), хотя часто такой способ тоже считается нарушением MVC, но всё же менее тяжким, чем вызов контроллера из контроллера и конкатенация результатов. Хотя самое красивое решение, по-моему, реализация в модели fotos загрузки (можно отложенной) комментариев и прямой доступ к ним из шаблона, всё-таки связь фото-комментарии обусловлена бизнес-логикой, а не логикой приложения/отображения.

>Cогласен не очень красива, это пример реализации модуля, к модулю все-таки требования на предмет красоты меньше, чем к самому движку. В модулях частенько несовершенные решения

«Демки» движка должны быть не менее красивы, чем сам движок, должны показывать best practices. От некрасивых (а то и откровенно кривых) складывается плохое мнение о самом фреймворке из-за мыслей типа «Если уж автор фреймворка не смог реализовать красивое решение, значит это вообще нереально или, в лучшем случае, грозит большими накладными расходами». А уж если новички, не способные определить "(не)красивость" на глаз, будут использовать аналогичные решения в своих модулях… Имхо, сомнительная слава PHP как «языка быдлокодеров» идёт, не в последнюю очередь, из-за таких примеров, где, хотя бы, нет дисклаймера «Пример учебный, не использовать без доработки в реальных проектах».
Я понимаю ваше стремление к совершенству, сам над этим постоянно ломаю голову.
Вообще, если честно, если писать и думать — оно само собой получается MVC. Я не боюсь нарушить концепцию, я даже всегда стараюсь сделать не так, как написано в шаблонах проектирования, но в итоге небходимость сама выталкивает, и другие сморят — и говорят MVC. А теперь я её еще и нарушаю :).
Тоже проходил этап «выталкивания на паттерны» не смотря на всё сопротивление организма :) Потом понял, что проще не сопротивляться и при наличии двух альтернатив реализации, из которых одна соответствует паттернам, а другая нет проще сразу реализовывать соответствующую, потом переделывать не придётся, если только нет особых причин её не реализовывать.

К одной из подобных причин отношу «преждевременное абстрагирование» (по аналогии с «преждевременной оптимизацией»). Вот вы выделили комментарии в (суб)модуль с отдельным контроллером — какова вероятность того, что он будет вызываться напрямую? По-моему нулевая, а значит контроллер не нужен, а его код нужно перенести в то место, где он будет использоваться. Появится необходимость использовать код повторно — тогда и абстрагировать его, но тоже не излишне: создать модель, создать шаблоны, но контроллер не создавать, пока не будет необходимости выводить комментарии отдельно от всего (но и тогда этот контроллер может быть вообще пустым при активном использовании «магии», при неактивном — две строки: получение данных и их передача в шаблон, плюс собственно вызов шаблона)
Хотел сначала как обычно покритиковать автора (для разнообразия/для творческой катализации), но перечитал статью (хотя это и похоже это на тень какого-то велосипеда) еще раз и понял, что автор человек думающий и прогрессивный, а через несколько статей и критиковать уже будет не за что.
а через несколько статей и критиковать уже будет не за что.
Очень часто глаз разработчика замыливается на первых пришедших в голову решениях (пускай даже с мыслью: «но потом надо будет подумать над этим серьезно, ну а пока и так сойдёт») и их объективные недостатки не замечаются. Критика, особенно конструктивная, может заставить автора взглянуть на решение с другой точки зрения и тогда вероятность того, что критиковать будет не за что, значительно повышается :)
Знатный велосипед! :) Но слишком много лишнего, еще пилить и пилить. Сейчас вот разбираюсь с двумя фреймворками, там выше про руби было уже, дык я про перл заряжу: mojolicious.org/ и perldancer.org/. У вас очень много кода лишнего, ну очень! ;)
// это магическая функция PHP, она вызовется каждый раз, когда мы напишем new SomeClass()
function __autoload($class_name){
Каждый модуль должен содержать все необходимое для работы в одной папке — и шаблоны, и модель, и контроллер.

Каким образом создаются необходимые таблицы БД, например, для хранения статей? Выполнением SQL-файлика вручную для каждого модуля?
Sign up to leave a comment.