<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>fxposter&#039;s wave &#187; Doctrine</title>
	<atom:link href="http://blog.fxposter.org/tag/doctrine/feed/" rel="self" type="application/rss+xml" />
	<link>http://blog.fxposter.org</link>
	<description>Stories about Ruby, JavaScript, Objective-C and other cool tools</description>
	<lastBuildDate>Sun, 30 Oct 2011 20:00:54 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.3.1</generator>
		<item>
		<title>&#8220;Скрытые&#8221; записи с Symfony и Doctrine</title>
		<link>http://blog.fxposter.org/2009/02/07/hidden-records-with-symfony-and-doctrine/</link>
		<comments>http://blog.fxposter.org/2009/02/07/hidden-records-with-symfony-and-doctrine/#comments</comments>
		<pubDate>Sat, 07 Feb 2009 06:24:14 +0000</pubDate>
		<dc:creator>fxposter</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://blog.fxposter.org/?p=663</guid>
		<description><![CDATA[Под &#8220;скрытыми&#8221; записями сегодня будут пониматься &#8220;unapproved&#8221;-записи в таблице. Кому лень ходить по сылкам: иногда не все записи какой-нибудь таблицы нужно показывать пользователю, например - если я не хочу показывать некоторые посты в блоге. Обычно для этого я делаю поле, например, is_hidden, а затем выбираю все записи, где is_hidden = 0. Проблема состоит в том, [...]]]></description>
			<content:encoded><![CDATA[<p>Под &#8220;скрытыми&#8221; записями сегодня будут пониматься <a href="http://blog.fxposter.org/2008/02/02/approved-and-unapproved-records/">&#8220;unapproved&#8221;</a>-записи в таблице. Кому лень ходить по сылкам: иногда не все записи какой-нибудь таблицы нужно показывать пользователю, например - если я не хочу показывать некоторые посты в блоге. Обычно для этого я делаю поле, например, is_hidden, а затем выбираю все записи, где is_hidden = 0. Проблема состоит в том, что я обычный человек и могу забыть поставить нужное мне условие. Поэтому я хочу получить какое-нибудь простое, но очень эффективное решение такой проблемы. В ActiveRecord этим решением является default_scope. А я вам сегодня расскажу, как этого добиться в Doctrine.</p>
<p>Итак, представим, что у нас есть табличка Post:</p>
<pre><code class="yaml">Post:
  actAs:
    Timestampable: ~
  columns:
    title:     { type: string(128), notnull: true }
    text:      { type: text, notnull: true }
    is_hidden: { type: boolean, notnull: true, default: 0 }</code></pre>
<p>Самый простой, на первый взгляд, подход - переопределять <code class="php">PostTable::createQuery()</code>, чтобы этот метод возвращал уже Query с нужным нам &#8220;WHERE is_hidden = 0&#8243;. К сожалению, не всегда это помогает. Например, при выборке постов для какой-либо категории через $category-&gt;Posts этот метод не сработает.</p>
<p>Есть гораздо более простой способ сделать то, что нам нужно - использовать <a href="http://www.doctrine-project.org/documentation/manual/1_0/en/one-page#event-listeners">listener-ы</a>. В Doctrine есть довольно много событий, которые мы можем &#8220;слушать&#8221; и на которые мы можем реагировать. В данном случае нам подходит событие &#8220;preDqlSelect&#8221;, которое входит в группу &#8220;<a href="http://www.doctrine-project.org/documentation/manual/1_0/en/one-page#event-listeners:dql-hooks">DQL Hooks</a>&#8220;, и которое вызывается перед выполнением запроса на выборку записей. Как нам нужно прореагировать на событие: взять Doctrine_Query из Doctrine_Event и добавить в него дополнительные условия выборки.</p>
<p>Самый простой способ - переопределить метод preDqlSelect в самой записи:</p>
<pre><code class="php">class Post extends BasePost
{
  public function preDqlSelect(Doctrine_Event $event)
  {
    $params = $event-&gt;getParams();
    $event-&gt;getQuery()-&gt;addWhere("{$params['alias']}.is_hidden = 0");
  }
}</code></pre>
<p>В первой строке метода мы получаем параметры запроса, из которых нам нужен alias - можете считать это обычным alias-ом таблицы из SQL (в данном случае это alias таблицы в DQL), т.е. при таком DQL:</p>
<pre><code class="dql">FROM Post p</code></pre>
<p>alias-ом будет &#8220;p&#8221;.</p>
<p>Во второй строке мы получаем текущую query и добавляем в неё условие &#8220;is_hidden = 0&#8243;.</p>
<p class="note">Мы не можем использовать <code class="php">-&gt;where()</code>, т.к. этот метод сотрет все имеющиеся части WHERE в запросе. В то же время <code class="php">-&gt;addWhere</code> и <code class="php">-&gt;andWhere</code> ведут себя так же, как и <code class="php">-&gt;where</code> при отсутствии where-части запроса.</p>
<p>Собственно, вот и всё - тепер у нас будут выбираться только &#8220;видимые&#8221; посты.</p>
<p><em>Во второй части будет показано, как сделать созданный в этом посте код более реюзабельным, а также как сделать так, чтобы в backend-е посты показывались полностью.</em></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.fxposter.org/2009/02/07/hidden-records-with-symfony-and-doctrine/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Doctrine admin generator + sfDoctrineGuardPlugin + disabled security = bug</title>
		<link>http://blog.fxposter.org/2009/01/31/doctrine-admin-generator-sfdoctrineguardplugin-disabled-security-bug/</link>
		<comments>http://blog.fxposter.org/2009/01/31/doctrine-admin-generator-sfdoctrineguardplugin-disabled-security-bug/#comments</comments>
		<pubDate>Sat, 31 Jan 2009 05:53:37 +0000</pubDate>
		<dc:creator>fxposter</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Работа]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[Symfony]]></category>
		<category><![CDATA[Symfony Plugins]]></category>

		<guid isPermaLink="false">http://blog.fxposter.org/?p=646</guid>
		<description><![CDATA[Есть в Symfony такая штука, как генератор админки на основании описанных моделей. Подробно о самом генераторе админки лучше читать здесь (кстати, я не понял, а про propel:generate-module теперь в Symfony Book не рассказывается ничего, что ли?). Я вчера столкнулся с багом, который ошибочно описал здесь, а затем правильно - здесь. Кто не хочет читать мой [...]]]></description>
			<content:encoded><![CDATA[<p>Есть в Symfony такая штука, как генератор админки на основании описанных моделей. Подробно о самом генераторе админки лучше читать <a href="http://www.symfony-project.org/book/1_2/14-Generators">здесь</a> (кстати, я не понял, а про propel:generate-module теперь в Symfony Book не рассказывается ничего, что ли?).</p>
<p>Я вчера столкнулся с багом, который ошибочно описал <a href="http://trac.symfony-project.org/ticket/5738">здесь</a>, а затем правильно - <a href="http://trac.symfony-project.org/ticket/5746">здесь</a>. Кто не хочет читать мой кривой английский - читаем дальше. Баг воспроизводится при наличии трех условий:</p>
<ol>
<li>класс myUser наседуется от sfGuardSecurityUser из плагина sfDoctrineGuardPlugin</li>
<li>для всего приложения (или только для модуля админки) отключена проверка безопасности (is_secure: off, в общем)</li>
<li>пользователь не авторизован</li>
</ol>
<p>В этом случае вы получите вот это сообщение:</p>
<blockquote><p>You don&#8217;t have the required permission to access this page.</p></blockquote>
<p>Решение описано в <a href="http://trac.symfony-project.org/ticket/5738">первом моём тикете</a>:</p>
<blockquote><p>Нужно заменить в файле &#8220;&lt;путь к библиотекам symfony&gt;/lib/plugins/sfDoctrinePlugin/data/generator/sfDoctrineModule/admin/template/actions/actions.class.php&#8221; (это шаблоны для генератора админки Doctrine) эти строки:</p>
<pre><code class="php">if (!$this-&gt;getUser()-&gt;hasCredential($this-&gt;configuration-&gt;getCredentials($this-&gt;getActionName())))
{
  $this-&gt;forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
}</code></pre>
<p>на эти:</p>
<pre><code class="php">credentials = $this-&gt;configuration-&gt;getCredentials($this-&gt;getActionName());
if (!empty($credentials) &amp;&amp; !$this-&gt;getUser()-&gt;hasCredential($credentials))
{
  $this-&gt;forward(sfConfig::get('sf_secure_module'), sfConfig::get('sf_secure_action'));
}</code></pre>
</blockquote>
<p>Баг некритичный, так что можно, в принципе, от него не избавляться, а подождать, пока изменения внесут в главный репозиторий. Просто если встретите его - не удивляйтесь. Лично я долго не мог понять - почему у меня не работает is_secure: off.</p>
]]></content:encoded>
			<wfw:commentRss>http://blog.fxposter.org/2009/01/31/doctrine-admin-generator-sfdoctrineguardplugin-disabled-security-bug/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>Doctrine</title>
		<link>http://blog.fxposter.org/2009/01/31/doctrine/</link>
		<comments>http://blog.fxposter.org/2009/01/31/doctrine/#comments</comments>
		<pubDate>Fri, 30 Jan 2009 23:33:21 +0000</pubDate>
		<dc:creator>fxposter</dc:creator>
				<category><![CDATA[Web]]></category>
		<category><![CDATA[Программирование]]></category>
		<category><![CDATA[Работа]]></category>
		<category><![CDATA[Doctrine]]></category>
		<category><![CDATA[Symfony]]></category>

		<guid isPermaLink="false">http://blog.fxposter.org/?p=639</guid>
		<description><![CDATA[Последнюю неделю работаю с Symfony 1.2 и Doctrine. Так вот, если раньше я хвалил Doctrine, то теперь&#8230; В общем, после нескольких дней работы с ней захотелось плеваться&#8230; На первый взгляд всё замечательно, но как только начинаешь копать глубже начинается ужас. Мне очень не хочется рассказывать про эти баги и недоделки (причина банальна - просто лень [...]]]></description>
			<content:encoded><![CDATA[<p>Последнюю неделю работаю с <a href="http://www.symfony-project.org/">Symfony 1.2</a> и <a href="http://www.doctrine-project.org/">Doctrine</a>. Так вот, если раньше я <a href="http://blog.fxposter.org/2008/02/06/propel/">хвалил Doctrine</a>, то теперь&#8230; В общем, после нескольких дней работы с ней захотелось плеваться&#8230; На первый взгляд всё замечательно, но как только начинаешь копать глубже начинается ужас. Мне очень не хочется рассказывать про эти баги и недоделки (причина банальна - просто лень вспоминать, выискивать по истории icq/jabber о всех багах, которые я нашел). У меня было не очень хорошее впечатление о Propel, но, по крайней мере, когда я работал с ним, у меня была уверенность, что всё будет работать и ничего не сломается, если я что-то добавлю/изменю. С Doctrine такой уверенности лично у меня нет (хотя коллеги тут подсказывают, что всё наладится :)) - такое впечатление, что &#8220;еще чуть-чуть&#8221; - и все развалится. Это мнение исключительно субьективное, обьяснять я его не буду, если кто-то что-то желает узнать - прошу в icq/jabber, все контакты есть в сайдбаре моего блога.</p>
<p>Напоследок всё-же хочется привести пример одной из &#8220;недоделок&#8221;, которую при хорошем коде можно было бы легко исправить:</p>
<p>Имеем следующий код:</p>
<pre><code class="php">// Category has many Posts
$category = Doctrine::getTable('Category')-&gt;find(1);
$posts = $category-&gt;Posts;</code></pre>
<p>Задача состоит в том, чтобы в $posts были посты, отсортированным по какому-нибудь полю бд. Причем, естественно, хочется, чтобы посты были отсортированы не только про вот такой их выборке через LazyLoading, но и при Eager Loading, т.е.:</p>
<pre><code class="php">$category = Doctrine::getTable('Category')-&gt;createQuery('c')-&gt;leftJoin('c.Posts')-&gt;fetchOne(); // выборка всех данных одним запросом
$posts = $category-&gt;Posts; // запроса к бд не проиходит</code></pre>
<p>На самом деле, про Eager Loading я с самого начала не думал, но, я думаю, со мной никто спорить не будет, что если сортировка должна быть в одном случае - она обязанна быть и во втором.</p>
<p>Так вот - эту проблему в доктрине приходится решать вручную для каждого поля, причем кешование данных тоже приходится хендлить самому.</p>
<p>При вызове свойства <code class="php">$category-&gt;Posts</code>, будет вызываться метод <code class="php">$category-&gt;getPosts()</code>, который мы и будем переопределять:</p>
<pre><code class="php">class Category extends BaseCategory
{
  public function getPosts() // в идеале сюда должен передаваться параметр $load, но пока о нем забудем
  {
    return Doctrine::getTable('Post')
        -&gt;createQuery()
          -&gt;orderBy('column ASC')
          -&gt;where('category_id = ?', $this-&gt;id)
        -&gt;execute();
  }
}</code></pre>
<p>Красиво? Как по мне - не очень, т.к. несмотря на наличие уже указанной связи - использовать её я не могу, приходится строить query заново.</p>
<p>Возможно, связью я воспользоваться могу, об этом нужно подумать&#8230; Пока писал пост пришла в голову идея, но проверять лень.</p>
<p>Запускаем. Пробуем:</p>
<pre><code class="php">$category = Doctrine::getTable('Category')-&gt;find(1);
$posts = $category-&gt;Posts;
$posts = $category-&gt;Posts;
$posts = $category-&gt;Posts;
$posts = $category-&gt;Posts;</code></pre>
<p>И что мы видим? 5 запросов к бд. Хмм. Убираем метод <code class="php">getPosts</code>. Запускаем тот же код. В итоге - два запроса. Я, конечно, понимаю, что это я такой плохой и лентяй еще к тому же, но ведь можно было сделать подобное &#8220;кеширование&#8221; для связей автоматически (если кто знает, как это сделать - дайте знать, походя дофига времени по кодам доктрины я не нашел место, где это можно сделать). Ладно, сделаем &#8220;кеширование&#8221;:</p>
<pre><code class="php">public function getPosts()
{
  if (!isset($this-&gt;_references['Posts']))
    $this-&gt;_references['Posts'] = Doctrine::getTable('Post')
        -&gt;createQuery()
          -&gt;orderBy('column ASC')
          -&gt;where('category_id = ?', $this-&gt;id)
        -&gt;execute();
  return $this-&gt;_references['Posts'];
}</code></pre>
<p>Запускаем уже запомнившийся нам код. Ну слава богу - два запроса.</p>
<p>На самом деле нужно еще кое-что переопределять (типа <code>Category::loadReference</code>), но я на это забил, т.к. вряд ли я буду вызывать этот метод вручную. А его вызов системой мне, вроде бы не встречался.</p>
<p>Ну, вроде более-менее готово. По крайней мере - можно пользоваться, а нам только это и нужно.</p>
<p>Теперь об Eager Loading. О нем я сам даже и не вспоминал, т.к. у меня не было ни одной идеи - как его реализовать (нужно было опять копаться в исходниках Doctrine, а это мне уже, порядком, поднадоело). Как вдруг, откуда ни возьмись, появился пост в блоге доктрины: <a href="http://www.doctrine-project.org/blog/cookbook-recipe-relation-dql-behavior">Cookbook Recipe: Relation DQL Behavior</a>. Советую прочесть его и комментарии (благо, их там немного). Вы увидите в комментариях и меня, говорящим спасибо за Eager Loading, а также указавшим на то, что в случае Lazy Loading-а решение не работает. На что мне ответили:</p>
<blockquote><p>You&#8217;re right the above won&#8217;t work, but you shouldn&#8217;t ever be doing that :) You should always load your data through full DQL queries and avoid lazy loading data</p></blockquote>
<p>Ага. Благодарю покорно. Зачем тогда вообще делать Lazy Loading, если его не нужно юзать. Может, мне банально удобнее написать <code class="php">$category-&gt;Posts</code> и получить посты тогда, когда это реально нужно, а не при выборке категории. Да, я получу два запроса вместо одного, ну и что? Это не O(n), а я не привык оптимизировать то, что нормально работает и так.</p>
<p>И напоследок: на самом деле проблему вполне можно было бы решить путем наследования от класса связи и его расширения. Однако сделать это в Doctrine сне не судилось, т.к. в отношениях hasOne, hasMany намертво зашиты стандартные классы связей Doctrine, а жаль. Такие связи - это плохо, т.к. они статичны и не способствуют хорошему расширению системы. С другой стороны - не мне критиковать jwage, самбы я вряд ли сделал что-то подобное, по крайней мере сейчас.</p>
<p><em><strong>PS</strong>. Долго думал, как назвать пост. Так и не придумал&#8230; :(</em></p>
]]></content:encoded>
			<wfw:commentRss>http://blog.fxposter.org/2009/01/31/doctrine/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
	</channel>
</rss>

<!-- Performance optimized by W3 Total Cache. Learn more: http://www.w3-edge.com/wordpress-plugins/

Page Caching using disk: enhanced

Served from: blog.fxposter.org @ 2012-02-10 05:51:39 -->
