tag:blogger.com,1999:blog-3530227379164395582024-03-14T03:07:01.788-07:00Ремарки на поляхivansuhttp://www.blogger.com/profile/06832719718249348472noreply@blogger.comBlogger3125tag:blogger.com,1999:blog-353022737916439558.post-36333004762339341612011-12-19T02:27:00.000-08:002011-12-19T02:33:59.957-08:00The "default" context does not exist и symfony doctrine:data-dump<div dir="ltr" style="text-align: left;" trbidi="on">Если вы не видели подобных сообщений в CLI symfony - вы счастливчик. Если да - рассмотрим workaround для борьбы с подобными капризами.<br />
<span id="fullpost"><br />
Если вы читаете это, скорее всего у вас вылетела ошибка при попытке выполнить эту команду (либо подобную ей):<br />
<br />
<script src="https://gist.github.com/1496437.js?file=ivansudakov.blogspot.com.111219.command.txt"></script><br />
Непродолжительное гугление показало, что, во-первых, панацеи нет, во-вторых - ноги растут из использования <b>sfContext::getInstance()</b> в классах модели. Для решения проблемы можно применить следующий workaround:<br />
<ol><li>Для того, чтобы понять где именно обваливается task, запустим команду с волшебным ключиком <b>--trace</b>:<br />
<br />
<script src="https://gist.github.com/1496419.js?file=ivansudakov.blogspot.com.111219.command2.txt"></script><br />
После кучи дампов запросов мы увидим уже намозолившую глаза ошибку, а далее trace:<br />
<br />
<script src="https://gist.github.com/1496420.js?file=ivansudakov.blogspot.com.111219.trace.txt"></script><br />
</li>
<li>Теперь дело за малым - пойти в метод PaymentSystem->getUrl(), и провести небольшой рефакторинг. Цель - убрать оттуда вызов sfContext::getInstance(), или, если это невозможно, поместить его внутрь условия:<br />
<br />
<script src="https://gist.github.com/1496424.js?file=ivansudakov.blogspot.com.111219.context.php"></script><br />
</li>
<li>Так повторяем до тех пор, пока не увидим сообщение:<br />
<br />
<script src="https://gist.github.com/1496434.js?file=ivansudakov.blogspot.com.111219.success.txt"></script><br />
</li>
</ol></div></span>ivansuhttp://www.blogger.com/profile/06832719718249348472noreply@blogger.com1tag:blogger.com,1999:blog-353022737916439558.post-16196888727988566462011-05-23T01:21:00.000-07:002011-05-30T09:23:39.952-07:00Учим генерилку модулей diem своему кодестайлуКто знаком с <a href="http://www.symfony-project.org/" target="_blank">symfony</a>, тот наверняка слышал о такой штуке как <a href="http://diem-project.org/" target="_blank">diem</a>. Это замечательная надстройка над первой версией symfony, которая прикручивает к нему возможности CMS.<br />
<br />
Diem, как и symfony, облегчает жизнь программисту, предоставляя возможность автоматической генерации кода. Но генерит он его, естественно, в symfony-style. А что, если он нам не подходит?<br />
<span id="fullpost"><br />
В проекте на diem концепция работы со view несколько отличается от стандартной. Если в стандартном symfony всё идёт от темплитов, которые представляют собой целые страницы (слой отображения), и экшенов - слой контроллера для темплита, то в diem сделали упор на удобство работы контенщика, а именно на возможность построения страницы "по кирпичикам". Это значит, что теперь менеджер создаёт страницу и накидывает на неё "виджеты" (<a href="http://diem-project.org/diem-5-1/doc/en/reference-book/widgets" target="_blank">widgets</a>). Прямая аналогия с компонентами битрикса. А widget по своей сути есть не что иное, как <a href="http://www.symfony-project.org/book/1_1/07-Inside-the-View-Layer" _target="blank">partial</a> с соответствующей логикой, помещённой в компонент.<br />
<br />
Таким образом, в diem слой Controller/View несколько сместился от action/template к component/partial (хотя, конечно action/template никто не отменял). Косвенный намёк на это можно найти в <a href="http://diem-project.org/diem-5-0/diem-5-0-0_beta5" _target="blank">описании</a> релиза 5й версии:<br />
<pre>The keyword "actions" has been replaced with "components".
"actions" keyword is now deprecated but still supported for backward compatibility.</pre>Теперь, собственно, про генерацию кода. Для примера - конкретная (и, я думаю, распространённая) задача. Научить diem следующим правилам:<br />
<ol><li>В качестве отступов использовать "\t" вместо четырёх пробелов.<br />
</li>
<li>Фигурная скобка должна открываться и закрываться на той же строке как у класса, так и у методов.<br />
</li>
</ol>Здесь рассматривается генерация кода и правка кодестайла для модулей в diem. Для "голого" symfony всё выглядит иначе (об этом, быть может напишу позже). <br />
<br />
Итак, чтобы сгенерить новый модуль в diem, описываем его в modules.yml:<br />
<pre>testmodule:
name: Демо-стенд
components:
createOrder: {name: 'Создание заказа'}
changeService: {name: 'Изменени услуги'}</pre>По такому описанию, diem в состоянии сгенерить файлы модуля, если набрать в CLI:<br />
<pre>./symfony dmFront:generate</pre>После вызова этой команды, управление по цепочке передаётся до метода dmFrontGenerateTask::execute(). Этот метод делает 3 вещи:<br />
<ol><li>Генерит скелет класса action'ов:<br />
<pre class="php" name="code">$actionGenerator = new dmFrontActionGenerator($module, $this->dispatcher, $this->get('filesystem'), $moduleDir);
</pre></li>
<li>Компонентов<br />
<pre class="php" name="code">$componentGenerator = new dmFrontComponentGenerator($module, $this->dispatcher, $this->get('filesystem'), $moduleDir);
</pre></li>
<li>И шаблоны<br />
<pre class="php" name="code">$actionTemplateGenerator = new dmFrontActionTemplateGenerator($module, $this->dispatcher, $this->get('filesystem'), $moduleDir);
</pre></li>
</ol>Diem в качестве инструмента для генерации кода использует Zend CodeGenerator (<b>lib/vendor/diem/dmCorePlugin/lib/vendor/Zend/CodeGenerator</b>). Кроме того, они добавили своих французских плющек и назвали их dmZend CodeGenerator(<b>lib/vendor/diem/dmCorePlugin/lib/vendor/dmZend</b>). Всё это вызывается из классов генерации кода <b>lib/vendor/diem/dmFrontPlugin/lib/generator</b>. <br />
<br />
Вот три места где заключены все правила кодестайла для генератора кода diem. <br />
<br />
Чтобы решить задачу, поставленную выше, делаем следующие изменения:<br />
<ul><li>lib/vendor/diem/dmCorePlugin/lib/vendor/Zend/CodeGenerator/Php/Class.php метод generate()<br />
<pre class="php" name="code">$implemented = $this->getImplementedInterfaces();
if (!empty($implemented)) {
$output .= ' implements ' . implode(', ', $implemented);
}
// $output .= self::LINE_FEED . '{' . self::LINE_FEED . self::LINE_FEED;
$output .= ' {' . self::LINE_FEED . self::LINE_FEED; // Our codestyle
</pre></li>
<li>lib/vendor/diem/dmCorePlugin/lib/vendor/dmZend/CodeGenerator/Php/Method.php метод generate()<br />
<pre class="php" name="code">if (!empty($parameters)) {
foreach ($parameters as $parameter) {
$parameterOuput[] = $parameter->generate();
}
$output .= implode(', ', $parameterOuput);
}
// $output .= ')' . self::LINE_FEED . $indent . '{' . self::LINE_FEED;
$output .= ') {' . self::LINE_FEED; //Our codestyle
</pre></li>
<li>lib/vendor/diem/dmFrontPlugin/lib/generator/dmFrontActionGenerator.php<br />
<pre class="php" name="code">class dmFrontActionGenerator extends dmFrontModuleGenerator
{
protected
$class,
// $indentation = ' ';
$indentation = "\t"; // Our codestyle
</pre></li>
<li>lib/vendor/diem/dmFrontPlugin/lib/generator/dmFrontComponentGenerator.php<br />
<pre class="php" name="code">class dmFrontComponentGenerator extends dmFrontModuleGenerator
{
protected
$class,
// $indentation = ' ';
$indentation = "\t"; // Our codestyle
</pre><br />
</li>
</ul>В итоге, получаем код, соответствующий нашему кодестайлу:<br />
<pre class="php" name="code">/**
* Демо-стенд components
*
* No redirection nor database manipulation ( insert, update, delete ) here
*/
class testmoduleComponents extends myFrontModuleComponents {
public function executeCreateOrder() {
// Your code here
}
public function executeChangeService() {
// Your code here
}
}</pre>У обычной правки файлов либы есть минус - они слетят при обновлении. Тут есть 2 выхода - поместить либу под систему контроля версий, либо добавить свой task для CLI. Второй, конечно, правильнее, но, как оно обычно бывает, сложнее :)<br />
</span>ivansuhttp://www.blogger.com/profile/06832719718249348472noreply@blogger.com0tag:blogger.com,1999:blog-353022737916439558.post-87878414321164686302011-03-24T22:05:00.000-07:002011-05-30T09:25:20.042-07:00Автолоад своих классов, bitrix и ZF<div dir="ltr" style="text-align: left;" trbidi="on">Однажды на одном из проектов у меня возникла задача сделать централизованный автолоад классов, так как проект из "маленького" стал перерастать в "не совсем маленький". Проблема, казалось бы, банальна, но не всё оказалось так просто. Проект на битриксе, слой контроллера и модели, в большинстве своём, самописный - порядка 25 классов, которые активно юзают Zend Framework.<br />
<span id="fullpost"><br />
Кратенько о структуре папок. В папке /external проекта у нас хранится ZF, а в папке /php.inc/classes - наша библиотечка. Причём наши классы - не отельный namespace формата ZF, а просто куча файлов с префиксом, лежащие на одном уровне вложенности.<br />
<br />
Прежде всего, регистрируем include_path'ы:<br />
<br />
<pre class="php" name="code">set_include_path(
$_SERVER['DOCUMENT_ROOT'] . '/../external' . PATH_SEPARATOR
. $_SERVER['DOCUMENT_ROOT'] . '/../php.inc/classes' . PATH_SEPARATOR
. get_include_path()
);
</pre><br />
Теперь собственно решение. Самое простое решение - через функцию __autoload.<br />
<ol><li>Свои классы определяем просто - смотрим на первую часть имени класса - она содержит уникальный (в рамках проекта) префикс. Если есть совпадение - просто делаем require_once.</li>
<li>Классы ZF тоже определяем по префиксу, но для подключения класса используем Zend_Loader::loadClass(). Он может кинуть исключение, если попытаться подключить класс, допустим, ZendTruLaLa. Поэтому оборачиваем в try/catch.</li>
<li>С классами битрикса хитрее. Основное время при написании автолоадера ушло на поиск того, как битрикс автолоадит свои классы. В результате нашёл. Без подробностей - делает он это через свой автолоад и метод CModule::RequireAutoloadClass(). Он возвращает true/false - как раз то что нам нужно.</li>
</ol>Теперь можно автолоадить:<br />
<pre class="php" name="code">function __autoload($sClassName) {
if (strpos($sClassName, 'My') === 0) {
return require_once $sClassName . '.php';
}
if (strpos($sClassName, 'Zend') === 0) {
try {
Zend_Loader::loadClass($sClassName);
return true;
} catch (Exception $e) {
return false;
}
}
if (CModule::RequireAutoloadClass($sClassName)) {
return true;
}
return false;
}
</pre>Решение далеко не идеальное. Оно подходит в качестве первоначального "рабочего" варианта и даёт пространство для рефакторинга. Здесь отмечу, что если делать "по уму", то, конечно, это должен быть отдельный класс, что-то вроде этого:<br />
<pre class="php" name="code">class MyAutoloader {
public function __construct() {
spl_autoload_register(array($this, 'loader'));
}
private function loader($sClassName) {
if (strpos($sClassName, 'My') === 0) {
return require_once $sClassName . '.php';
}
if (strpos($sClassName, 'Zend') === 0) {
try {
Zend_Loader::loadClass($sClassName);
return true;
} catch (Exception $e) {
return false;
}
}
if (CModule::RequireAutoloadClass($sClassName)) {
return true;
}
return false;
}
}
</pre>Поэтому ставим себе<br />
<pre class="php" name="code">/**
* @todo Разобраться, почему не подключаются классы
* битрикса при использовании spl_autoload_register.
* После этого можно будет вынести автолоад
* в отдельный класс.
*/
</pre></div></span>ivansuhttp://www.blogger.com/profile/06832719718249348472noreply@blogger.com0