YiiTalk подкаст о Yii фреймворке, или разговоры ни о чем ;)

Совсем не давно Юрий Беляков(belyakov.su) начал серию подкастов по Yii фреймворку. Изначально была идея вести это дело совместно, но в силу различных обстоятельств, которые как обычно не зависят от нас ;) не получилось. Это наверное и к лучшему, двум ведущим там делать не чего :).

Был записан пилотный выпуск, затем в скором времени подкаст с Александром Макаровым(rmcreative.ru), кто не знает это наш соотечественник, который участвует в разработке ядра Yii. Ну и третий выпуск был записан со мной, до сих пор не понимаю чем удостоен такой чести ;). В плане запись подкаста с одним из активных участником yii сообщества Ekstazi.

Послушать все это дело можно здесь: yiitalk.podfm.ru yiitalk.rpod.ru

Хочу добавить к тому что было сказано в подкасте, а именно про книги. Как мне было подмечено по пхп стоит почитать: Котерова PHP5(у меня есть такая книга, читал давно и просто забыл уже про нее.) и PHP. Объекты, шаблоны и методики программирования(ее не читал, но все говорят хорошая).

З.Ы. не забывайте писать комменты, фитбэки. Для нас это очень важно, чтобы понять куда двигаться дальше ;)

С Новым 2012 Годом!

Хочу всех поздравить с наступающим 2012 годом! 2011году осталось совсем чуть-чуть и можно подвести его итоги. Не считая, что на улицах Питера снега нет и у большинства нет новогоднего настроения, 2011 в целом удался))
Начало года было не очень удачным, были финансовые проблемы. Полностью перешел на фриланс и повсеместное использование Yii. Начал писать свою CMS для управления сайтов своих клиентов. Втянулся в пару авантюрных проектов, не все оказались удачными. Многие отвалились по мере работы над ними. Некоторые дожили до конца года, посмотри что им принесет будущий 2012 год.
Осень 2011 стала очень неожиданной, было много работы, несколько проектов, которые помогли моей CMS подрасти и обзавестись не только стандартными функциями типа управления статическими страницами, новости, сео, общие настройки системы, но и каталогом с конструктором товаров.
Под конец года обрисовался один стартап и даже вроде инвестор вместе с ним. Так что посмотрим что принесет 2012 год)

Ну а в конце хочу пожелать всем здоровья, удачи, успехов в работе и личной жизни. А так же чтобы сбылись все поставленные цели.

ВСЕХ С НОВЫМ ГОДОМ!

Использование CDbCriteria в DAO

Не знал какое название дать посту, получилось, что получилось.
Очередной рабочий вечер, работа над интернет-магазином. Для уменьшения нагрузки отказываюсь в некоторых местах от использования AR. Естественно для выборки данных с использованием AR использую класс CDbCriteria. Но отказываясь от AR приходится отказаться и от CDbCriteria. Решил "поплакаться" на этот счет одному из знакомых разработчиков, который подсказал поковырять фреймворк и посмотреть действительно ли необходимо отказаться от CDbCriteria, когда не используешь AR. И как оказалось все не так плохо, иначе бы этот пост и не появился ;).
Ну а теперь к практическому решению:
$criteria = new CDbCriteria;
// идет некий сумасшедший сбор критерии.
...
// Дальше необходимо создать экземпляр класса CDbCommandBuilder
$builder = new CDbCommandBuilder(Yii::app()->db->getSchema());
// Дальше если необходимо сделать выборку, то используем createFindCommand
// Если что-то удалить, то createDeleteCommand и т.п.
// Первым параметром передаем название таблицы или объект CDbTableSchema
$command = $builder->createFindCommand('product', $criteria);
// дальше можно использовать queryColumn(), queryAll() и т.п.
$productIds = $command->queryColumn();
Возможно это слишком низкоуровневое решение, но оно работает.

Игры в модуль, подмодуль в Yii

Поскольку Yii дает возможность из коробки использовать "любую" вложенность модулей, я решил опробовать написать админку в виде модуля и подмодулей.
Единый лейаут для модуля и его подмодулей.
Возникла логичная необходимость в том, что леайт модуля должен использоваться и в его подмодулях. Сделал я это следующим образом:
class AdminModule extends BaseWebModule {
public function init() {
Yii::app()->setViewPath($this->getViewPath());
}
}
Генерация ссылок в подмодуль.
Из коробки ий может генерировать ссылки в в подмодуль только по абсолютному пути:
$this->createUrl('/module/submodule/controller/action')

НО! Возникают проблемы при подключение в подмодулях CGridView и т.п. Для этого я создал базовый контроллер для всех контроллеров в AdminModule и его подмодулей, переопределив метод CController.createUrl следующим образом:
    public function createUrl($route,$params=array(),$ampersand='&') {
if($route==='')
$route=$this->getId().'/'.$this->getAction()->getId();
else if(strpos($route,'/')===false)
$route=$this->getId().'/'.$route;
if($route[0]!=='/') {
if(($module=$this->getModule())!==null) {
$route = $module->getId().'/'.$route;
$route = $this->_getParentModuleRecursive($module, $route);
}
}

return Yii::app()->createUrl(trim($route,'/'),$params,$ampersand);
}

private function _getParentModuleRecursive(CModule $module, $route) {
if(($parentModule = $module->getParentModule()) !== null) {
$route = $parentModule->getId().'/'.$route;
$this->_getParentModuleRecursive($parentModule, $route);
}
return $route;
}
По мере моего изучения возможности Yii - модуля, подмодуля буду пополнять данную заметку различной информацией.

Сравнение двух phpinfo()

Часто для разработки используются один сервер, а рабочий проект лежит на другом сервере и возникает необходимость чтобы настройки среды были одинаковы. Чтобы сравнить настройки php есть такая простая функция, которую все разработчики узнают одну из первых - phpinfo(). Но настроек там много и сравнивать все флаги глазами ни очень приятно, а если еще не знаешь, что ищешь, то вообще беда. Не много погуглив нашел решение, которое удовлетворяет поставленной задаче вполне - сравнить два phpinfo. Теперь просто приведу код:
На 1ом сервере, создаем файл и пишем в него следующий код:
<?php echo serialize(ini_get_all()); ?>

На 2ом сервере, создаем файл и пишем не много более "сложный" код:
<?php 
function ini_flatten($config) {
$flat = array();
foreach ($config as $key => $info) {
$flat[$key] = $info['local_value'];
}
return $flat;
}
function ini_diff($config1, $config2) {
return array_diff_assoc(ini_flatten($config1), ini_flatten($config2));
}
$config1 = ini_get_all();
$export_script = 'http://servername1.ru/filename.php';
$config2 = unserialize(file_get_contents($export_script));
$diff = ini_diff($config2, $config1);
?>
<pre><?php print_r($diff) ?></pre>

На выходе получаем массив, такого виду:
Array
(
[error_log] => /work/www/project.loc/log/php.log
[expose_php] => 1
[extension_dir] => /usr/lib/php5/ext/
[include_path] => .:/usr/share/php:/usr/share/pear
[max_execution_time] => 30
[open_basedir] =>
[safe_mode] =>
[session.gc_probability] => 1
[session.save_path] => /work/www/project.loc/sessions
[upload_tmp_dir] => /work/www/project.loc/tmp
[xdebug.auto_trace] => 0
[xdebug.collect_assignments] => 0
[xdebug.collect_includes] => 1
)

Переезд блога на hetzner

Еще с начала мая начал использовать хостинг от Hetzner. Потихоньку переносил все проекты на новый хостинг. Последним был мой блог. Раньше пользовался sweb'ом, но потом потребности возросли и решил переехать на что-то по мощнее и выбор пал на hetzner.de. Хорошие тарифы за небольшие деньги. За 4месяца работы падений не было, да и вообще проблем со стороны хостинга не было. Знаний по администрированию у меня конечно не хватает, но даже с ними и помощью друзей приятелей и какой-то матери, удалось довести все до рабочего состояния))

Вышел Yii 1.1.8

Вышел Yii 1.1.8. По словам разработчиков внесено более 80 улучшений, изменений и фиксов багов. Из интересного:
  • Использование классов в правилах роутинга в urlManager'е
  • Улучшены автозагрузчик
  • Отправка сообщений в лог в реальном времени
  • Работаем со счётчиками в БД через AR
    Сомнительная фишка добавленная в AR, которая даже вызвала некоторые споры.
  • В clips теперь можно использовать параметры

  • Анонс можно прочитать на русском здесь.

Plesk заменяем Horde на Roundcube

Приобрел себе панель управления хостингом Plesk 10.2.0. Пока все устраивает, кроме встроенного почтового веб-клиента Horde. Показался он мне морально устаревшим и перегруженным функционалом. Не долго выбирая из возможных клиентов остановился на Roundcube. Мне не удалось найти внятной информации на родном сайте plesk'а как к нему прикрутить другой веб-клиент, так же как и на сайте самого roundcube. Конечно не отрицаю, что может плохо искал)) По сети информацию можно найти обрывками. Вообщем от слов к делу.
Входные данные:
  • Debian 6
  • Plesk 10.2.0
  • У вас есть root доступ к серверу по ssh
  • И помните что вы все делаете на свой страх и риск

Начнем с настройки Roundcube.
1. Заходим в Plesk под администратором.
2. Переходим Управление Серверов - Инструменты и утилиты - Серверы базы данных
3. Кликаем на иконку Вход на этот сервер через DB Webamin. Откроется phpMyAdmin.
4. Создаем БД для Roundcube, я назвал roundcube
5. Идем на сайт http://roundcube.net/ и скачиваем последнию версию. На данный момент это 0.5.3
6. Распаковываем архив, у меня это папка roudcubemail
7. Находим roudcubemail/SQL в ней открываем mysql.initial.sql и выполняем увиденный SQL в нашей БД roundcube.
8. В phpMyAdmin кликаем на иконку домика. Дальше вкладка Привелегии и Добавить нового пользователя. Заполните поля Имя пользователя: username Хост: localhost Пароль: pass Подтверждение: pass. Отметьте все привилегии. Эти данные потом надо будет указать в конфиге roundcube.
9. Создаем файл roudcubemail/config/db.inc.php с следующим содержимым:
<?php
/*
+-----------------------------------------------------------------------+
| Configuration file for database access |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
*/
$rcmail_config = array();

$rcmail_config['db_dsnw'] = 'mysql://username:pass@localhost/roundcube';
$rcmail_config['db_dsnr'] = '';

$rcmail_config['db_max_length'] = 512000;
$rcmail_config['db_persistent'] = FALSE;

$rcmail_config['db_table_users'] = 'users';
$rcmail_config['db_table_identities'] = 'identities';
$rcmail_config['db_table_contacts'] = 'contacts';
$rcmail_config['db_table_session'] = 'session';
$rcmail_config['db_table_cache'] = 'cache';
$rcmail_config['db_table_messages'] = 'messages';

$rcmail_config['db_sequence_users'] = 'user_ids';
$rcmail_config['db_sequence_identities'] = 'identity_ids';
$rcmail_config['db_sequence_contacts'] = 'contact_ids';
$rcmail_config['db_sequence_cache'] = 'cache_ids';
$rcmail_config['db_sequence_messages'] = 'message_ids';

В $rcmail_config['db_dsnw'] указываем свои данные.
10. Создаем файл roundcubemain/config/main.inc.php
<?php
/*
+-----------------------------------------------------------------------+
| Configuration file for database access |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, Roundcube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
+-----------------------------------------------------------------------+
*/
$rcmail_config = array();

$tmp = strtolower($_SERVER['HTTP_HOST']);
$tmp = str_replace('www.', '', $tmp);
$tmp = str_replace('webmail.', '', $tmp);
$rcmail_config['real_host_name'] = $tmp;
$rcmail_config['real_mail_host'] = 'mail.' . $tmp;
unset($tmp);

$rcmail_config['default_host'] = $rcmail_config['real_mail_host'];
$rcmail_config['default_port'] = 143;

$rcmail_config['username_domain'] = ''; // $rcmail_config['real_host_name'];
$rcmail_config['mail_domain'] = ''; // $rcmail_config['real_host_name'];

$rcmail_config['smtp_server'] = $rcmail_config['real_mail_host'];
$rcmail_config['smtp_port'] = 25;
$rcmail_config['smtp_user'] = '%u';
$rcmail_config['smtp_pass'] = '%p';
$rcmail_config['smtp_auth_type'] = '';
$rcmail_config['smtp_helo_host'] = '';
$rcmail_config['smtp_log'] = TRUE;

$rcmail_config['imap_auth_type'] = null;
$rcmail_config['imap_root'] = null;
$rcmail_config['imap_delimiter'] = null;

$rcmail_config['include_host_config'] = FALSE;
$rcmail_config['sendmail_delay'] = 30;

$rcmail_config['debug_level'] = 1;
$rcmail_config['log_dir'] = 'logs/';
$rcmail_config['log_date_format'] = 'd-M-Y H:i:s O';
$rcmail_config['log_driver'] = 'file';
$rcmail_config['log_logins'] = FALSE;
$rcmail_config['syslog_id'] = 'roundcube';
$rcmail_config['syslog_facility'] = LOG_USER;

$rcmail_config['temp_dir'] = 'temp/';
$rcmail_config['plugins_dir'] = 'plugins/';
$rcmail_config['plugins'] = array(
'emoticons',
'markasjunk'
);

$rcmail_config['enable_caching'] = TRUE;
$rcmail_config['message_cache_lifetime'] = '14d';
$rcmail_config['list_cols'] = array('subject', 'from', 'date', 'size', 'flag', 'attachment');
$rcmail_config['skin_include_php'] = FALSE;

$rcmail_config['auto_create_user'] = TRUE;
$rcmail_config['des_key'] = '9IDJVL8%nf6%RTgOwpyfkuDv';
$rcmail_config['double_auth'] = FALSE;
$rcmail_config['ip_check'] = FALSE;
$rcmail_config['keep_alive'] = 60;
$rcmail_config['min_keep_alive'] = 60;
$rcmail_config['session_lifetime'] = 10;
$rcmail_config['session_domain'] = '.' . $rcmail_config['real_host_name'];
$rcmail_config['virtuser_file'] = '';
$rcmail_config['virtuser_query'] = '';

$rcmail_config['language'] = null;
$rcmail_config['date_short'] = 'D H:i';
$rcmail_config['date_long'] = 'd.m.Y H:i';
$rcmail_config['date_today'] = 'H:i';
$rcmail_config['useragent'] = 'RoundCube Webmail/0.3-beta';
$rcmail_config['quota_zero_as_unlimited'] = TRUE;
$rcmail_config['create_default_folders'] = TRUE;
$rcmail_config['check_all_folders'] = FALSE;
$rcmail_config['default_imap_folders'] = array('INBOX', 'Drafts', 'Sent', 'Junk', 'Trash');
$rcmail_config['protect_default_folders'] = TRUE;
$rcmail_config['drafts_mbox'] = 'Drafts';
$rcmail_config['junk_mbox'] = 'Junk';
$rcmail_config['sent_mbox'] = 'Sent';
$rcmail_config['trash_mbox'] = 'Trash';
$rcmail_config['delete_always'] = FALSE;
$rcmail_config['default_charset'] = 'ISO-8859-1';
$rcmail_config['enable_spellcheck'] = TRUE;
$rcmail_config['spellcheck_engine'] = 'googlie';
$rcmail_config['spellcheck_languages'] = NULL;
$rcmail_config['spellcheck_uri'] = '';
$rcmail_config['address_book_type'] = 'sql';
$rcmail_config['autocomplete_addressbooks'] = array('sql');
$rcmail_config['ldap_public'] = array();
$rcmail_config['dont_override'] = array();

$rcmail_config['enable_installer'] = FALSE;
$rcmail_config['generic_message_footer'] = '';
$rcmail_config['http_received_header'] = FALSE;
$rcmail_config['http_received_header_encrypt'] = FALSE;

$rcmail_config['identities_level'] = 0;
$rcmail_config['mail_header_delimiter'] = NULL;
$rcmail_config['max_pagesize'] = 500;

$rcmail_config['mdn_requests'] = 0;
$rcmail_config['mime_magic'] = '/usr/share/misc/magic';
$rcmail_config['product_name'] = 'RoundCube WebMail';
$rcmail_config['skin'] = 'default';

$rcmail_config['draft_autosave'] = 300;
$rcmail_config['dst_active'] = (bool)date('I');
$rcmail_config['flag_for_deletion'] = FALSE;
$rcmail_config['focus_on_new_message'] = TRUE;
$rcmail_config['htmleditor'] = TRUE; // FALSE;
$rcmail_config['inline_images'] = TRUE;
$rcmail_config['logout_expunge'] = FALSE;
$rcmail_config['logout_purge'] = FALSE;
$rcmail_config['message_sort_col'] = 'date';
$rcmail_config['message_sort_order'] = 'DESC';
$rcmail_config['mime_param_folding'] = 0;
$rcmail_config['pagesize'] = 50;
$rcmail_config['prefer_html'] = TRUE;
$rcmail_config['prettydate'] = FALSE; // TRUE;
$rcmail_config['preview_pane'] = TRUE; // FALSE;
$rcmail_config['read_when_deleted'] = TRUE;
$rcmail_config['show_images'] = 2; // 0;
$rcmail_config['skip_deleted'] = FALSE;
$rcmail_config['timezone'] = 'auto';
?>

11. Теперь необходимо закачать содержимое папки roudcubemail на сервер в папку /usr/share/psa-horde. Перед этим сохраните папку psa-horde или переименуйте в _psa-horde чтобы можно было откатится если что-то пойдет не так.
12. Если все правильно было сделано, то теперь webmail.yourdomain.prefix будет открываться roundcube.

Но если у вас появилась ошибка, как у меня:
Fatal error: ini_set/set_include_path does not work.

То необходимо еще потанцевать с бубном)) Для того чтобы избавится от этой ошибки, необходимо найти на сервере /etc/apache2/conf.d/zz***httpd.conf там будут строкa /opt/psa/admin/conf/generated/*****_horde.include
В данном файле необходимо изменить значения php_admin_value include_path.
php_admin_value include_path "/usr/share/psa-horde/program:/usr/share/psa-horde/program/lib:
/usr/share/psa-horde/program/include:/usr/share/psa-horde:/usr/share/psa-horde/lib:
/usr/share/psa-pear:."

Ну и последний штрих идем в /usr/share/psa-horde/program/include/iniset.php
/* Комментируем этот код
if (set_include_path($include_path) === false) {
die("Fatal error: ini_set/set_include_path does not work.");
}
*/
...
// убираем первый слыш
require_once 'program/include/main.inc';
require_once 'program/include/rcube_shared.inc';

Надеюсь это кому-нить поможет))

CListView пример использования

Недавно в конференции были вопросы по CListView. Поэтому решил написать небольшой пример использования данного класса. Он предназначен для вывода сущностей(в частности моделей) в виде списка(упорядоченных блоков). Допустим у нас товары и их надо вывести в категории, по 12штук на странице с возможность сортировки по названию и цене товара. Код моделей опущу, т.к. он прост и не принципиален.
Контроллер отвечающий за вывод категории, ни чего сложно. Принимает параметром $alias - псевдоним категории. Производит простые манипуляции, получает данные от модели и отдает их в отображение. Все остальные значимые пометки сделал в коде.
CategoriesController.php
public function actionView($alias) {
$category = Category::model()->getByAlias($alias);

if(!$category)
throw new CHttpException(404, 'Такой страницы не существует.');

// заполняем заголовок страницы, хлебные крошки,
// устанавливаем различные значения для СЕО и т.п.
...
// формируем критерий для выборки товаров
$criteria = new CDbCriteria();
$criteria->condition = 'category_id = :categoryId';
$criteria->params = array(':categoryId'=>$category->id);
// создаем экземпляр CSort для сортировки в CGridView,
// можно так же описать простым массивом
$sort = new CSort();
// имя $_GET параметра для сортировки,
// по умолчанию ModelName_sort
$sort->sortVar = 'sort';
// сортировка по умолчанию
$sort->defaultOrder = 'good.final_price ASC';
// включает поддержку мультисортировки,
// т.е. можно отсортировать сразу и по названию и по цене
$sort->multiSort = true;
// здесь описываем аттрибуты, по которым будет сортировка
// ключ может быть произвольный, это будет $_GET параметр
$sort->attributes = array(
'title'=>array(
'label'=>'названию',
'asc'=>'good.title ASC',
'desc'=>'good.title DESC',
'default'=>'desc',
),
'price'=>array(
'asc'=>'good.final_price ASC',
'desc'=>'good.final_price DESC',
'default'=>'desc',
'label'=>'цене',
),
);
$dataProvider = new CActiveDataProvider(Good::model()->active()->with('category'),
array(
'criteria'=>$criteria,
'sort'=>$sort,
'pagination'=>array(
'pageSize'=>12,
),
)
);
$this->render('view', array('model'=>$category, 'dataProvider'=>$dataProvider));
}

Представление view.php:
<?php $this->widget('zii.widgets.CListView', array(
'dataProvider'=>$dataProvider,
'itemView'=>'_good', // представление для одной записи
'ajaxUpdate'=>false, // отключаем ajax поведение
'emptyText'=>'В данной категории нет товаров.',
'summaryText'=>"{start}&mdash;{end} из {count}",
'template'=>'{summary} {sorter} {items} <hr> {pager}',
'sorterHeader'=>'Сортировать по:',
// ключи, которые были описаны $sort->attributes
// если не описывать $sort->attributes, можно использовать атрибуты модели
// настройки CSort перекрывают настройки sortableAttributes
'sortableAttributes'=>array('title', 'price'),
'pager'=>array(
'class'=>'CLinkPager',
'header'=>false,
'cssFile'=>'/css/pager.css', // устанавливаем свой .css файл
'htmlOptions'=>array('class'=>'pager'),
),
)); ?>
Представление _good.php не буду приводить, т.к. в нем нет ни чего сложно. В нем доступен объект $data. Вот и весь пример использования.

Алиасы моделей в Yii

При работе с моделями, yii сам формирует алиасы для таблиц. Для обычных запросов это t0, t1, t2, t3 и т.п. В отношениях алиас для связанной таблицы - это название отношения. Но для меня это не очень удобно, т.к иногда взглянув на сложный запрос в профайлере сложно понять что к чему. Поэтому я устанавливаю для всех моделей алиас равный названию таблицы. Казалось бы просто в методе init() или __construct() нужно вызвать setTableAlias() и передать ему $this->tableName(), но почему у меня так не удалось сделать, я не стал с этим разбиратся и устанавливаю алиас таблицы через defaultScope() моей базовой модели.
class BaseModel extends CActiveRecord { 
...
public function defaultScope() {
return array(
'alias' => $this->tableName(),
);
}
...
}

Самое главное помнить, что если вы захотите использовать в какой-то модели defaultScope(), то надо смержить с базовым чтобы не утратить данное поведение, CMap::mergeArray() в помощь)