Вы находитесь здесь: Главная > Программирование > Средства и возможности ++

Средства и возможности ++

40 weeks 2 days pregnant .

analiz■   Одновременная загрузка нескольких записей. Пока что в системе управ­ления блогом мы имели возможность загрузить для просмотра и редактиро­вания только одну запись блога из базы данных. А теперь у нас появится возможность с помощью одной операции получать из базы сразу большое ко­личество данных.

■   Отображение существующих заметок блога. Используя функции, соз­данные для извлечения сразу нескольких записей блога из базы данных, мы создадим индексную страницу с перечнем заметок пользователя, чтобы он мог просматривать и редактировать ilk по мере надобности. Эта страница будет организована с применением технологии Ajax, чтобы ускорить доступ к заметкам блога.

■   Интегрирование визуального редактора в блог. Будет реализован редак­тор FCKeditor, текстовый редактор типа WYSIWYG («что видишь, то и полу­чаешь») с открытым кодом. Благодаря ему пользователи смогут форматиро­вать заметки своих блогов тегами HTML с использованием специальной па­нели инструментов.

После завершения этой главы средства управления блогами достигнут такого уровня готовности, что пользователи смогут быстро и легко помещать новые запи­си в свои блоги. Прочитав главу 9, пользователи смогут публиковать свои блоги для всеобщего обозрения.

Организация списка записей на индексной странице

Сейчас пользователи могут создавать новые записи в своих блогах с использо­ванием средств, разработанных в главе 7, но вот вернуться к существующей запи­си для ее редактирования пока невозможно. Чтобы пользователя имели возмож­ность легко управлять записями блогов, мы организуем на главной (индексной) странице блога (http: //phpweb2 0/blogmar.ager) список всех его записей.

Записи будут отображаться следующим образом. Все заметки за текущий месяц будут показаны, начиная с верха страницы (с кратким анонсом каждой заметки), а итоговая сводка за месяц — сбоку от списка, в левом столбце. Пользователь смо­жет щелкнуть на определенном месяце, чтобы перезагрузить страницу с заметка­ми за этот месяц.

Как только мы реализуем эту функцию, сразу же усовершенствуем код так, что­бы получать список заметок за выбранный месяц средствами Ajax, т.е. без переза­грузки страницы. Изначально мы разрабатываем версию индексной страницы блога без применения Ajax, — это сделано для совместимости с браузерами, в ко­торых не поддерживается javascript.

Весь процесс будет состоять из нескольких шагов.

1.    Загрузка записей за указанный месяц (по умолчанию — текущий).

2.    Загрузка списка других месяцев, в которых сделаны записи, а также коли­чества записей за каждый месяц.

3.    Вывод записей за выбранный месяц с кратким анонсом каждой записи.

4.    Вывод итогов за месяц с использованием метода indexAct ion () для вывода списка записей.

Извлечение заметок блога из базы данных

Прежде чем двигаться дальше, необходимо решить вопрос одновременного по­лучения сразу нескольких записей из базы в классе DatabaseOb j ect_BlogPost. До сих пор записи всегда извлекались по одной, а сейчас возникла необходимость за­гружать сразу несколько.

Для этой цели напишем четыре статических метода, которые можно будет ис­пользовать как в этой главе, так и в других частях приложения (при отображении заметок блога в других разделах сайта).

■   GetPosts {). Метод извлекает массив записей в зависимости от переданных в него параметров, в том числе смещения и максимального количества воз­вращаемых записей с целью многостраничной организации данных. Метод возвращает массив объектов DatabaseObject_BlogPost.

■   GetPostsCount (). Метод возвращает общее количество записей, удовлетво­ряющих переданным в него критериям. Поскольку метод GetPosts () может возвращать многостраничные данные (для которых задано смещение и мак­симальное количество записей), нам понадобится знать общее количество записей, чтобы определить количество страниц.

■   GetMonthlySummary {). Аналогично GetPostsCount (), этот метод использу­ется для получения количества записей, найденных для каждого месяца в указанном диапазоне дат. Если диапазон дат не задан, будут учтены все месяцы, в которых в блог добавлялись заметки.

■    GetBaseQuery (). Этот скрытый метод используется всеми перечисленными выше функциями для построения запроса по заданным параметрам. Его единственная задача — избежать дублирования кода. Например, если нуж­но добавить новый параметр извлечения записей в функцию GetPosts (), то это же средство необходимо будет реализовать и в ф»ункции GetPost­sCount () для возвращения точного количества записей.

Метод GetBaseQuery ()

 

Начнем с метода GetBaseQuery {). поскольку именно он вызывается из методов GetPosts (), GetPostsCount () и _GetBaseQuery {}. Воспользуемся классом Zend_Db_ Select, прилагающимся к компоненту Zend Db из библиотеки Zend Framework.

Этот класс используется для построения SQL-запросов select. Он предоставляет методы, позволяющие легко добавлять в запрос те или иные его части. Например, для добавления условия where вызывается метод where (). Более подробно об этом классе можно прочитать в руководстве пользователя к библиотеке Zend Framework по адре­су http://framework.zend.com/manual/en/zend.db.select.html.

Для инициализации экземпляра класса Zend Db Select можно сделать вызов Zend_Db_Select ($db), где $db — соединение с базой данных, или же вызов $db- >select () для получения данных сразу в новом экземпляре класса.

В листинге 8.1 приведен полный текст функции _GetBaseQuery (), помещенной в класс DatabaseObject_BlogPost.

Листинг 8.1. Функция GetBaseQuery () для построения SQL-запросов select (файл BlogPost.php)

 

<?php

class DatabaseObject BlogPost extends DatabaseObject {

// ... другой код

private static function GetBaseQuery ($db, $options) {

// инициализация параметров $defaults = array (

'user_id' => arrayO,

•       from' => ' 1,

•                 to'        => ''

), —

foreach ($defaults as $k ■> $v) {

$options[$k] — arrayjcey exists ($k, $options) ? $options[$k] : $v;

>

// создание запроса на выбор из таблицы blogposts $select ■ $db->select ();

$select->f rom (array (' p 1 => 1 blogposts ■) , arrayO);

// фильтрация записей по начальной и конечной дате if (strlen ($options[1from']) > 0) { $ts = strtotime ($options['from1]);

$select->where ('p.ts created >= ?', date ('Y-m-d H:i:s', $ts));

}

if (strlen ($options['to']) > 0) { $ts = strtotime ($options['to']);

$select->where ('p. ts_created <= ?', datepY-m-d H:i:s', $ts) ) ;

}

// фильтрация результатов по заданным пользователям (если есть) if (count ($options['user id']) > 0)

$select->wherePp.user id in (?)', $options['user________________ id']);

return $select;

}

}

Прежде всего этот метод определяет массив параметров, устанавливаемых по умолчанию. Пока что параметров всего три. Параметр user id определяет, по ка­кому пользователю фильтровать возвращаемые данные, а параметры from и to — диапазон дат для записей блога.

Далее в цикле все эти параметры перебираются и инициализируются в массиве $ opt ions, чем гарантируется их существование при обращении к ним из других мест функции.

Следующий шаг — создание экземпляра класса Zend Db Select путем вызова метода $db->select (). Обычно первое, что нужно сделать с созданным экземпля­ром Zend_Db_Select, — это определить таблицы, к которым выполняется запрос, и поля, из которых производится выбор. Каждая таблица, из которой будет выпол­няться выбор, задается вызовом метода from () — его нужно вызвать один раз для каждого метода.

Первым аргументом в функцию from () передается имя таблицы. Типом этого аргумента может быть либо строка, либо массив. Использование массива позволя­ет заменить явное имя подстановочным (псевдонимом) для дальнейшего использо­вания в запросе. Такой массив будет состоять из одной пары «ключ-значение»: значением является имя таблицы, а ключом — псевдоним для подстановки.

Мы будем работать с таблицей blog_posts, которой присвоим псевдоним р. Соот- ветствеш ю первым аргументом функции f rom () будет array (' р' <= > ' b 1 og_pos t s').

Второй аргумент функции from () представляет собой поля для выбора. Можно задать одно поле, указав одну строку, а можно несколько с использованием масси­ва (в котором каждый элемент соответствует столбцу таблицы). В нашем случае мы используем пустой массив, поскольку метод GetBaseQuery () формирует только базовый набор параметров для запроса. В других методах, из которых вызывается _GetBaseQuery (), мы зададим выбор конкретных полей.

Далее проверяется наличие параметров from и to, используемых для фильтра­ции заметок блога по дате и времени из столбца ts created таблицы blog posts. Если эти параметры пусты, они игнорируются. Но если они указаны, то в запрос добавляется условие where для ограничения выбора заметок по времени.

Следующая задача — добавить в запрос условия where для фильтрации резуль­тата по столбцу user id. Проблему корректного заключения значений в кавычки берет на себя класс Zend_Db. Это очень важно, поскольку помогает защититься от злонамеренных пользователей, которые попытались бы взломать приложение при помощи SQL-инъекции.

При вызове метода where () первым аргументом служит условие выбора where. Оно может содержать знак вопроса в качестве заменителя значения, подставляе­мого в условие. Второй (необязательный) аргумент — значение, которое следует подставить вместо вопросительного знака.

Когда класс Zend Db заключает значения в кавычки, он проверяет их тип. что­бы все было сделано правильно. Например, если задан массив (как в нашем слу­чае), то каждое значение соответственно берется в кавычки, а затем присоединя­ется через запятую. Благодаря этому можно передать несколько значений через $options [ 'user_id'], т.е. фильтровать сразу по нескольким пользователям, если понадобится.

Примечание

Применение массива в качестве значения задумано для того, чтобы использовать оператор in вместо зна­ка равенства, Если бы массив $options [ • user_id' ] был определен как {i, 2, з, 4), то полу­чившийся запрос выглядел бы как user_id in (1, 2, з, 4).

Наконец, из метода возвращается объект Zend Db Select. Благодаря этому функции GetPostsO и GetPostsCount (} могут при необходимости делать даль­нейшие добавления к запросу (а такая необходимость возникнет, как мы вскоре увидим). Отметим, что сгенерированный до сих пор запрос пока что непригоден к использованию, поскольку еще не указаны поля для выбора, о которых говорилось ранее в описании метода from ().

Метод GetPostsCount ()

 

Определим метод GetPostsCount (). который возвращает общее количество ре­зультатов, полученных согласно переданным критериям (т.е. количество строк, ко­торые вернул бы метод GetPosts (), если бы не был задан максимум). Как и в мето­де _GetBaseQuery (). принимается массив Soptions, который содержит требуемые параметры запроса к базе данных. Никакие дополнительные параметры в теле GetPostsCount () не задаются, поэтому массив передается дальше в функцию _GetBaseQuery ().

В листинге 8.2 приведен текст метода GetPostsCount {), находящегося в файле BlogPost .php из каталога ./include/DatabaseObject. Поскольку таблица, из кото­рой будут извлекаться данные, уже задана в GetBaseQuery (), в метод from () пере­даются аргумент null вместо таблицы и столбец, который нужно из нее извлечь, — в данном случае count (*), так как мы подсчитываем количество строк. Затем с по­мощью функции fetchOne () доставляется первый элемент первой возвращенной строки, который в данном случае будет общим количеством найденных строк.

Листинг 8.2. Метод GetPostsCount О для определения общего количества возвращаемых СТрОК (файл BlogPost .php)

 

<?php

class DatabaseObject_BlogPost extends DatabaseObject // ... другой код

public static function GetPostsCount ($db, $options) {

$select ■ self:: GetBaseQuery ($db, $options);

$select->from (null, 'count (*)');

return $db->fetchOne ($select)!

}

private static function GetBaseQuery ($db, $options) {

// ... другой код

>>

Метод GetPosts ()

 

Имея представление о работе функций GetBaseQuery () и GetPostsCount (). можно приступать к разработке функции GetPosts (). Ее идея состоит в построе­нии запроса с помощью метода GetBaseQuery () и добавлении к нему необходи­мых полей для выбора. Еще в этой функции, в отличие от GetBaseQuery (}, выпол­няются такие важные задачи, как задание смешения, максимального количества за­писей и упорядочения. Поскольку эти параметры не имеют отношения к функции GetPostsCount (), их нужно задавать именно здесь, а не в _GetBaseQuery ().

Функция разбита на три части для удобства анализа. В листинге 8.3 приведена первая ее часть.

Листинг 8.3. Первая часть функции GetPosts () из трех (файл BlogPost .php)

 

<?php

class DatabaseObject BlogPost extends DatabaseObject

{

// ... другой код

public static function GetPosts ($db, $options = array ()) {

// инициализация параметров

$defaults = array ( •offset' => 0, 'limit' => 0, ■order' => 'p.ts created'

) ;

foreach ($defaults as $k => $v) {

$options[$k] = arraykeyexists ($k, $options) ? $options[$k] : $v;

}

$select = self:: GetBaseQuery ($db, $options);

// поля для выбора

$select->from (null, 'p.*');

// смещение, максимум и упорядочение результатов if ($options ['limit'] > 0)

$select->limit ($options['limit' ] , $options['offset' ] ) ;

$select->order ($options['order' ] ) ;

 

 

Примечание

Использование класса zend_Db_Select позволяет создавать запросы, работающие на разных серве­рах баз данных. Например, в MySQL для ограничения множества результатов используется команда limit х, у или limit х offset у, а в PostgreSQL- offset х limit у (где х- смещение, у —максимум).

Следующий наш шаг — выполнение запроса к базе данных и формирование массива объектов DatabaseObject BlogPost, который молено вюзвратить из функ­ции. Для получения данных из базы и записи их в массив используется метод $db- >fetchAll. Поскольку один экземпляр подкласса, производного от DatabaseObject (например, класса DatabaseObject_BlogPost). соответствует одной записи базы данных, нам понадобится несколько экземпляров: по одному для каждой строки, возвращаемой по только что построенному запросу SQL.

Для формирования массива воспользуемся статическим вспомогательным ме­тодом BuildMultiple О из класса DatabaseObject. В этот метод мы передаем имя класса (DatabaseObject_BlogPost, для динамического генерирования которого можно использовать подстановочное имя_______________________ CLASS__ ). а также сами данные для по­строения массива объектов. Все это показано в листинге 8.4. Ключ каждого эле­мента в массиве соответствует значению поля post_id.

Листинг 8.4. Создание массива объектов DatabaseObj ect BlogPost (файл BlogPost .php)

// загрузка данных из базы $data = $db->fetchAll ($select);

// преобразование данных в массив объектов DatabaseObject_BlogPost

$posts = self::BuildMultiple ($db, ____________ CLASS_ , $data);

$post_ids = array_keys ($posts);

if (count ($post_ids) == 0)

return array () ;_____________________________________________ _________ _

Процесс организации данных, полученных из базы, в GetPosts () почти завер­шен. Однако в предыдущих примерах использования экземпляров класса Data­baseOb jectBlogPost к каждому такому объекту быт прикреплен объект Рго- f ile BlogPost в виде свойства $prof ile. Поскольку все важнейшие данные запи­сей блога (например, заголовок и собственно текст) помещаются в профиле, для каждой заметки блога необходимо загрузить ее профиль.

После нормальной загрузки записи посредством объекта DatabaseObject авто­матически вызывается метод postLoad (). Поскольку при этом для каждой записи выполняется SQL-запрос (это необходимо для загрузки профиля), необходимо най­ти более экономичное решение. Воспользуемся методом из моего собственного класса Profile, который создает несколько экземпляров Profile. Благодаря этому будет выполняться только один оператор SQL, а не по одному на каждую запись блога, для которой загружается профиль.

Для получения массива объектов Prof ile_BlogPost применим метод Build­Multiple () класса Profile. Первый его аргумент— соединение с базой данных, второй— используемый подкласс Profile, а третий содержит идентификаторы загружаемых записей. Фактически это то же самое, что сделать вызов $prof ile = new ProfileBlogPost ($db, $post_id) по одному разу для каждой записи блога.

Наконец, нужно установить соответствие между объектами Prof ileBlogPost и соответствующими им объектами DatabaseObject BlogPost. Как в массиве $prof iles, так и в массиве $posts ключ соответствует полю post_icl.

Для нахождения соответствия между каждым профилем и соответствующей за­писью блога мы перебираем каждую запись в цикле и ищем для нее профиль. Если профиль найти не удается, то просто вызывается метод setPostld () . чтобы при не­обходимости можно было выполнять запись в профиль под этим идентификатором (переменная-свойство profile является экземпляром класса Profile_BlogPost в конструкторе класса DatabaseOb j ect_BlogPost).

В листинге 8.5 приведено окончание метода GetPosts (). Этот код, как и предыду­щие части, находится в файле BlogPost. php из каталога . /include/Da tabaseOb j ect.

Листинг 8.5. Загрузка данных профиля для каждой записи блога (файл BlogPost .php)

// загрузка данных профиля для уже полученных записей блога $proflies = Profile::BuildMultiple ( $db,

 

'ProfileBlogPost•,

array ('postid' => $post_ids)

) г

foreach ($posts as $post_id => $post) { if (arraykeyexists ($post_id, $profiles)

&& $profiles[$post_id] instanceof Profile BlogPost) { $posts[$post_id]->profile — $profiles[$post_id] ;

}

else {

$posts[$post_id]->profile->setPostId ($post_id) ;

}

}

return $posts;

}

// ... другой код

}

?>

Получение сводки блога за месяц

В качестве дополнительной функции реализуем метод GetMonthlySummary (), ко­торый будет возвращать сводку по количеству заметок в блоге за каждый месяц. И снова воспользуемся массивом $options, передаваемым в метод _GetBaseQuery (), благодаря чему можно будет в будущем легко расширить возможности функции.

Функция несколько отличается от GetPosts () и GetPostsCount (), поскольку результаты группируются по году и месяцу каждой заметки.

В листинге 8.6 приведен код функции GetMonthlySummary (), в котором с при­менением класса Zend Db Select строится запрос, а затем вызывается метод f etchPairs () для создания ассоциативного массива с первым столбцом (год и ме­сяц) в качестве ключа и вторым (количество записей) — в качестве значения.

Листинг 8.6. Построение SQL-запроса и загрузка данных о записях блога (файл BlogPost .php)

 

<?php

class DatabaseObject BlogPost extends DatabaseObject

{

// ... другой код

public static function GetMonthlySummary ($db, $opfcions) {

if ($db instanceof Zend Db AdapterPdoMysql)

$dateString = «dateformat (p.ts created, 1 %Y-%m')»j else

$dateString = «tochar (p.ts created, 'yyyy-mm*)»;

// инициализация параметров $defaults = array (

•offset' => о, 'limit' => О,

'order' => $dateString . 1 desc'

) ;

foreach ($defaults as $k -> $v) {

$options[$k] — array key exists ($k, $options) ? $options t$k] : $v;

}

$select = self:: GetBaseQuery ($db, $options);

$select->from (null,

array ($dateString . ' as month', 'count (*) as num_posts'));

$select->group ($dateString)}

$select->order ($options['order'3);

return $db->fetchPairs ($select) ;

}

// ... другой код

}

?>

 

 

После вызова GetBaseQuery () в запрос добавляются требуемые поля. Для вы­полнения запроса вызывается метод f etchPairs (). Этот метод возвращает массив строк, полученных по SQL-запросу. Первый выбранный столбец таблицы он ис­пользует как ключ массива, а второй — как элемент массива.

В этом коде ключом массива является временная метка, а значением — коли­чество записей за соответствующий месяц. Строка формата, передаваемая в функ­цию MySQL date format (), генерирует временную метку в виде ГГГГ-ММ — например, для ноября 2007 года поле месяца будет возвращаться в виде 2007-11.

Примечание

 

Функция date_format О специфична для MySQL и не будет работать в других СУБД. Такие системы, как PostgreSQL, используют вместо нее функцию to char (), вот почему в листинге 8.6 приходится про­верять, в какой среде мы находимся.

Далее необходимо сгруппировать данные по значению «год/месяц», а затем за­давать параметры упорядочения.

Примечание

*

Если в MySQL присвоить вызову функции псевдоним столбца таблицы (как это сделано у нас, где функция date_month () используется в качестве поля month), то в других частях оператора SQL можно будет ссылаться непосредственно на псевдостолбец month; в нашем случае это условия group by и or­der by. В других системах управления базами данных такой синтаксис не работает — в каждом нужном месте следует явно вписывать вызов функции. Вот почему вызов функции у нас присваивается переменной ($dateString).

  • Digg
  • Del.icio.us
  • StumbleUpon
  • Reddit
  • Twitter
  • RSS

Оставить комментарий

This blog is kept spam free by WP-SpamFree.