<?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>hudson@work &#187; doctrine</title>
	<atom:link href="http://hudson.su/tag/doctrine/feed/" rel="self" type="application/rss+xml" />
	<link>http://hudson.su</link>
	<description>статьи о web-разработке, менеджменте IT проектов и контроле качества</description>
	<lastBuildDate>Fri, 20 Jan 2012 13:15:39 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	
		<item>
		<title>Doctrine2: эмулируем timestampable behavior через lifecycle callback</title>
		<link>http://hudson.su/2011/04/22/doctrine2-emulate-timestampable-behavior-via-lifecycle-callback/</link>
		<comments>http://hudson.su/2011/04/22/doctrine2-emulate-timestampable-behavior-via-lifecycle-callback/#comments</comments>
		<pubDate>Fri, 22 Apr 2011 05:16:38 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[doctrine2]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[ORM]]></category>
		<category><![CDATA[snippet]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1917</guid>
		<description><![CDATA[Ниже рассмотрим простой, но имхо нужный пример по реализации аналога timestampable в Doctrine2. Схема: Vendor\MyBundle\Entity\User: type: entity table: user id: id: type: bigint generator: strategy: AUTO fields: # ... updated: type: datetime nullable: true created: type: datetime nullable: true Далее полагаем что сущности сгенерированы. При создании новой сущности вызывается констуктор, поэтому даты устанавливаем там: &#60;?php [...]]]></description>
			<content:encoded><![CDATA[<p>Ниже рассмотрим простой, но имхо нужный пример по реализации аналога timestampable в Doctrine2.</p>
<p><span id="more-1917"></span><br />
Схема:</p>

<div class="wp_syntax"><div class="code"><pre class="yaml" style="font-family:monospace;">Vendor\MyBundle\Entity\User:
  type: entity
  table: user
  id:
    id:
      type: bigint
      generator:
        strategy: AUTO
  fields:
    # ...
    updated:
      type: datetime
      nullable: true
    created:
      type: datetime
      nullable: true</pre></div></div>

<p>Далее полагаем что сущности сгенерированы. При создании новой сущности вызывается констуктор, поэтому даты устанавливаем там:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #666666; font-style: italic;">// Vendor/MyBundle/Entity/User.php</span>
<span style="color: #000000; font-weight: bold;">class</span> User<span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// ....    </span>
    <span style="color: #009933; font-style: italic;">/**
     * @var datetime $created
     */</span>
    <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000088;">$created</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #009933; font-style: italic;">/**
     * @var datetime $updated
     */</span>
    <span style="color: #000000; font-weight: bold;">private</span> <span style="color: #000088;">$updated</span><span style="color: #339933;">;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> __construct<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">created</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">updated</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> \DateTime<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;now&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #666666; font-style: italic;">// ....    </span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>Для того чтобы при обновлении сущности updated также обновлялся бы, нам надо воспользоваться lifecycle callback. Модифицируем схему и генерим по ней модель:</p>

<div class="wp_syntax"><div class="code"><pre class="yaml" style="font-family:monospace;">Vendor\MyBundle\Entity\User:
  type: entity
  table: user
  # ...
  lifecycleCallbacks:
    preUpdate: [ preUpdate ]</pre></div></div>

<p>После генерации у вас в классе сущности появится метод preUpdate, который надо слегка модифицировать:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #000000; font-weight: bold;">&lt;?php</span>
<span style="color: #666666; font-style: italic;">// Vendor/MyBundle/Entity/User.php</span>
<span style="color: #000000; font-weight: bold;">class</span> User<span style="color: #009900;">&#123;</span>
    <span style="color: #666666; font-style: italic;">// ....    </span>
    <span style="color: #009933; font-style: italic;">/**
     * @orm:preUpdate
     */</span>
    <span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> preUpdate<span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #009900;">&#123;</span>
        <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">updated</span> <span style="color: #339933;">=</span> <span style="color: #000000; font-weight: bold;">new</span> \DateTime<span style="color: #009900;">&#40;</span><span style="color: #0000ff;">&quot;now&quot;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
    <span style="color: #009900;">&#125;</span>
    <span style="color: #666666; font-style: italic;">// ....    </span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>Берем и пользуемся )</p>
<blockquote><p>Источник: <a href="http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell" target="_blank">http://www.doctrine-project.org/blog/doctrine2-behaviours-nutshell</a>. Посмотрите, там еще много интересного.</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2011/04/22/doctrine2-emulate-timestampable-behavior-via-lifecycle-callback/feed/</wfw:commentRss>
		<slash:comments>5</slash:comments>
		</item>
		<item>
		<title>MySQL оптимизация: ORDER BY RAND()</title>
		<link>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/</link>
		<comments>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/#comments</comments>
		<pubDate>Thu, 16 Sep 2010 09:13:03 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[optimization]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1600</guid>
		<description><![CDATA[Лирическое вступление: Довольно часто у нас возникает потребность выборки случайных данных из mysql базы данных. Как правило времени нет и используется самая простая конструкция вида SELECT [что-то] FROM [где-то] WHERE [то и сё] ORDER BY RAND(). Эта конструкция работает на ура. Но вот прототип выезжает на продуктовые сервера и такой милый сердцу запрос вдруг начинает [...]]]></description>
			<content:encoded><![CDATA[<p>Лирическое вступление:</p>
<p>Довольно часто у нас возникает потребность выборки случайных данных из mysql базы данных. Как правило времени нет и используется самая простая конструкция вида SELECT [что-то] FROM [где-то] WHERE [то и сё] ORDER BY RAND(). Эта конструкция работает на ура. Но вот прототип выезжает на продуктовые сервера и такой милый сердцу запрос вдруг начинает выпадать в топы медленных логов. Ниже будут рассмотрены несколько возможностей для оптимизации этого запроса по нарастанию их эффективности:</p>
<p><span id="more-1600"></span>В первых примерах мы полагаем что ID стартует с номера 1 и в ID нет разрывов между 1 и максимальным ID.</p>
<h2>#1. Передать всю работу в приложение</h2>
<p>Мы можем тупо слить всю работу по определению случайного номера в приложение.</p>
<pre>SELECT MAX(id) FROM random;
## генерируем случайный ID в приложении
SELECT name FROM random WHERE id = id_из_приложения</pre>
<p>Так как <code>MAX(id) == COUNT(id)</code> нам всеголишь нужно сгенерировать случайное число между 1 и MAX(id), передать его в запрос к БД и получить свою случайную строку.</p>
<p>Первый SELECT у нас фактически NO-OP и он оптимизирован по самое &#8220;не балуйся&#8221;. Второй запрос это eq_ref по константе и он тоже очень быстр.</p>
<h2>#2. Делаем всю работу на стороне базы данных</h2>
<p>Но настолько ли необходимо делать случайные выборки через приложение? Может стоит вынести &#8220;грязную&#8221; работу на сторону базы данных (<em>прим. пер.: на самом деле первый способ это практически сферический конь в вакууме &#8211; большинство реальных задач будет выходить за рамки его применимости</em>)</p>
<pre># генерируем случайный ID
&gt; SELECT RAND() * MAX(id) FROM random;
+------------------+
| RAND() * MAX(id) |
+------------------+
|  689.37582507297 |
+------------------+</pre>
<p>упс, это число типа double, а нам нужен int</p>
<pre>&gt; SELECT CEIL(RAND() * MAX(id)) FROM random;
+-------------------------+
| CEIL(RAND() * MAX(id))  |
+-------------------------+
|                1000000  |
+-------------------------+
</pre>
<p>уже лучше, но что насчет скорости?</p>
<pre>&gt; EXPLAIN
SELECT CEIL(RAND() * MAX(id)) FROM random;
+----+-------------+-------+-------+---------+-------------+
| id | select_type | table | type  |   rows  | Extra       |
+----+-------------+-------+-------+---------+-------------+
|  1 | SIMPLE      |random | index | 1000000 | Using index |
+----+-------------+-------+-------+---------+-------------+
</pre>
<p>index scan? похоже мы потеряли оптимизацию MAX()</p>
<pre>&gt; EXPLAIN
SELECT CEIL(RAND() * (SELECT MAX(id) FROM random));
+----+-------------+-------+------+------+------------------------------+
| id | select_type | table | type | rows | Extra                        |
+----+-------------+-------+------+------+------------------------------+
|  1 | PRIMARY     | NULL  | NULL | NULL | No tables used               |
|  2 | SUBQUERY    | NULL  | NULL | NULL | Select tables optimized away |
+----+-------------+-------+------+------+------------------------------+
</pre>
<p>Ура! Простой подзапрос возвращает нашу потерянную производительность!</p>
<p>Окей, теперь мы знаем как сгенерировать случайный ID, теперь надо получить и соответствующую ему строку:</p>
<pre>&gt; EXPLAIN
SELECT name
FROM random
WHERE id = (SELECT CEIL(RAND() *
(SELECT MAX(id)
FROM random));
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
| id | select_type | table  | type | possible_keys | key  | key_len | ref  | rows    | Extra                        |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
|  1 | PRIMARY     | random | ALL  | NULL          | NULL | NULL    | NULL | 1000000 | Using where                  |
|  3 | SUBQUERY    | NULL   | NULL | NULL          | NULL | NULL    | NULL |    NULL | Select tables optimized away |
+----+-------------+--------+------+---------------+------+---------+------+---------+------------------------------+
&gt; show warnings;
+-------+------+------------------------------------------+
| Level | Code | Message                                  |
+-------+------+------------------------------------------+
| Note  | 1249 | Select 2 was reduced during optimization |
+-------+------+------------------------------------------+</pre>
<p>Нет нет нет! Не идите этим путем! Это самый очевидный, но также самый неверный способ! Почему? А вот почему: SELECT в условии WHERE будет выполняться <strong>для каждой</strong> строки! А это число может составлять от 0 до 4091 строки, в зависимости от того насколько вы будете удачливы.</p>
<p>Нам нужен такой способ выборки, при котором мы будем уверены что случайный номер генерируется только однажды:</p>
<pre>SELECT name
FROM random
JOIN
(
  SELECT CEIL(RAND() * (SELECT MAX(id) FROM random)) AS id
) AS r2
USING (id);
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table      | type   | rows | Extra                        |
+----+-------------+------------+--------+------+------------------------------+
|  1 | PRIMARY     |            | system |    1 |                              |
|  1 | PRIMARY     | random     | const  |    1 |                              |
|  2 | DERIVED     | NULL       | NULL   | NULL | No tables used               |
|  3 | SUBQUERY    | NULL       | NULL   | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+</pre>
<p>Внутренний SELECT генерирует константу в TEMPORARY таблицу и JOIN выбирает одну строку. Великолепно! Нет сортировок, нет вмешательства приложения. Все части запроса оптимизированы.</p>
<h2>#3. Добавляем &#8220;дыры&#8221; в primary key</h2>
<p>Для того чтобы сделать наше предыдущее решение более универсальным, нам нужно учесть возможность &#8220;дыр&#8221; в ID (как если бы вы удалили некоторые строки).</p>
<pre>SELECT name
FROM random AS r1 JOIN
(
 SELECT (RAND() * (SELECT MAX(id) FROM random)) AS id
)
AS r2
WHERE r1.id &gt;= r2.id
ORDER BY r1.id ASC
LIMIT 1;
+----+-------------+------------+--------+------+------------------------------+
| id | select_type | table      | type   | rows | Extra                        |
+----+-------------+------------+--------+------+------------------------------+
|  1 | PRIMARY     |            | system |    1 |                              |
|  1 | PRIMARY     | r1         | range  |  689 | Using where                  |
|  2 | DERIVED     | NULL       | NULL   | NULL | No tables used               |
|  3 | SUBQUERY    | NULL       | NULL   | NULL | Select tables optimized away |
+----+-------------+------------+--------+------+------------------------------+</pre>
<p>Теперь JOIN добавляет все ID который больше или равны нашему случайному значению и мы выбираем ближайшего соседа, если равенство не возможно. НО как только одна строка найдена мы останавливаемся (LIMIT 1). И мы читаем строки в соответствии с индексом (ORDER BY id ASC). Так как мы используем знак &#8220;&gt;=&#8221; вместо строгого равенства &#8220;=&#8221; мы можем избавиться от CEIL и получить тот же резудьтат при немного меньших затратах.</p>
<h2>#4. Равномерное распределение</h2>
<p>Поскольку распределение ID не равномерно, наша выборка на самом деле стала не совсем случайной (<em>прим. пер.: насколько я понимаю, чем больше &#8220;больших&#8221; дыр в ID тем менее равномерно распределение и тем &#8220;менее случайной&#8221; будет выборка</em>).</p>
<pre>&gt; select * from holes;
+----+----------------------------------+----------+
| id | name                             | accesses |
+----+----------------------------------+----------+
|  1 | d12b2551c6cb7d7a64e40221569a8571 |      107 |
|  2 | f82ad6f29c9a680d7873d1bef822e3e9 |       50 |
|  4 | 9da1ed7dbbdcc6ec90d6cb139521f14a |      132 |
|  8 | 677a196206d93cdf18c3744905b94f73 |      230 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa |      481 |
+----+----------------------------------+----------+</pre>
<p>Функция RAND генерирует ID от 9 до 15, которые попадают в &#8220;дыру&#8221; перед 16 и как следствие, 16 выбирается намного чаще чем остальные.</p>
<p>Для этой проблемы не существует нормального решения, но если ваши данные более-менее постоянны, вы можете добавить таблицу для маппинга номера строки с ее ID:</p>
<pre>&gt; create table holes_map (
&gt;   row_id int not NULL primary key,
&gt;   random_id int not null
&gt; );
&gt; SET @id = 0;
&gt; INSERT INTO holes_map SELECT @id := @id + 1, id FROM holes;
&gt; select * from holes_map;
+--------+-----------+
| row_id | random_id |
+--------+-----------+
|      1 |         1 |
|      2 |         2 |
|      3 |         4 |
|      4 |         8 |
|      5 |        16 |
+--------+-----------+</pre>
<p>Идентификатор row_id теперь не содержит дыр и мы опять можем воспользоваться нашим запросом:</p>
<pre>SELECT name
FROM holes
JOIN (
  SELECT r1.random_id
  FROM holes_map AS r1
  JOIN (
    SELECT (RAND() * (SELECT MAX(row_id) FROM holes_map)
  ) AS row_id
) AS r2
WHERE r1.row_id &gt;= r2.row_id
ORDER BY r1.row_id ASC
LIMIT 1) as rows ON (id = random_id);</pre>
<p>После 1000 попыток опять имеем равномерное распределение:</p>
<pre>&gt; select * from holes;
+----+----------------------------------+----------+
| id | name                             | accesses |
+----+----------------------------------+----------+
|  1 | d12b2551c6cb7d7a64e40221569a8571 |      222 |
|  2 | f82ad6f29c9a680d7873d1bef822e3e9 |      187 |
|  4 | 9da1ed7dbbdcc6ec90d6cb139521f14a |      195 |
|  8 | 677a196206d93cdf18c3744905b94f73 |      207 |
| 16 | b7556d8ed40587a33dc5c449ae0345aa |      189 |
+----+----------------------------------+----------+</pre>
<h2>#5. Обслуживание таблицы Holes при помощи триггеров</h2>
<p>Давайте подготовим таблицы как описано ниже:</p>
<pre>DROP TABLE IF EXISTS r2;
CREATE TABLE r2 (
  id SERIAL,
  name VARCHAR(32) NOT NULL UNIQUE
);

DROP TABLE IF EXISTS r2_equi_dist;
CREATE TABLE r2_equi_dist (
  id SERIAL,
  r2_id bigint unsigned NOT NULL UNIQUE
);</pre>
<p>Когда мы что-то меняем в r2, мы хотим чтобы r2_equi_dist также изменялась.</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tai_r2$$
CREATE TRIGGER tai_r2
AFTER INSERT ON r2 FOR EACH ROW
BEGIN
DECLARE m BIGINT UNSIGNED DEFAULT 1;

SELECT MAX(id) + 1 FROM r2_equi_dist INTO m;
SELECT IFNULL(m, 1) INTO m;
INSERT INTO r2_equi_dist (id, r2_id) VALUES (m, NEW.id);
END$$
DELIMITER ;

DELETE FROM r2;

INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );
INSERT INTO r2 VALUES ( NULL, MD5(RAND()) );</pre>
<pre>SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  2 | a09a3959d68187ce48f4fe7e388926a9 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
|  4 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     2 |
|  3 |     3 |
|  4 |     4 |
+----+-------+</pre>
<p>INSERT весьма прост. При DELETE же мы хотим поддерживать equi-dist-id в состоянии &#8220;без дыр&#8221;:</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tad_r2$$
CREATE TRIGGER tad_r2
AFTER DELETE ON r2 FOR EACH ROW
BEGIN
DELETE FROM r2_equi_dist WHERE r2_id = OLD.id;
UPDATE r2_equi_dist SET id = id - 1 WHERE r2_id &gt; OLD.id;
END$$
DELIMITER ;</pre>
<pre>DELETE FROM r2 WHERE id = 2;

SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
|  4 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     3 |
|  3 |     4 |
+----+-------+</pre>
<p>UPDATE также прост. Мы должны обслужить лишь Foreign Key constraint:</p>
<pre>DELIMITER $$
DROP TRIGGER IF EXISTS tau_r2$$
CREATE TRIGGER tau_r2
AFTER UPDATE ON r2 FOR EACH ROW
BEGIN
UPDATE r2_equi_dist SET r2_id = NEW.id WHERE r2_id = OLD.id;
END$$
DELIMITER ;</pre>
<pre>UPDATE r2 SET id = 25 WHERE id = 4;

SELECT * FROM r2;
+----+----------------------------------+
| id | name                             |
+----+----------------------------------+
|  1 | 8b4cf277a3343cdefbe19aa4dabc40e1 |
|  3 | 4e1897cd6d326f8079108292376fa7d5 |
| 25 | 29a5e3ed838db497aa330878920ec01b |
+----+----------------------------------+
SELECT * FROM r2_equi_dist;
+----+-------+
| id | r2_id |
+----+-------+
|  1 |     1 |
|  2 |     3 |
|  3 |    25 |
+----+-------+</pre>
<h2>#6. Несколько случайных строк за один раз</h2>
<p>Если вы хотите получить более одной случайной строки за раз вы можете:</p>
<ol>
<li>Выполнить запрос несколько раз</li>
<li>Написать хранимую процедуру, которая выполняет запрос и хранит результат во временной таблице</li>
<li>Выполнить UNION наконец</li>
</ol>
<p><strong>Хранимая процедура:</strong></p>
<p>Хранимая процедура позволяет вам использовать структуры, известные в любом популярном языке программирования:</p>
<ol>
<li>Циклы</li>
<li>Управляющие конструкции</li>
<li>Процедуры</li>
<li>&#8230;</li>
</ol>
<p>Для нашей задачи нам нужен только цикл LOOP:</p>
<pre>DELIMITER $$
DROP PROCEDURE IF EXISTS get_rands$$
CREATE PROCEDURE get_rands(IN cnt INT)
BEGIN
DROP TEMPORARY TABLE IF EXISTS rands;
CREATE TEMPORARY TABLE rands ( rand_id INT );

loop_me: LOOP
IF cnt &amp;lt; 1
  THEN LEAVE loop_me;
END IF;
INSERT INTO rands
  SELECT r1.id
  FROM random AS r1
  JOIN (
    SELECT (RAND() * (SELECT MAX(id) FROM random)) AS id
  ) AS r2
  WHERE r1.id &gt;= r2.id
ORDER BY r1.id ASC
LIMIT 1;

SET cnt = cnt - 1;
END LOOP loop_me;
END$$
DELIMITER ;</pre>
<pre>CALL get_rands(4);
SELECT * FROM rands;
+---------+
| rand_id |
+---------+
|  133716 |
|  702643 |
|  112066 |
|  452400 |
+---------+</pre>
<p>Оставляю в качестве заданий читателю следующие задачки:</p>
<ol>
<li>Динамически составлять запрос, генерируя название временной таблицы (спасибо 2 Evgeny Babin)</li>
<li>Используя UNIQUE index отлавливать нарушения UNIQUE key для удаления возможных дублей.</li>
</ol>
<h2><strong>#7. Быстродействие</strong></h2>
<p>Чтоже стало с быстродействием? У нас есть 3 различные запроса, решающие нашу проблему:</p>
<ol>
<li><code>Q1. ORDER BY RAND()</code></li>
<li><code>Q2. RAND() * MAX(ID)</code></li>
<li><code>Q3. RAND() * MAX(ID) + ORDER BY ID</code></li>
</ol>
<p>Q1 можно оценить как N * log2(N), Q2 и Q3 что-то около константы.</p>
<p>Чтобы получить реальные значения мы провели несколько тестов с числом строк от 100 до миллиона и выполнили каждый запрос 1000 раз.</p>
<pre>    100        1.000      10.000     100.000    1.000.000
Q1  0:00.718s  0:02.092s  0:18.684s  2:59.081s  58:20.000s
Q2  0:00.519s  0:00.607s  0:00.614s  0:00.628s   0:00.637s
Q3  0:00.570s  0:00.607s  0:00.614s  0:00.628s   0:00.637s</pre>
<p>Как вы можете видеть, простой ORDER BY RAND() оптимизирован для выполнения при количестве строк не более 100.</p>
<p>По мотивам <a href="http://jan.kneschke.de/projects/mysql/order-by-rand/" target="blank">http://jan.kneschke.de/projects/mysql/order-by-rand/</a></p>
<p>Coding with fun!</p>
<blockquote><p>P.S. Сам я на практике использовал пока лишь 3е решение ))</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/09/16/mysql-optimizaciya-order-by-rand/feed/</wfw:commentRss>
		<slash:comments>20</slash:comments>
		</item>
		<item>
		<title>Doctrine forms &#8211; редактирование i18n контента</title>
		<link>http://hudson.su/2010/06/04/doctrine-forms-editing-i18n-content/</link>
		<comments>http://hudson.su/2010/06/04/doctrine-forms-editing-i18n-content/#comments</comments>
		<pubDate>Fri, 04 Jun 2010 07:44:23 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1549</guid>
		<description><![CDATA[Данная заметка не открывает ничего нового, тем не менее, подобные вопросы постоянно возникают, в том числе и у меня. Суть проблемы: есть i18n таблица (вернее пара таблиц, например content и content_translation). Хочется быстро и просто сделать в админке редактирование данных для нескольких языков. Оказывается, Doctrine form &#8220;из коробки&#8221; это умеет. Необходимо лишь выполнить несложное конфигурирование: [...]]]></description>
			<content:encoded><![CDATA[<p>Данная заметка не открывает ничего нового, тем не менее, подобные вопросы постоянно возникают, в том числе и у меня.</p>
<p><span id="more-1549"></span>Суть проблемы: есть i18n таблица (вернее пара таблиц, например content и content_translation). Хочется быстро и просто сделать в админке редактирование данных для нескольких языков.</p>
<p>Оказывается, Doctrine form &#8220;из коробки&#8221; это умеет. Необходимо лишь выполнить несложное конфигурирование:</p>
<pre>&lt;?php
class ContentForm extends BaseContentForm
{
  public function configure()
  {
    // ...
    $this-&gt;embedI18n(array('en', 'ru'));
    $this-&gt;widgetSchema-&gt;setLabel('en', 'Английская версия');
    $this-&gt;widgetSchema-&gt;setLabel('ru', 'Русская версия');
    // ...
  }
}
?&gt;
</pre>
<p>Этот прием описан в Jobeet, день 19: <a href="http://www.symfony-project.org/jobeet/1_4/Doctrine/en/19#chapter_19_sub_admin_generator" target="_blank">http://www.symfony-project.org/jobeet/1_4/Doctrine/en/19#chapter_19_sub_admin_generator</a>, но, похоже, мало кто дочитывает до этого места.</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/06/04/doctrine-forms-editing-i18n-content/feed/</wfw:commentRss>
		<slash:comments>11</slash:comments>
		</item>
		<item>
		<title>Встраиваем форму с sfDoctrineJCroppablePlugin &#8211; решение проблем</title>
		<link>http://hudson.su/2010/05/03/embed-form-with-sfdoctrinejcroppableplugin/</link>
		<comments>http://hudson.su/2010/05/03/embed-form-with-sfdoctrinejcroppableplugin/#comments</comments>
		<pubDate>Mon, 03 May 2010 12:54:27 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony forms]]></category>
		<category><![CDATA[symfony plugins]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1410</guid>
		<description><![CDATA[Вы уже сталкивались в своей практике с замечательным плагином для symfony 1.2-1.4 &#8211; sfDoctrineJCroppablePlugin? Если нет, то в двух словах поясню что это такое. Итак, sfDoctrineJCroppablePlugin (он устанавливается в паре с еще одним очень полезным плагином &#8211; sfImageTransformPlugin) позволяет быстро и просто добавить в вашу форму аплоад картинки и прицепить к нему jquery-плагин jcroppable. Последний [...]]]></description>
			<content:encoded><![CDATA[<p>Вы уже сталкивались в своей практике с замечательным плагином для symfony 1.2-1.4 &#8211; <code>sfDoctrineJCroppablePlugin</code>? Если нет, то в двух словах поясню что это такое. Итак, <code>sfDoctrineJCroppablePlugin</code> (он устанавливается в паре с еще одним очень полезным плагином &#8211; <code>sfImageTransformPlugin</code>) позволяет быстро и просто добавить в вашу форму аплоад картинки и прицепить к нему jquery-плагин jcroppable. Последний позволяет выбирать область на изображении, которая будет использоваться для превью (это нужно тогда, когда под превью отведено по дизайну строго определенное место, например 100х100 пискелей).</p>
<p>Итак, когда мы работаем с изображениями в рамках одной формы &#8211; все отлично. Но вот, нам потребовалось встроить (embed) наши картинки в другую форму (именно картинкИ, думаю с одной картинкой проблем не будет). Вот тут то и начинается веселье.</p>
<p><span id="more-1410"></span>Конкретно, веселье заключается в том, что <code>sfDoctrineJCroppable</code> не понимает что его встроили и считает, что каждое изображение отображается самостоятельно. Кроме того, неправильно определяются идентификаторы hidden полей с координатами углов (x1, y1, x2, y2). Как следствие &#8211; javascript ошибка и неработающий jcroppable (в остальном плагин вроде фунциклирует, но ставился он преимущественно из-за быстрой интеграции с jcroppable!).</p>
<p>Давайте покопаемся во внутренностях плагина, чтобы понять что там пошло не так.</p>
<p>Виджет формируется в классе <code>sfWidgetFormInputFileInputImageJCroppable</code>. Наше внимание должен привлечь метод <code>getJCropJS()</code>, который собственно и осуществляет рендеринг javascript-кода:</p>
<pre>// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php:128
private function getJCropJS() {
 $idStub = $this-&gt;getIdStub();
 $ratio = $this-&gt;getOption('image_ratio') ? 'aspectRatio: ' . $this-&gt;getOption('image_ratio') . ',' : '';
 $js = "
&lt;script language="Javascript"&gt;
 jQuery(document).ready(function(){
   jQuery('#{$idStub}_img').Jcrop({
     $ratio
     setSelect: [document.getElementById('{$idStub}_x1').value,
                 document.getElementById('{$idStub}_y1').value,
                 document.getElementById('{$idStub}_x2').value,
                 document.getElementById('{$idStub}_y2').value
     ],
     onChange: _jCropUpdateCoords" . ucfirst($idStub) . ",
     onSelect: _jCropUpdateCoords" . ucfirst($idStub) . "
 });
//...
</pre>
<p>Тут фигурирует вызов соседней функции getIdStub():</p>
<pre>//plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php:161
private function getIdStub() {
  $form = $this-&gt;getOption('form');
  $separator = '';
  $imageName = $this-&gt;getOption('image_field');
  $tableName = str_replace("[%s]", '', $form-&gt;getWidgetSchema()-&gt;getNameFormat());
  if ($form-&gt;getOption('embedded'))
  {
    $parentTableName = $form-&gt;getOption('parent_model')-&gt;getTable()-&gt;getTableName();
    $separator = 'embedded_' . $tableName . '_' . $tableName .
    '_' . $this-&gt;getOption('invoker')-&gt;getId();
    $idStub = $parentTableName . '_' . $separator . '_' . $imageName;
  }
  else
  {
    $idStub = $tableName . '_' . $imageName;
  }
  return $idStub;
}</pre>
<p>Уже интереснее! getIdStub() на основе некоей опции &#8220;embedded&#8221; обещает выдавать два различных результата. Казалось бы бери &#8211; да действуй. Ну чтоже, давайте подсунем оную опцию в наш виджет. Итак, класс создания коллекции фото:</p>
<pre>// lib/form/doctrine/PropertyPhotoCollectionForm.class.php
class PropertyPhotoCollectionForm extends sfForm
{
  public function configure()
  {
    if ( !$property = $this-&gt;getOption( "property" ) )
    {
      throw new InvalidArgumentException( "You must provide a property object." );
    }

    for ( $i = 0; $i &lt; $this-&gt;getOption( "size", 4 ); $i++ )
    {
      $property_photo = ( $property-&gt;PropertyPhoto[$i] ) ? $property-&gt;PropertyPhoto[$i] : new PropertyPhoto();
      $property_photo-&gt;PropertyCatalog = $property;
      $form = new PropertyPhotoForm(
        $property_photo,
        array(
          "embedded" =&gt; true,
          "parent_model" =&gt; new PropertyCatalog(),
          "embed_form_name" =&gt; "photos",
          "embed_id" =&gt; $i,
        )
      );
      $this-&gt;embedForm( $i, $form);
    }
  }
</pre>
<p>Отлично. Однако, обратите внимание, что помимо оции embedded я добавил еще parent_model, embedded_form_name, embed_id. Они появились в результате отладки, посему они будут фигурировать в коде, представленном ниже. Теперь надо модифицировать виджет, чтобы он с этими опциями не ронял приложение (в основном из-за parent_model):</p>
<pre>// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php
private function getIdStub()
{
  sfContext::getInstance()-&gt;getLogger()-&gt;debug( "[sfWidgetFormInputFileInputImageJCroppable::getIdStub]" );
  $form = $this-&gt;getOption('form');
  $separator = '';

  $imageName = $this-&gt;getOption( 'image_field' );
  $tableName = str_replace("[%s]", '', $form-&gt;getWidgetSchema()-&gt;getNameFormat());

  if ( $form-&gt;getOption( 'embedded' ) )
  {
    $parentTableName = $form-&gt;getOption('parent_model')-&gt;getTable()-&gt;getTableName();

    $idStub = sprintf(
      "%s_%s_%s_%s",
      $form-&gt;getOption('parent_model')-&gt;getTable()-&gt;getTableName(),
      $form-&gt;getOption('embed_form_name'),
      $form-&gt;getOption('embed_id'),
      $imageName
    );
  }
  else
  {
    $idStub = $tableName . '_' . $imageName;
  }

  return $idStub;
}</pre>
<p>Как видите, основные изменения коснулись ветвления <code>if ( $form-&gt;getOption( 'embedded' ) )</code> &#8211; как раз тут должны создаваться IDs для встроенных форм. Однако, не все так безоблачно как хотелось бы. На самом деле перестал корректно работать FileEditable виджет, поэтому я тупо закомментировал генерацию delete опции в методе <code>render</code> в ветке для embedded формы. Благо она не сильно была нужна:</p>
<pre>// plugins/sfDoctrineJCroppablePlugin/lib/widget/sfWidgetFormInputFileInputImageJCroppable.class.php
  public function render($name, $value = null, $attributes = array(), $errors = array())
  {
    //...
    // гдето в районе строки 90
    if ( $form-&gt;getOption('embedded', false) &amp;&amp; $form-&gt;getOption('parent_model', false) )
    {
      //$delete = $form-&gt;getOption('parent_model')-&gt;getDeleteLinkFor($object); // Эту строку и закомментировали
      $deleteLabel = '';
    }
    else
    {
      $delete = $this-&gt;renderTag('input', array_merge(array('type' =&gt; 'checkbox', 'name' =&gt; $deleteName), $attributes));
      $deleteLabel = $this-&gt;renderContentTag('label', $this-&gt;getOption('delete_label'), array_merge(array('for' =&gt; $this-&gt;generateId($deleteName))));
    }
    // ...
  }</pre>
<p>Итого, имеем кривоватую подпорку для того чтобы получить нормально работающий виджет. Я запросил разработчика где можно запостить решение или описать проблему, но он не ответил. Жаль. По возможности старайтесь избегать встраивания форм с JCroppable, если не хотите писать полноценный патч <img src='http://hudson.su/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<p>Ну и собственно результат выглядит примерно так:</p>
<p><a href="http://hudson.su/files/2010/05/jcroppable-picture.png" target="_blank"><img class="alignnone size-medium wp-image-1431" src="http://hudson.su/files/2010/05/jcroppable-picture-300x203.png" alt="" width="300" height="203" /></a></p>
<p>Успехов!</p>
<p>P.S. Если найдете более элегантное решение &#8211; маякните плиз!</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/05/03/embed-form-with-sfdoctrinejcroppableplugin/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Добавляем в форму sfGuardUser связь many-to-many</title>
		<link>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/</link>
		<comments>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/#comments</comments>
		<pubDate>Thu, 08 Apr 2010 04:46:38 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[symfony forms]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1359</guid>
		<description><![CDATA[В данной заметке я расскажу об использованном мной способе добавления к sfGuardUser связи many-to-many. Начальные условия. Имеется symfony 1.4 проект с установленным sfDoctrineGuardPlugin. Цели. Требуется реализовать для пользователя список ежедневных задач (памятка). Решение. Схема. Создадим в БД следующие таблицы: # таблица ежедневных задач CREATE TABLE IF NOT EXISTS `daily_task` ( `id` int(11) NOT NULL AUTO_INCREMENT, [...]]]></description>
			<content:encoded><![CDATA[<p>В данной заметке я расскажу об использованном мной способе добавления к sfGuardUser связи many-to-many.<br />
<span id="more-1359"></span><br />
<h2>Начальные условия.</h2>
<p>Имеется symfony 1.4 проект с установленным sfDoctrineGuardPlugin.</p>
<h2>Цели.</h2>
<p>Требуется реализовать для пользователя список ежедневных задач (памятка).</p>
<h2>Решение.</h2>
<h3>Схема.</h3>
<p>Создадим в БД следующие таблицы:</p>
<pre># таблица ежедневных задач
CREATE TABLE IF NOT EXISTS `daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `daily_task_name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# таблица связей many-to-many задач и sfGuardUser
CREATE TABLE IF NOT EXISTS `user_has_daily_task` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_id` int(11) NOT NULL,
  `daily_task_id` int(11) NOT NULL,
  PRIMARY KEY (`id`),
  KEY `user_id_idx` (`user_id`),
  KEY `daily_task_id_idx` (`daily_task_id`)
) ENGINE=InnoDB  DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=1;

# внешние ключи для связок
ALTER TABLE `user_has_daily_task`
  ADD CONSTRAINT `daily_task_id_idx` FOREIGN KEY (`daily_task_id`) REFERENCES `daily_task` (`id`),
  ADD CONSTRAINT `user_id_idx` FOREIGN KEY (`user_id`) REFERENCES `sf_guard_user` (`id`);</pre>
<p>Этой структуре соответствует следующая схема Doctrine:</p>
<pre># схема для таблицы daily_task
DailyTask:
  connection: doctrine
  tableName: daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    daily_task_name:
      type: string(255)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    UserHasDailyTask:
      local: id
      foreign: daily_task_id
      type: many
      onDelete: NO ACTION
      onUpdate: NO ACTION
# схема для таблицы user_has_daily_task
UserHasDailyTask:
  connection: doctrine
  tableName: user_has_daily_task
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    user_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    daily_task_id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
  relations:
    sfGuardUser:
      local: user_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION
    DailyTask:
      local: daily_task_id
      foreign: id
      type: one
      onDelete: NO ACTION
      onUpdate: NO ACTION</pre>
<p>Выполняем пересоздание классов Doctrine (модели, формы, фильтры) и очищаем кэш:</p>
<pre>$ ./symfony doctrine:build --all-classes
$ ./symfony cache:clear</pre>
<p>Подготовительную часть на этом можно считать завершенной.</p>
<h3>Админ-генератор sfGuardUser</h3>
<p>Теперь на уровне БД у нас есть привязка ежедневных задач к пользователю sfGuardUser. Для редактирования пользователя sfGuard предусматривает одноименный модуль sfGuardUser с админ-генератором. По-умолчанию этот модуль позволяет редактировать имя, пароль пользователя, а также группы и список прав доступа. Вот они то нас и интересуют. Почему? Потому что эти параметры также представляют из себя связки many-to-many, а значит, если мы хотим добавить ежедневные задачи в редактирование пользователя, то сделать это логично по образу и подобию.</p>
<h3>Модель sfGuardUser</h3>
<p>Для того чтобы sfGuardUser корректно понимал нашу новую связь, необходимо модифицировать сгенерированный класс его модели следующим образом:</p>
<pre># lib/model/doctrine/sfDoctrineGuardPlugin/sfGuardUser.class.php
class sfGuardUser extends PluginsfGuardUser
{
  public function setUp()
  {
    parent::setUp();
    $this-&gt;hasMany('DailyTask', array(
      'refClass'  =&gt; 'UserHasDailyTask',
      'local'     =&gt; 'user_id',
      'foreign'   =&gt; 'daily_task_id'
    ));
  }
}</pre>
<p>Отлично, теперь модель пользователя знает о новой связке ) Теперь можно заняться формой</p>
<h3>Форма sfGuardUserForm</h3>
<p>Теперь мы можем модифицировать форму sfGuardUserForm. Для начала добавим виджет для нашего нового поля (виджет назовем <code>daily_tasks_list</code>):</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  public function configure()
  {
    $this-&gt;widgetSchema['daily_tasks_list'] = new sfWidgetFormDoctrineChoice(array(
      'multiple'  =&gt; true,
      'model'     =&gt; 'DailyTask'
    ));

    $this-&gt;validatorSchema['daily_tasks_list'] = new sfValidatorDoctrineChoice(array(
      'multiple'  =&gt; true,
      'model'     =&gt; 'DailyTask',
      'required'  =&gt; false
    ));
}</pre>
<p>Тут желательно еще раз пересобрать все классы и очистить кэш.</p>
<p>Теперь при редактировании пользователя, вы уже сможете увидеть наш новый виджет. Но он еще не пригоден к использованию. Т.е. то что вы выберете, не будет сохранено.</p>
<h3>Сохранение новой связки</h3>
<blockquote><p><strong>Небольшое лирическое отступление, написанное по здравому размышлению</strong>. Если у вас виджет называется точно также как таблица + <code>_list</code> (в нашем случае это будет <code>daily_task_list</code>), то, скорее всего вы уже готовы использовать форму по полной. Я же назвал виджет не так как таблицу (в статье еще опущен префикс таблицы) и поимел последующий геморрой, который все-таки считаю нужным описать полностью.</p></blockquote>
<p>Вот тут нам пригодится аналогия с группами и правами доступа. Если покопошиться в исходниках плагина, выяснится, что для сохранения связей, реализовано два дополнительных метода &#8211; <code>savegroupsList </code>и <code>savepermissionsList</code>. Т.о. мы реализуем наш метод <code>savedailytasksList </code>и переопределяем метод <code>doSave()</code>:</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...
  /**
   * Сохранение списка ежедневных задач
   */
  public function savedailytasksList($con = null)
  {
    if (!$this-&gt;isValid())
    {
      throw $this-&gt;getErrorSchema();
    }
    if (!isset($this-&gt;widgetSchema['daily_tasks_list']))
    {
      // somebody has unset this widget
      return;
    }
    if (null === $con)
    {
      $con = $this-&gt;getConnection();
    }
    $existing = $this-&gt;object-&gt;DailyTask-&gt;getPrimaryKeys();
    $values = $this-&gt;getValue('daily_tasks_list');
    if (!is_array($values))
    {
      $values = array();
    }
    $unlink = array_diff($existing, $values);
    if (count($unlink))
    {
      $this-&gt;object-&gt;unlink('DailyTask', array_values($unlink));
    }
    $link = array_diff($values, $existing);
    if (count($link))
    {
      $this-&gt;object-&gt;link('DailyTask', array_values($link), true);
    }
  }

  /**
   * Сохранение формы
   */
  protected function doSave($con = null)
  {
    $this-&gt;savedailytasksList($con);
    parent::doSave($con);
  }
}</pre>
<p>Ура! Теперь мы можем сохранять наши задачи! )) Остался один маленький заключительный штришок &#8211; сохранить то мы сохраняем, но при повторном редактировании пользователя сохраненные связи не отображаются. Для этого переопределим метод определения значения по-умолчанию для виджета:</p>
<pre># lib/form/doctrine/sfDoctrineGuardPlugin/sfGuardUserForm.class.php
class sfGuardUserForm extends BasesfGuardUserAdminForm
{
  //...

  /**
   * Значение по-умолчанию для виджета ежедневных задач
   */
  public function updateDefaultsFromObject()
  {
    parent::updateDefaultsFromObject();
    if (isset($this-&gt;widgetSchema['daily_tasks_list']))
    {
      $this-&gt;setDefault('daily_tasks_list', $this-&gt;object-&gt;DailyTask-&gt;getPrimaryKeys());
    }
  }
}</pre>
<p>Ну вот, кажется и все! Пользуйтесь на здоровье )) И ставьте &#8220;плюсики&#8221;, если понравилось! ))</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/04/08/add-many-to-many-relation-in-sfguarduser-form/feed/</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
		<item>
		<title>Symfony snippet: partial для постраничной навигации (pagination)</title>
		<link>http://hudson.su/2010/03/25/symfony-snippet-partial-for-pagination/</link>
		<comments>http://hudson.su/2010/03/25/symfony-snippet-partial-for-pagination/#comments</comments>
		<pubDate>Thu, 25 Mar 2010 05:45:29 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[web разработка]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1241</guid>
		<description><![CDATA[Предлагаю вашему вниманию небольшой сниппет для повседневного применения в symfony 1.3 &#8211; 1.4. Сниппет предназначен для отображения постраничной навигации по записям. Задача: выводить список страниц для типового набора записей (новости, каталог и т.п.) Решение: Поскольку пейджер в большинстве типовых проектов требуется однотипный, то и решение для него должно быть простым и универсальным. Для бОльшей наглядости [...]]]></description>
			<content:encoded><![CDATA[<p>Предлагаю вашему вниманию небольшой сниппет для повседневного применения в symfony 1.3 &#8211; 1.4.</p>
<p>Сниппет предназначен для отображения постраничной навигации по записям.</p>
<p><span id="more-1241"></span></p>
<h2>Задача:</h2>
<p>выводить список страниц для типового набора записей (новости, каталог и т.п.)</p>
<h2>Решение:</h2>
<p>Поскольку пейджер в большинстве типовых проектов требуется однотипный, то и решение для него должно быть простым и универсальным. Для бОльшей наглядости предположим что у нас есть модель NewsItem &#8211; новости сайта.</p>
<h3>Подготовительные операции:</h3>
<p>Схема:</p>

<div class="wp_syntax"><div class="code"><pre class="text" style="font-family:monospace;">NewsItem:
  options:
    type: INNODB
    collate: utf8_general_ci
    charset: utf8
  actAs:
    Timestampable:
  connection: doctrine
  tableName: news_item
  columns:
    id:
      type: integer(4)
      fixed: false
      unsigned: false
      primary: true
      autoincrement: true
    title:
      type: string(255)
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    abstract:
      type: string()
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    description:
      type: string()
      fixed: false
      unsigned: false
      primary: false
      notnull: true
      autoincrement: false
    is_published:
      type: boolean
      fixed: false
      unsigned: false
      primary: false
      default: '0'
      notnull: true
      autoincrement: false</pre></div></div>

<p>Метод получения пейджера в NewsItemTable.class.php:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// Где-то в lib/model/doctrine/NewsItemTable.class.php</span>
<span style="color: #009933; font-style: italic;">/**
 * Что-то с постраничным выводом
 * @param &lt;int&gt; $page
 * @return &lt;Doctrine_Pager&gt;
 */</span>
<span style="color: #000000; font-weight: bold;">public</span> static <span style="color: #000000; font-weight: bold;">function</span> getNewsPager<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$page</span> <span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$query</span> <span style="color: #339933;">=</span> Doctrine_Query<span style="color: #339933;">::</span><span style="color: #004000;">create</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span>
    <span style="color: #339933;">-&gt;</span><span style="color: #004000;">select</span>    <span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;*&quot;</span> <span style="color: #009900;">&#41;</span>
    <span style="color: #339933;">-&gt;</span><span style="color: #004000;">from</span>      <span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;NewsItem n&quot;</span> <span style="color: #009900;">&#41;</span>
    <span style="color: #339933;">-&gt;</span><span style="color: #004000;">where</span>     <span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;n.is_published = 1&quot;</span> <span style="color: #009900;">&#41;</span>
    <span style="color: #339933;">-&gt;</span><span style="color: #004000;">orderBy</span>   <span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;n.created_at DESC&quot;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #b1b100;">return</span> <span style="color: #000000; font-weight: bold;">new</span> Doctrine_Pager<span style="color: #009900;">&#40;</span> <span style="color: #000088;">$query</span><span style="color: #339933;">,</span> <span style="color: #000088;">$page</span><span style="color: #339933;">,</span> sfConfig<span style="color: #339933;">::</span><span style="color: #004000;">get</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;app_pager_perpage&quot;</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">10</span> <span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>Настройку app_pager_perpage добавляем в app.yml:</p>

<div class="wp_syntax"><div class="code"><pre class="yaml" style="font-family:monospace;"># apps/frontend/config/app.yml
all:
  pager:
    perpage: 10</pre></div></div>

<p>В действии:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;"><span style="color: #666666; font-style: italic;">// apps/frontend/modules/news/actions/actions.class.php</span>
<span style="color: #009933; font-style: italic;">/**
 * Архив новостей
 * @param  $request
 */</span>
<span style="color: #000000; font-weight: bold;">public</span> <span style="color: #000000; font-weight: bold;">function</span> executeArchive<span style="color: #009900;">&#40;</span>sfWebRequest <span style="color: #000088;">$request</span><span style="color: #009900;">&#41;</span>
<span style="color: #009900;">&#123;</span>
  <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">news_pager</span> <span style="color: #339933;">=</span> NewsItemTable<span style="color: #339933;">::</span><span style="color: #004000;">getNewsPager</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$request</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getParameter</span><span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;page&quot;</span><span style="color: #339933;">,</span> <span style="color: #cc66cc;">1</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
  <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">news</span>       <span style="color: #339933;">=</span> <span style="color: #000088;">$this</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">news_pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">execute</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #009900;">&#125;</span></pre></div></div>

<p>Теперь создаем партиал пейджера (в каком-нибудь общем модуле, shared или commons):</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;">// apps/frontend/modules/commons/templates/_pager.php
<span style="color: #000000; font-weight: bold;">&lt;?php</span>
&nbsp;
<span style="color: #009933; font-style: italic;">/**
 * Типовой пейджер
 *
 * @param &lt;Doctrine_Pager&gt; $pager - объект пейджера
 * @param &lt;string&gt; $route - маршрут для построения ссылок пейджера
 *
 * @package     commons
 * @subpackage  templates
 * @author      Dmitry.Bykadorov@gmail.com
 * @version     SVN: $Id: _pager.php dmitry.bykadorov $
 *
 */</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">?&gt;</span>
&nbsp;
<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">haveToPaginate</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
&lt;div class=&quot;pager&quot;&gt;
&nbsp;
  &lt;ul&gt;
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getFirstPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">!=</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
    &lt;li&gt;&lt;a href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;<span style="color: #006699; font-weight: bold;">$route</span>?page=&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getFirstPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; title=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Первая страница'</span><span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;&lt;&lt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;<span style="color: #006699; font-weight: bold;">$route</span>?page=&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPreviousPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; title=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Назад'</span><span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;&lt;&lt;/a&gt;&lt;/li&gt;
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endif</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">for</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">=</span> <span style="color: #339933;">-</span><span style="color: #cc66cc;">2</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">&lt;</span> <span style="color: #cc66cc;">3</span><span style="color: #339933;">;</span> <span style="color: #000088;">$i</span><span style="color: #339933;">++</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
      <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span> <span style="color: #009900;">&#40;</span> <span style="color: #000088;">$p</span> <span style="color: #339933;">=</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">+</span> <span style="color: #000088;">$i</span> <span style="color: #009900;">&#41;</span> <span style="color: #339933;">&gt;</span> <span style="color: #cc66cc;">0</span> <span style="color: #339933;">&amp;&amp;</span> <span style="color: #009900;">&#40;</span> <span style="color: #000088;">$p</span> <span style="color: #339933;">&lt;=</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getLastPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
        <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #000088;">$active</span> <span style="color: #339933;">=</span> <span style="color: #009900;">&#40;</span> <span style="color: #000088;">$i</span> <span style="color: #339933;">==</span> <span style="color: #cc66cc;">0</span> <span style="color: #009900;">&#41;</span> ? <span style="color: #0000ff;">&quot;active&quot;</span> <span style="color: #339933;">:</span> <span style="color: #0000ff;">&quot;&quot;</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
        &lt;li&gt;&lt;a class=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$active</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;<span style="color: #006699; font-weight: bold;">$route</span>?page=&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$p</span> <span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; title=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Страница '</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$p</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #000088;">$p</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&lt;/a&gt;&lt;/li&gt;
      <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endif</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endfor</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
&nbsp;
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">if</span><span style="color: #009900;">&#40;</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getLastPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #339933;">!=</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">:</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
    &lt;li&gt;&lt;a href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;<span style="color: #006699; font-weight: bold;">$route</span>?page=&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getNextPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; title=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Вперед'</span><span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;&gt;&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> url_for<span style="color: #009900;">&#40;</span> <span style="color: #0000ff;">&quot;<span style="color: #006699; font-weight: bold;">$route</span>?page=&quot;</span> <span style="color: #339933;">.</span> <span style="color: #000088;">$pager</span><span style="color: #339933;">-&gt;</span><span style="color: #004000;">getLastPage</span><span style="color: #009900;">&#40;</span><span style="color: #009900;">&#41;</span> <span style="color: #009900;">&#41;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>&quot; title=&quot;<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">echo</span> <span style="color: #0000ff;">'Последняя страница'</span><span style="color: #000000; font-weight: bold;">?&gt;</span>&quot;&gt;&gt;&gt;&lt;/a&gt;&lt;/li&gt;
    <span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endif</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span>
  &lt;/ul&gt;
&nbsp;
&lt;/div&gt;
<span style="color: #000000; font-weight: bold;">&lt;?php</span> <span style="color: #b1b100;">endif</span><span style="color: #339933;">;</span> <span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<p>Ну и все что нам осталось, разместить в шаблоне архива постраничную навигацию:</p>

<div class="wp_syntax"><div class="code"><pre class="php" style="font-family:monospace;">// apps/frontend/modules/news/templates/archiveSuccess.php
<span style="color: #000000; font-weight: bold;">&lt;?php</span>
include_partial<span style="color: #009900;">&#40;</span>
  <span style="color: #0000ff;">&quot;commons/pager&quot;</span><span style="color: #339933;">,</span>
  <span style="color: #990000;">array</span><span style="color: #009900;">&#40;</span>
    <span style="color: #0000ff;">&quot;pager&quot;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #000088;">$news_pager</span><span style="color: #339933;">,</span>
    <span style="color: #0000ff;">&quot;route&quot;</span> <span style="color: #339933;">=&gt;</span> <span style="color: #0000ff;">&quot;@news_archive&quot;</span>
<span style="color: #009900;">&#41;</span><span style="color: #009900;">&#41;</span><span style="color: #339933;">;</span>
<span style="color: #000000; font-weight: bold;">?&gt;</span></pre></div></div>

<blockquote><p>Вот и все. Можно пользоваться.</p>
<p>Пожелания, предложения и багрепорты оставляйте в комментариях )</p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/03/25/symfony-snippet-partial-for-pagination/feed/</wfw:commentRss>
		<slash:comments>13</slash:comments>
		</item>
		<item>
		<title>Сниппет: инициализация character_set и collation для Doctrine в Symfony</title>
		<link>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/</link>
		<comments>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/#comments</comments>
		<pubDate>Mon, 22 Mar 2010 20:21:38 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[mysql]]></category>
		<category><![CDATA[snippet]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[web разработка]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1243</guid>
		<description><![CDATA[Для того чтобы Doctrine создавал таблицы в базе данных с правильным набором символов и collation (например utf8 и utf8_general_ci, а не latin1, используемый mysql по умолчанию (например)) необходимо выполнить простую инициализацию: // config/ProjectConfiguration.class.php: public function configureDoctrine(Doctrine_Manager $manager) { $manager-&#62;setCharset( 'utf8' ); $manager-&#62;setCollate( 'utf8_unicode_ci' ); } Спасибо Андрэю Дзягелю из русскоговорящего symfony-коммьюнити )]]></description>
			<content:encoded><![CDATA[<p>Для того чтобы <strong>Doctrine </strong>создавал таблицы в базе данных с правильным набором символов и collation (например <strong>utf8 </strong>и <strong>utf8_general_ci</strong>, а не <strong>latin1</strong>, используемый mysql по умолчанию (например)) необходимо выполнить простую инициализацию:</p>
<pre>
// config/ProjectConfiguration.class.php:
public function configureDoctrine(Doctrine_Manager $manager)
{
  $manager-&gt;setCharset( 'utf8' );
  $manager-&gt;setCollate( 'utf8_unicode_ci' );
}
</pre>
<p>Спасибо <a href="http://develop7.info/" target="_blank">Андрэю Дзягелю</a> из русскоговорящего symfony-коммьюнити )</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/03/22/character-set-and-collation-doctrine-symfony/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Магические числа и таблицы-справочники в Doctrine/Propel</title>
		<link>http://hudson.su/2010/03/08/magic-numbers-and-reference-tables-in-doctrine-propel/</link>
		<comments>http://hudson.su/2010/03/08/magic-numbers-and-reference-tables-in-doctrine-propel/#comments</comments>
		<pubDate>Mon, 08 Mar 2010 13:34:06 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[propel]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1086</guid>
		<description><![CDATA[Кросспост моего хабратопика. Коль скоро у вас в проекте используется база данных, то вам рано или поздно потребуются справочные таблицы. Такие таблицы я бы условно разбил на три категории: Небольшие справочники, до 10, реже 20 записей. Например &#8211; таблица статусов чего-нибудь (active&#124;inactive&#124;deleted как минимум). Средние справочники &#8211; от 20 до нескольких сотен записей. Например, таблица [...]]]></description>
			<content:encoded><![CDATA[<p>Кросспост моего <a target="_blank" href="http://hudson.habrahabr.ru/blog/86730/">хабратопика</a>.</p>
<p>Коль скоро у вас в проекте используется база данных, то вам рано или поздно потребуются справочные таблицы. Такие таблицы я бы условно разбил на три категории:</p>
<ol>
<li>Небольшие справочники, до 10, реже 20 записей. <i>Например &#8211; таблица статусов чего-нибудь (active|inactive|deleted как минимум)</i>.</li>
<li>Средние справочники &#8211; от 20 до нескольких сотен записей. <i>Например, таблица типов или категорий чего-либо</i>.</li>
<li>Большие справочники &#8211; от нескольких сотен до сотен тысяч записей. <i>Например список городов и улиц России</i>.</li>
</ol>
<p>Справочники, как правило, заполняются разово при создании и крайне редко пополняются. Но тем не менее, пополнение возможно и наиболее вероятно для третьего типа, менее для второго и редко для первого.</p>
<p>Собственно зачем я это пишу:</p>
<p><span id="more-1086"></span></p>
<p>Раз у вас есть таблица, то есть и классы модели для нее. Все хорошо, пока вам в коде не приходится сослаться на какое-то значение из справочника. Например есть таблица <code>Status</code>:</p>
<pre>
|    id    |     name       |
----------------------------
|    1     |     active     |
|    2     |     inactive   |
|    3     |     deleted    |
</pre>
<p>В связанных таблицах мы имеем <code>HasOne: Status</code>, и foreign key на <code>status.id</code>.</p>
<p>И вот нам понадобилось выбрать все активные записи. Не долго думая мы пишем:</p>
<pre>
$query = Doctrine_Query::create()
  -&gt;select()
  -&gt;from( "Product p, p.Status s" )
  -&gt;where( "s.name = 'active'" )
//...
</pre>
<p>Oops. Джойн хорош, но нам постоянно добавлять его к любому запросу неинтересно. &#8220;Рефакторим&#8221;:</p>
<pre>
$query = Doctrine_Query::create()
  -&gt;select()
  -&gt;from( "Product p" )
  -&gt;where( "p.status_id = 1" )
//...
</pre>
<p>OOOOOooops. По проекту плодятся магические числа:</p>
<pre>
status_id = 2
type_id = 36
city_id = 1234
</pre>
<p><b>Стоп.</b></p>
<p>а) мы не хотим в каждый запрос, который требует связки со справочником пихать join (да и name=&#8221;active&#8221; тоже магическая константа по сути, но вербально понятная).<br />
б) мы не хотим плодить магические числа.</p>
<p>И какой же выход?</p>
<p>Честно говоря, <b>красивого</b> выхода я не вижу. Для справочников типа 1 я применяю следующий подход:</p>
<pre>
// Status
class StatusTable extends Doctrine_Table
{
  const STATUS_ACTIVE   = 1;
  const STATUS_INACTIVE = 2;
  const STATUS_DELETED  = 3;
  ...
}
// Product
class ProductTable extends Doctrine_Table
{
  public function getActive()
  {
    ...
    -&gt;addWhere( "p.status_id = ?", self::STATUS_ACTIVE ) // ну или как-то так <img src='http://hudson.su/wp-includes/images/smilies/icon_smile.gif' alt=':)' class='wp-smiley' />
  }
  ...
}
</pre>
<p>Этот подход более-менее работает только со справочниками типа 1 (при этом, при добавлении нового статуса попробуй еще вспомнить, что надо добавить еще одну константу, благо это не часто требуется).</p>
<p>Со справочниками типов 2 и 3 вообще не знаю что делать. </p>
<p>А как вы работаете со справочниками?</p>
<p>P.S. не факт что со справочниками типа 3 приходится работать &#8220;точечно&#8221;, поэтому для них может это и не актуально.</p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/03/08/magic-numbers-and-reference-tables-in-doctrine-propel/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Вывод неэкранированных (unescaped) данных в symfony 1.3/1.4</title>
		<link>http://hudson.su/2010/03/02/print-unescaped-data-in-symfony-13-14/</link>
		<comments>http://hudson.su/2010/03/02/print-unescaped-data-in-symfony-13-14/#comments</comments>
		<pubDate>Tue, 02 Mar 2010 13:45:48 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[hints]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1050</guid>
		<description><![CDATA[Коль скоро в symfony 1.3/1.4 по умолчанию включено экранирование (escaping) всех выводимых данных, вам рано или поздно понадобится вывести что-либо в неэкранированном виде. Судя по постоянным вопросам в коммьюнити, не все могут найти документацию по этому вопросу, поэтому публикую эту небольшую заметку. Итак, во-первых, в помощь вам EscapingHelper. Подключить его можно так: &#60;?php use_helper('Escaping'); ?&#62; [...]]]></description>
			<content:encoded><![CDATA[<p>Коль скоро в symfony 1.3/1.4 по умолчанию включено экранирование (escaping) всех выводимых данных, вам рано или поздно понадобится вывести что-либо в неэкранированном виде. Судя по постоянным вопросам в коммьюнити, не все могут найти документацию по этому вопросу, поэтому публикую эту небольшую заметку.</p>
<p><span id="more-1050"></span></p>
<p>Итак, во-первых, в помощь вам EscapingHelper. Подключить его можно так:</p>
<pre>&lt;?php
  use_helper('Escaping');
?&gt;</pre>
<p>Это даст нам возможность использовать такие методы как:</p>
<ul>
<li><code> string <strong>esc_entities</strong>($value)</code><br />
Применяет htmlentities к $value.</li>
<li><code> string <strong>esc_js</strong>($value)</code><br />
Экранирование в C-стиле переданного значения, после того как будет выполнено {@link esc_entities()}.</li>
<li><code> string <strong>esc_js_no_entities</strong>($value)</code><br />
Экранирование в C-стиле переданного значения.</li>
<li><code> string <strong>esc_raw</strong>($value)</code><br />
А это как раз то что нам нужно &#8211; <strong>вывод неэкранированного значения</strong>.</li>
<li> <code>string <strong>esc_specialchars</strong>($value)</code><br />
Применяет htmlspecialchars к $value.</li>
</ul>
<p>Итак, если нам нужно просто вывести что-то, не экранируя, мы применяем <code><strong>esc_raw()</strong></code>.</p>
<p>Но, зачастую, нужно выводить не экранируя, значения из базы данных.</p>
<p>Для Doctrine, если мы получим значение через array access ($model['field']) или как параметр объекта ($model-&gt;field), значение будет экранировано.</p>
<p>К счастью, доктрина имеет магические аксессоры к полям класса типа <code>getField </code>(реализовано через <code>__call()</code>) и мы можем сделать такой вызов:</p>
<pre>&lt;?php
  echo $model-&gt;getField(ESC_RAW);
?&gt;</pre>
<p>Что поможет вам добиться нужного результата.</p>
<p>Надеюсь был полезен <img src='http://hudson.su/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
<blockquote><p>Оно же в моем блоге на хабре )) <a href="http://hudson.habrahabr.ru/blog/86024/" target="_blank">http://hudson.habrahabr.ru/blog/86024/</a></p></blockquote>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/03/02/print-unescaped-data-in-symfony-13-14/feed/</wfw:commentRss>
		<slash:comments>6</slash:comments>
		</item>
		<item>
		<title>Doctrine + memcached: использование и тестирование</title>
		<link>http://hudson.su/2010/02/23/doctrine-memcached-usage-and-testing/</link>
		<comments>http://hudson.su/2010/02/23/doctrine-memcached-usage-and-testing/#comments</comments>
		<pubDate>Tue, 23 Feb 2010 11:28:36 +0000</pubDate>
		<dc:creator>hudson</dc:creator>
				<category><![CDATA[Профессиональное]]></category>
		<category><![CDATA[doctrine]]></category>
		<category><![CDATA[howto]]></category>
		<category><![CDATA[memcached]]></category>
		<category><![CDATA[php]]></category>
		<category><![CDATA[symfony]]></category>
		<category><![CDATA[тестирование]]></category>

		<guid isPermaLink="false">http://hudson.su/?p=1026</guid>
		<description><![CDATA[Doctrine ORM имеет встроенный кэш-менеджер, который умеет кэшировать в Memcached APC DataBase (видимо имеется в виду некая плоская БД с быстрым доступом, типа SQLite) Узнав сей факт решил воспользоваться встроенным механизмом и протестировать механизм кэширования. Включение кэширования в symfony Расширяем возможности конфигурации. В config/ProjectConfiguration.class.php добавляем метод конфигурирования Doctrine: /** * Конфигурация ORM * @param Doctrine_Manager [...]]]></description>
			<content:encoded><![CDATA[<p>Doctrine ORM имеет встроенный кэш-менеджер, который умеет кэшировать в</p>
<ul>
<li>Memcached</li>
<li>APC</li>
<li>DataBase (видимо имеется в виду некая плоская БД с быстрым доступом, типа SQLite)</li>
</ul>
<p>Узнав сей факт решил воспользоваться встроенным механизмом и протестировать механизм кэширования.</p>
<h2><span id="more-1026"></span>Включение кэширования в symfony</h2>
<p>Расширяем возможности конфигурации. В <code>config/ProjectConfiguration.class.php</code> добавляем метод конфигурирования Doctrine:</p>
<pre>/**
* Конфигурация ORM
* @param Doctrine_Manager $manager
*/
public function configureDoctrine( Doctrine_Manager $manager )
{
  if ( extension_loaded( 'memcache' ) )
  {
    $servers = array(
      'host'        =&gt; 'localhost',
      'port'        =&gt; 11211,
      'persistent'  =&gt; true
    );
    $cacheDriver = new Doctrine_Cache_Memcache( array(
      'servers'     =&gt; $servers,
      'compression' =&gt; false
    ));
    $manager = Doctrine_Manager::getInstance();
    $manager-&gt;setAttribute( Doctrine::ATTR_QUERY_CACHE, $cacheDriver );
    $manager-&gt;setAttribute( Doctrine::ATTR_RESULT_CACHE, $cacheDriver );
  }
}
</pre>
<p>Итак, если загружено расширение memcached (это подпорка для того чтобы работало без проблем и без кэширования), определяем сервер (серверы), создаем инстанс драйвера кэша. Кэшировать будем запросы <strong>Doctrine::ATTR_QUERY_CACHE</strong> (разобранные Doctrine-запросы, чтобы не парсить их каждый раз заново) и результаты запросов <strong>Doctrine::ATTR_RESULT_CACHE</strong>.</p>
<p>Собственно для того чтобы кэшировались запросы придется использовать такую конструкцию:</p>
<pre>Doctrine_Query::create()
  ...
  -&gt;<strong>useQueryCache </strong>()
  -&gt;<strong>useResultCache</strong>()
  ...
  -&gt;execute       ();
</pre>
<p>Вообще говоря, судя по документации Doctrine, прописав атрибуты менеджера мы обеспечиваем себя кэшированием. На самом деле у меня кэширование не заработало пока не использовал в конструкторе запросов <strong>use*Cache()</strong>.</p>
<blockquote><p>На самом деле это не есть гуд, так как при таком подходе нельзя выключить кэширование сразу и хитрое конфигурирование не убережет нас от падения продуктовой среды, если в ней нет memcached.</p></blockquote>
<p>Собственно вот. Переходим к тестированию.</p>
<h2>Тестирование кэширования</h2>
<p>Для того чтобы оценить эффект от кэширования напишем простой таск, который будет в цикле выполнять однотипные запросы, но со случайными параметрами. Создаем класс <code>lib/task/doctrineTestTask.class.php</code>:</p>
<pre>class doctrineTestTask extends sfDoctrineBaseTask
{
}
</pre>
<p>Конфигуратор &#8211; без неожиданностей:</p>
<pre>protected function configure()
{
  $this-&gt;briefDescription = 'Test doctrine memcached';
  $this-&gt;detailedDescription = &lt;&lt;&lt;EOF
Test doctrine memcached
EOF;
}
</pre>
<p>Тестовый метод:</p>
<pre>protected function execute($arguments = array(), $options = array())
{
  // макс ID
  $q = Doctrine_Query::create()
    -&gt;select  ( "MAX(l.id) as max, MIN(l.id) as min" )
    -&gt;from    ( "Locality l" )
    -&gt;useResultCache()
    -&gt;execute ()
    -&gt;toArray ();

  // тестовый цикл
  echo ( $start = time() ) . "n";
  for( $i = 0; $i &lt; 10000; $i++ )
  {
    echo "$ir";
    // Случайная locality
   while( !( $locality ) )
   {
     $locality = $this-&gt;getRandLocality( $q[0]["min"], $q[0]["max"] );
   }

   // собственно тестовый запрос с несколькими джойнами. Таблицы от тысяч до пости сотни тысяч строк. Для теста сойдут.
  $search_query = Doctrine_Query::create()
    -&gt;select  ( "prop.*, loc.*, haskw.*, kw.*" )
   -&gt;from    ( "Property prop, prop.Locality loc, prop.PropertyHasKeyword haskw, haskw.Keyword kw" )
   -&gt;where   ( "loc.lft &gt;= ? AND loc.rgt &lt;= ?", array( $locality-&gt;lft, $locality-&gt;rgt ) )
   -&gt;offset  ( 10 * rand( 1, 100 ) )
   -&gt;limit   ( 10 )
   -&gt;useResultCache()
   -&gt;execute ();
  }
  echo ( $end = time() ) . "n";
  echo "====================================================n" . ($end - $start);
}
</pre>
<p>getRandLocality() особого значения не имеет (но результат того запроса тоже кэшируем), поэтому с find() пришлось уйти на Doctrine_Query (пока не смотрел как подружить find* с кэшированием).</p>
<p>Для тестирования под win понадобятся <a href="http://hudson.su/?p=965" target="_blank">memcached dll</a> и <a href="http://allegiance.chi-town.com/MemCacheDManager.aspx" target="_blank">MemCacheD Manager</a>.</p>
<h3>Результаты тестирования (последовательный запуски &#8211; чтобы прокэшировать данные):</h3>
<pre>Z:homedoctrine-test&gt;symfony doctrine-test
1266870147
1266872222
====================================================
<strong>2075</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266872876
1266873507
====================================================
<strong>631</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266877034
1266877157
====================================================
<strong>123</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266877387
1266877478
====================================================
<strong>91</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266878158
1266878233
====================================================
<strong>75</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266878598
1266878658
====================================================
<strong>60</strong>

Z:homedoctrine-test&gt;symfony doctrine-test
1266880037
1266880096
====================================================
<strong>59</strong>
</pre>
<p>Нисходящий тренд на лицо. Первый запуск занял &gt; 2000 секунд (в кэше ничего не было). Повторный запуск оказался в 3 раза быстрее, последующий в 5 раз быстрее. После этого начали подходить к пределу возможностей кэшера и последующие 4 запуска уменьшили время лишь в два раза. Но там не менее, всего с первого запуска скорость выполнения теста уменьшилась более чем в 35 раз.</p>
<p>Состояние memcached:</p>
<p><strong>Memcached Server</strong> 1.2.4<br />
<strong>Uptime </strong>04:13:26<br />
<strong>Items count</strong> 13859<br />
<strong>Total items</strong> 27711	11<br />
<strong>Hits </strong>86088<br />
<strong>Misses </strong>15994<br />
<strong>Used bytes</strong> 8695086<br />
<strong>Bytes read</strong> 4513330164<br />
<strong>Bytes writen</strong> 4547347068<br />
<strong>Max bytes</strong> 134217728</p>
<p>Итого, наш тест в памяти memcached занял примерно 8 Мб. Неплохо, неплохо. Готовим memcached на production серверах <img src='http://hudson.su/wp-includes/images/smilies/icon_wink.gif' alt=';)' class='wp-smiley' /> </p>
]]></content:encoded>
			<wfw:commentRss>http://hudson.su/2010/02/23/doctrine-memcached-usage-and-testing/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

