среда, июля 15, 2009

Полезные заметки по symfony - 2

Если надо использовать функции из View в Controller, то надо вопользоваться sfLoader

class blogActions extends sfActions
{
public function preExecute(){
...
}
public function executeRedirect(sfWebRequest $request)
{
sfLoader::loadHelpers('Url');
$this->redirect(url_for('@archive_blog?id='.$this->currentBlog->getId(), 301);
}
}


Но лучше использовать внутренние средства, в symfony почти для каждого из составляющих MVC если дублирующие средства:

class blogActions extends sfActions
{
public function preExecute(){
...
}
public function executeRedirect(sfWebRequest $request)
{
sfLoader::loadHelpers('Url');
$this->redirect(
$this->getController()->genUrl('@archive_blog?id='.$this->currentBlog->getId()),
301
);
}
}


Исключение составляет вызов action из View, как например в ZendFramework, но в symfony похоже это не трубуется (прим. автора: по крайней мере в моей практике).

среда, июля 08, 2009

Как добавить в форму admin генератора ссылку

К примеру у вас есть backend (admin) и вам нужно поставить ссылку на какой нибудь ресурс.
Для этого нужно создать свой виджет, например, такой:



class sfWidgetFormHtmlLink extends sfWidgetForm
{

protected function configure($options = array(), $attributes = array())
{
$this->addOption('url', false);
$this->addOption('target', 'self'); // self, blank
$this->addOption('route', false);
$this->addOption('name', true);
}

public function render($name, $value = null, $attributes = array(), $errors = array())
{
if($this->getOption('route')){
return link_to(
$this->getOption('name'),
$this->getOption('route'),
array('target'=>'_'.$this->getOption('target'))
);
}

if($this->getOption('url')){
return "<a href='".$this->getOption('name').
"' target='_".$this->getOption('target')."'>".
$this->getOption('name')."</a>";
}

}
}


Теперь его просто использовать:

class SomeForm extends BaseSomeForm
{

public function configure()
{
$this->setWidget('external_link',new sfWidgetFormHtmlLink(array('url'=>'http://ya.ru','name'=>'Поискать')));
}
}


Меняя метод render можно создать свой виджет по выводу какого-либо html текста и т.д.

среда, июня 17, 2009

Полезные заметки по symfony - 1

Установка meta html данных:

class SomeObjectActions extends sfActions
{
public function executeShowObject(sfWebRequest $request){
...
$this->getResponse()->setTitle($this->object->getMetaTitle());
$this->getResponse()->addMeta('meta_keywords', $this->object->getMetaKeywords());
$this->getResponse()->addMeta('meta_description', $this->object->getMetaDescription());
}
}

Правильное использование sfWidgetFormTextareaTinyMCE

sfWidgetFormTextareaTinyMCE - это виджет, HTML WYSIWYG editor. Очень удобная вешь, но в блоге symfony очень скудно описан принцип работы, точнее в последней версии возникает ошибка:

You must pass a "class" attribute for a TinyMCE widget

Вот, как и какой класс надо указывать:

$this->setWidget('text', new sfWidgetFormTextareaTinyMCE(array(
'width' => 650,
'height' => 550,
'config' => 'theme_advanced_disable: "anchor,image,cleanup,help"',
)
,array(
'class' => 'tinyMCE',
)
));


в генераторе (если вы используете его в backend) generator.yml укажите class в атрибутах

config:
actions: ~
fields:
text: { label: 'Текст', attributes: { class: "tinyMCE" } }


еще обсуждение здесь

вторник, июня 16, 2009

Как заставить mysql5 использовать нужный вам default-character и collation

Для начала посмотрите что у вас есть


SHOW VARIABLES LIKE 'character_set%';
+--------------------------+----------------------------+
| Variable_name | Value |
+--------------------------+----------------------------+
| character_set_client | utf8 |
| character_set_connection | utf8 |
| character_set_database | utf8 |
| character_set_filesystem | binary |
| character_set_results | utf8 |
| character_set_server | utf8 |
| character_set_system | utf8 |
| character_sets_dir | /usr/share/mysql/charsets/ |
+--------------------------+----------------------------+
8 rows in set (0.00 sec)


По умолчанию Value=latin1

Теперь вы хотите чтобы все клиенты mysql сразу использовали нужную ва кодировку:
utf8,cp1251 или koi8r

Нужно добавить в файл my.cnf
/etc/mysql/my.cnf

Слудущие переменные:

[client]
default-character-set=utf8
[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci
[mysql]
default-character-set=utf8


После изменений перезагружайте сервер.
/etc/init.d/mysql restart

Если не работает еще раз проверьте что у вас происходит:

SHOW VARIABLES LIKE 'character_set%';


проверьте кодировку базы данных:

mysql> show create database yourdatabase;
+-------------+----------------------------------------------------------------------+
| Database | Create Database |
+-------------+----------------------------------------------------------------------+
| yourdatabase | CREATE DATABASE `yourdatabase` /*!40100 DEFAULT CHARACTER SET utf8 */ |
+-------------+----------------------------------------------------------------------+
1 row in set (0.00 sec)


Кодировка по умолчанию для создания таблиц наследуется.

Можно еще проще для ubuntu (10.04):
создать файл, например /etc/mysql/conf.d/mysqld_charset.cnf
с текстом:

[mysqld]
default-character-set=utf8
default-collation=utf8_general_ci
character-set-server=utf8
init-connect='SET NAMES utf8;'
collation-server=utf8_general_ci

[mysql]
default-character-set=utf8


и перезапустить mysql

пятница, мая 08, 2009

Полезные команды для web-разработчика

Искать любое упоминание чего-либо в проекте, исключая системные svn файлы:
grep -Rn 'mysql.php' ./ | grep -v svn

Перенести изменения из одного проекта в другой методом патча:
project1 $ svn diff > filename.patch
project2 $ patch -p0 -i ../project1/filename.patch

Собрать файлы в архив по определенному принципу, в данном случае измененные и добавленные файлы:
perl -e '$res = `svn stat|grep -E "^[AM]"`; $res =~ s/\+|^\w+\s*//gm; $res =~ s/\n/ /g; `tar cfz 1.tgz $res`;'


В буду дальнейшем буду пополнять, есть что то вспомню.

четверг, мая 07, 2009

Google I/O

Как многим разработчикам на Android или просто интерисующимся уже известно, что в 27-28 мая в Сан-франциско пройдет конференция посвященная разработке под Google.Android.
Будет затронуто очень много интересных тем, но самой интересной для меня будет тема посвященная разработке игр.
А именно Chris Pruett расскажет о  Writing Real-Time Games for Android, т.к. я сейчас сам разрабатываю игру, о которой скоро расскажу.
Он болжен будет рассказать о фреймворке, который он разрабатывает специально для разработчиков игр. К тому же расскажет о новых возможностях работы с OpenGLES появившихся в AndroidSDK1.5
Немного поискав я нашел (svn) что-то, что разрабатывает Chris, но не уверен, что это именно тот фреймворк, о котором шла речь.
Если кто "в теме" и нашел этот фреймворк, буду признателен, если скинете ссылку.

вторник, мая 05, 2009

Samsung I7500 будет первым телефоном на Android в России

Пресс-центр корпорации Samsung объявил о примерной дате выхода смартфона на базе Android: 3 квартал 2009 года.
Возникает много вопросов таких как:

  1. Будет ли Android.Market в России?
  2. Сколько будет стоить устройство?
  3. Почему Google позволяет появится первым устройством в России не свой аппарат G1 или G2?


Т.к. в России Samsung пользуется большой популярностью, то ответ на 3 вопрос выглядит логичным.
Если рассуждать дальше, то Google согласен пустить первым Samsung.
К тому же если посмотреть темпы распространения платформы по странам(в прошивке 1.5 уже доступна русская локаль), то скорее всего Android.Market в России в тому времени будет.
А стоить мне кажется он будет не дешевле 20 тыс. руб., чтобы составить конкуренцию iPhone.

суббота, марта 28, 2009

Segmentation fault в php5

Взгляните на этот код:

class A extends B
{
public function getA(){
if($this->getA()){
return $this->getA();
} else {
return $this->getB();
}
}
}

С первого взгляда все верно. Но выдает:
[Sat Mar 28 17:12:59 2009] [notice] child pid 11239 exit signal Segmentation fault (11)

Образуется цикл, тогда надо:

class A extends B
{
public function getA(){
if(parent::getA()){
return parent::getA();
} else {
return $this->getB();
}
}
}

среда, марта 25, 2009

Монтирование(mount) файловой системы по ssh

sudo apt-get install sshfs

sudo sshfs -o allow_other username@hostname.ru:/home/usernamedir /home/usermountdir

Можно создать файл mount.sh и сделать ярлык для запуска на него. Gnome легко определяет что надо ввести пароль для ssh и предлагает сделать это через GUI


вторник, марта 24, 2009

Необходимые компоненты для работы symfony в Ubuntu

Недавно переставил систему с ubuntu 8.10 x86 на ubuntu 9.04 x86_64 на свой рабочий acer aspire 5720G.
Пришлось настраивать систему заново, но так как я сейчас разрабатываю только под symfony. Настраивал работу системы как раз для симфони, поэтому можно рассматривать этот пост как Необходимые компоненты для работы symfony в Ubuntu.
Поставим apache и php5.

sudo apt-get install apache2 php5-mysql libapache2-mod-php5 php5-common mysql-server mysql-common mysql-client-5.0 php5-cli php5-xsl


Устанавливаем кодировку по умолчанию для mysql:

sudo mcedit /etc/mysql/my.cnf
или
sudo nano /etc/mysql/my.cnf

добавить в конец строчки:

default-character-set=utf8
character_set_client=utf8

вместо utf8 можно любую другую, например cp1251

Ставим svn. Если не используете, то можно пропустить.

apt-get install subversion

libapache2-svn ssl-cert libapache2-svn - опционально



Ставим symfony:
sudo mkdir -p /usr/share/php5/symfony-1.2/
sudo svn co http://svn.symfony-project.com/branches/1.2 /usr/share/php5/symfony-1.2/



<VirtualHost 127.0.0.1:80>
ServerAdmin webmaster@localhost
DocumentRoot /var/www/symfony/web/
<Directory />
Options FollowSymLinks Indexes
AllowOverride All
</Directory>
ErrorLog /var/log/apache2/error.log
LogLevel warn
CustomLog /var/log/apache2/access.log combined
</VirtualHost>


Если проект начинаете с нуля:

wget http://www.symfony-project.org/get/sf_sandbox_1_2.tgz

tar xfz sf_sandbox_1_2.tgz
cp sf_sandbox
mv sf_sandbox/* /var/www/symfony/


Подключаем библиотеку symfony. В файле:
/var/www/symfony/config/ProjectConfiguration.class.php

меняем на:

require_once '/usr/share/php5/symfony-1.2/lib/autoload/sfCoreAutoload.class.php';


Проверяем
cp /var/www/symfony/
./symfony

если все без ошибок, то можно приступать к работе

понедельник, марта 23, 2009

Группировка виджетов(sfWidget) в админ генераторе(admin-generator) для symfony 1.2

Сегодня мне понадобилось динамически стороить элементы формы в админ генераторе и группировать их.
Это оказалось не столь простой задачей.
Я использую symfony 1.2.5-DEV из svn.
Сразу оговорюсь, что строить из embedded forms я не хочу, т.к. элементы динамические.
Вот мои действия. Постоим простейший вариант:

$this->widgetSchema['group0'] = new sfWidgetFormSchema(array(
'name0' => new sfWidgetFormInput(),
'email0' => new sfWidgetFormInput(),
));

сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<label for="t_object_key_group0_name0">Name0</label>
<input name="t_object_key[group0][name0]" id="t_object_key_group0_name0" type="text">
<label for="t_object_key_group0_email0">Email0</label>
<input name="t_object_key[group0][email0]"id="t_object_key_group0_email0" type="text">
</div>




Смотрится - не очень. Тогда покопавшись в коде я попробовал указать FormFormatter:

$decorator = new sfWidgetFormSchemaFormatterTable($this->widgetSchema
['group0']);
$this->widgetSchema['group0']->addFormFormatter('custom', $decorator);
$this->widgetSchema['group0']->setFormFormatterName('custom');


Эффект тот же.
Тогда я использовал RowFormat:

$this->widgetSchema['group0']->getFormFormatter()->setRowFormat
("\n<table> <th>%label%</th>\n <td>%error%%field%%help%
%hidden_fields
%</td>\n</tr></table>");


сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<table> <tbody><tr><th><label for="t_object_key_group0_name0"
>Name0</label></th>
<td><input name="t_object_key[group0][name0]"id="t_object_key_group0_name0" type="text"></td>
</tr></tbody></table>
<table> <tbody><tr><th><label
for="t_object_key_group0_email0">Email0</label></th>
<td><input name="t_object_key[group0][email0]" id="t_object_key_group0_email0" type="text"></td>
</tr></tbody></table>
</div>




Тоже не то что я ожидал.
И выходом из всего этого будет:

$this->widgetSchema['group0'] = new sfWidgetFormSchema(array(
'grouped' => new sfWidgetFormSchema(array(
'name0' => new sfWidgetFormInput(),
'email0' => new sfWidgetFormInput(),
))
));
$this->widgetSchema['group0']->getFormFormatter()->setRowFormat
("\n<table> <th>%label%</th>\n <td>%error%%field%%help%
%hidden_fields
%</td>\n</tr></table>");



сгенерированный html код будет:

<div>
<label for="t_object_key_group0">Group0</label>
<table> <tbody><tr><th>Grouped</th>
<td></td></tr><tr>
<th><label for="t_object_key_group0_grouped_name0">Name0</label></th>
<td><input name="t_object_key[group0][grouped][name0]"
id="t_object_key_group0_grouped_name0" type="text"></td>
</tr>
<tr>
<th><label for="t_object_key_group0_grouped_email0">Email0</label></
th>
<td><input name="t_object_key[group0][grouped][email0]"
id="t_object_key_group0_grouped_email0" type="text"></td>
</tr>
</tbody></table>
</div>




Это и есть то что нам надо.
Похоже на то, что setFormFormatterName работает только с embedded forms.
Я пытался попросить объяснений это на гугл групс, но без результатно.

Теперь самое интересное. Как же будет выглядеть валидатор:

$this->validatorSchema['group0'] = new sfValidatorSchema(array(
'grouped' => new sfValidatorSchema(array(
'name0' => new sfValidatorInteger(),
'email0' => new sfValidatorString(),
))
));


Таким образом используя sfWidgetFormSchema можно делать сгруппированные формы любой вложенности.

Интересный момент в mysql 5 для foreign key

CREATE TABLE `key` (
`id` int(11) NOT NULL auto_increment,
`name` varchar(255) NOT NULL,
`slug` varchar(128) NOT NULL,
`group_id` int(11) NOT NULL,
`parent_id` int(11) default '0',
`comp_id` int(11) default NULL,
PRIMARY KEY (`id`),
KEY `key_FI_1` (`group_id`),
KEY `key_FI_2` (`parent_id`),
CONSTRAINT `key_FK_1` FOREIGN KEY (`group_id`) REFERENCES `key_group` (`id`) ON DELETE CASCADE,
CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8


С первого взгляда все ок.
Теперь попробуем вставить запись:
insert into key (name,group_id) values ('ключ1',1)

выходит ошибка
mysql>ERROR 1452 (23000): Cannot add or update a child row: a foreign key constraint fails (`database/key`, CONSTRAINT `key_FK_2` FOREIGN KEY (`parent_id`) REFERENCES `key` (`id`) ON DELETE CASCADE)


Дело оказывается в `parent_id` int(11) default '0' , т.к. нет записи с id=0.
Правильнее сделать `parent_id` int(11) default NULL.

Мне кажется в propel при генерации из схемы можно сделать проверку на defaultValue, т.к. в купе с foreign key это уже ошибочно в большинстве случаев.

воскресенье, марта 22, 2009

Правильное расширение базовых классов Propel в symfony 1.2

Пусть наша модель называется SomeModel, тогда методом для извлечения объекта по строке(slug) будет:

class SomeModelPeer extends BaseSomeModelPeer
{
public static function retrieveBySlug($slug, PropelPDO $con = null){
if (null !== ($obj = SomeModelPeer::getInstanceFromPool((string) $slug))) {
return $obj;
}
if ($con === null) {
$con = Propel::getConnection(self::DATABASE_NAME, Propel::CONNECTION_READ);
}
$criteria = new Criteria();
self::addSelectColumns($criteria);
$criteria->add(self::SLUG,$slug);
$object = self::doSelectOne($criteria,$con);
if($object instanceof SomeModel){
self::addInstanceToPool($object);
}
return $object;
}
}

среда, марта 18, 2009

Как динамически изменить layout для всего application

Иногда нужно менять layout для всего приложения, на пример в празничные дни.
Но писать условия в каждом модуле - не красиво.
Поэтому можно сделать это заранее в фильтре.
В файле app/your_module/config/filters.yml добавь ваш фильтр:
rendering: ~
security: ~

# insert your own filters here

change_layuot:
class: changeLayoutFilter
param:

cache: ~
common: ~
execution: ~


Создайте класс changeLayoutFilter и положите его в lib/:
class changeLayoutFilter extends sfFilter
{
public function execute($filterChain)
{
$request = $this->getContext()->getRequest();
$user = $this->getContext()->getUser();
//устанавливаю layuot
if(isNY()){
sfConfig::set('symfony.view.'.
$this->getContext()->getModuleName().'_'.
$this->getContext()->getActionName().'_layout',
'your_layout');
}
// Execute next filter
$filterChain->execute();
}
}


isNY() - это ваше условие, 'your_layout' - ваш layout.

Кстати, почему я не использовал конструкцию по учебнику:
class changeLayoutFilter extends sfFilter
{
public function execute($filterChain)
{
// Execute this filter only once
if ($this->isFirstCall())
{
[... ...]
}
}
}

?
Потому что если будет использован forward в контроллере(sfAction), то
цепочка фильтров будет запущено заново и наш хак не сработает.
Посмотрите сами в дебагере, вкладка "log" как ваш фильтр будет запущен 2 раза.

вторник, марта 17, 2009

Валидатор телефонного номера в symfony 1.2

Телефонный номер вида: +7(495)1234567

/**
* sfValidatorTelephone
*
* @package
* @subpackage validator
* @author broderix
* @version
*/
class sfValidatorTelephone extends sfValidatorRegex
{
/**
* @see sfValidatorRegex
*/
protected function configure($options = array(), $messages = array())
{
parent::configure($options, $messages);

$this->setOption('pattern', '/^(\+\d\(\d+\)\d*)$/i');
}
}


чтобы заработал положить в lib/

суббота, марта 14, 2009

Упрощение работы с sfWidgetFormSelect в sfForm

Этим постом я начну серию статьёй по symfony 1.2

Иногда необходимо использовать ограниенные списки select в админе в symfony 1.2.
Но не хочется выбирать данные для sfWidgetFormSelect из массива или yaml файла.
Поэтому можно создать свой виджет sfWidgetFormYamlSelect:

class sfWidgetFormYamlSelect extends sfWidgetFormChoice
{
public function __construct($options = array(), $attributes = array())
{
$options['choices'] = new sfCallable(array($this, 'getChoices'));
parent::__construct($options, $attributes);
}
protected function configure($options = array(), $attributes = array())
{
addRequiredOption('form_name', null);
$this->addRequiredOption('field_name', null);
$this->addOption('add_empty', false);
parent::configure($options, $attributes);
}
public function getChoices()
{
$conf_array = sfConfig::get('app_'.$this->getOption('form_name'));
$choices = array();
if(false !== $this->getOption('add_empty')){
$choices[''] = true === $this->getOption('add_empty') ? '' :
$this->getOption('add_empty');
}
$choices = array_merge($choices,$conf_array[$this->getOption('field_name')]);
return $choices;
}
}


В файле config/app.yml (если его нет, то создайте) опишите ваши поля для форм:
all:
.form_settings:
my_form1:
type: { 'yes': 'да', 'no': 'нет }
my_form2:
status: { 0: 'новый', 1: 'подтвержденный', 2: 'редактируется' }



В самой форме это будет выглядеть так:

class MyFormForm1 extends BaseMyForm1Form
{
public function configure()
{
$this->setWidget('status' , new sfWidgetFormYamlSelect(array(
'form_name'=>'my_form1',
'field_name'=>'status'
)));
}
}





Так же это можно использовать в фильтрах.

четверг, января 22, 2009

Обзор шаблонов проектирования

Сегодня наткнулся на хороший обзор шаблонов проектирования. Автор проекта monkeycode.ru периодически выкладывает шаблоны проектирования с краткими пояснениями пока только на языке php5.
Думаю чтобы понять смысл этого достаточно.
Будем следить.
Все шаблоны можно вытащить по тегу шаблоны проектирования.