Представлю вашему вниманию 30 лучших практик для symfony – перевод презентации Nicolas Perriault с SymfonyDay’09. По ходу дела буду стараться добавлять свои комментарии.
Это первая часть статьи (часть вторая, часть третья)
Для чего вообще нужны практики?
- Они улучшают коммуникации между разработчиками путем стандартизации кода;
- Улучшают безопасность, сопровождаемость, переносимость и возможности взаимодействия через улучшение качества кода.
Отказ от гарантий )
Приведенные ниже практики никоим образом не сортированы, ни по критичности, ни по важности. Также эти практики не являются истиной последней инстанции, они обсуждаемы и основываются на личном опыте автора. Кроме того, некоторые практики не являются строго symfony-ориентироваными, так что будут полезны и простым смертным ))
#0
Всегда пишите symfony с маленькой буквы.
Хотя вы также можете принимать решение касательно этой практики в зависимости от того какой результат вернет rand(0, 1); ))))
#1
Управляйте View внутри View:
- View предназначен для использования в шаблонах
- Старайтесь избегать использования view.yml
- Старайтесь избегать использования видов в контроллере (ииик!) или в модели (вы уволены!)
- Slot’ы вам помогут (сильно)
Местами будет проблематично избавиться от наследия symfony 1.0 – view.yml и хелперов для мета тагов. Буквально сегодня вычитал в документации, что
include_metas()
иinclude_http_metas()
устарели // прим. hudson
Например, вот как можно работать с TITLE и прочими ассетами:
<html> <head> <?php include_http_metas() ?> <?php include_metas() ?> <title> <?php echo get_slot( 'title', $defaultTitle = 'Untitled page' ) ?> - MySite.com </title> <?php include_stylesheets() ?> </head> <body> <h1><?php echo get_slot( 'title', $defaultTitle ) ?></h1> <?php echo $sf_content ?> <?php include_javascripts() ?> </body> </html>
Значение $defaultTitle можно (нужно) задавать в app.yml
<?php include_javascript( 'jquery.js' ) ?> <?php include_stylesheet( 'jquery.js' ) ?> <?php slot( 'title', 'My first page' ) ?> <p>My content</p>
Запомните: если вы дизайнер или интегрируете дизайн, вы не должны работать с YAML файлами или сложным PHP кодом для представления.
#2
Всегда включайте эскейпинг и защиту от СSRF (Cross-Site Request Forgery – межсайтовая подделка запроса). Начиная с symfony 1.3 эти опции включены по умолчанию.
ХОРОШО
all: .settings: csrf_secret: c81ba3eb452a8e217afd7cdded31b8cd58b7a1b8 escaping_strategy: true escaping_method: ESC_SPECIALCHARS
#3
Всегда используйте redirect после постинга данных.
- По соображениям безопасности;
- Для эргономики;
- Для того чтобы избежать дублирования записей в базе (при обновлении страницы //hudson);
- Не забывайте показывать пользователю flash-сообщение после совершенной транзакции.
ПЛОХО
public function executeAddToCart( sfWebRequest $request ) { $this->form = new CartForm(); if( $this->isMethod( 'post' ) && $this->formbindAndSave( $request->getParameter( 'cart' ) ) ) { $this->forward( 'payment', 'index' ); } }
ХОРОШО
public function executeAddToCart( sfWebRequest $request ) { $this->form = new CartForm(); if( $this->isMethod( 'post' ) && $this->formbindAndSave( $request->getParameter( 'cart' ) ) ) { $this->getUser()->setFlash( 'notice', 'Items have been added to your cart' ); $this->redirect( '@payment' ); } }
Не совсем согласен, что редирект добавляет эргономики, но быстро обойти детскую болезнь с повторной вставкой данных при рефреше формы помогает )) // hudson
#4
- Никаких экземпляров Criteria (Propel) или Doctrine_Query, SQL запросов и любой ORM/RDBMS специфики не должно быть ни в шаблонах ни в экшенах (actions);
- Чем сильнее вы привяжете модель к контроллерам и видам, тем более инертным к изменениям будет ваш бэкэнд.
ПЛОХО
// контроллер public function executeRecentProducts( sfWebRequest $request ) { $this->products = Doctrine::getTable( 'Product' ) ->createQuery( 'p' ) ->where( 'p.is_published = 1' ) ->orderBy( 'published_at DESC' ) ->limit(10) ->execute(); // Плохо, получаем Doctrine_Collection }
ЧУТЬ ЛУЧШЕ
// контроллер public function executeRecentProducts( sfWebRequest $request ) { $this->products = Doctrine::getTable( 'Product' ) ->createQuery( 'p' ) ->where( 'p.is_published = 1' ) ->orderBy( 'published_at DESC' ) ->limit(10) ->fetchArray(); // Уже лучше, Array }
ХОРОШО
// контроллер public function executeRecentProducts( sfWebRequest $request ) { $this->products = ProductTable::getRecent( $max = 10, $published = true ); // Array, идеально )) }
Честно говоря сам грешу чем-то похожим на “плохо” и “чуть лучше” (только на Propel). Надо работать над собой и выносить выборки в модели. // hudson
#5
- Никогда не изменяйте ядро symfony для того чтобы добавить или изменить их функции. Их можно подменять через механизм autoloading’а, а еще лучше, наследоваться от них.
- Настройка factories.yml и перехват нативных событий symfony может сильно помочь вам в расширении функциональности ядра.
ПЛОХО
// контроллер public function executeProcess( sfWebRequest $request ) { $postContent = trim( file_get_contents( 'php://input' ) ); //... // Плохо - параметр $request не используется }
ХОРОШО
// myWebRequest.class.php class myWebRequest extends sfWebRequest { public function getHttpRawPostData() { if( !$this->isMethod( 'post' ) ) { return; } return trim( file_get_contents( 'php://input' ) ); } }
# factories.yml all: request: class: myWebRequest
// контроллер public function executeProcess( myWebRequest $request ) { $postContent = $request->getHttpRawPostData(); }
#6
В шаблонах необходимо использовать только альтернативный синтаксис PHP для лучшей читаемости и бОльшего удобства.
ПЛОХО
// шаблон <?php if( $sf_request->hasParameter( 'foo' ) ) { echo '<p>Haz foo: ' . $sfRequest->getParameter( 'foo' ) . '</p>'; } else { echo '<p>Hazn't foo</p>'; } if( count( $foo ) ) { foreach( $foo as $bar ) { echo '<p>' . $bar . '</p>'; } } ?>
ХОРОШО
// шаблон <?php if( $sf_request->hasParameter( 'foo' ) ): ?> <p>Haz foo: <?php $sfRequest->getParameter( 'foo' ) ?></p> <?php else: ?> <p>Hazn't foo</p> <?php endif; ?> <?php if( count( $foo ) ): ?> <?php foreach( $foo as $bar ): ?> <p><?php echo $bar ?></p> <?php endforeach; ?> <?php endif; ?>
На мой взгляд, альтернативный синтаксис тоже не очень хорош. И порой его можно запутать не хуже чем первый пример в этом пункте. Но все-же я голосую за 2й вариант. Как правило все-таки так бывает читабельнее. // hudson
#7
- Контролируйте настройки кэширования, особенно то, что кэш действительно включен в продуктовом окружении (prod). И не смейтесь. Это частенько происходит ));
- Если используете кэширование, создайте тестовую (staging) среду для тестирования стратегии кэширования (если вы конечно еще этого не сделали);
- А еще лучше – избегайте использования кэша. Без шуток.
ХОРОШО
# staging окружение для тестирование кэширования staging: .settings: error_reporting: <?php echoln (E_ALL | E_STRICT) ?> web_debug: on cache: on no_script_name: off etag: off
#8
- Используйте маршруты (routes) вместо пары module/action в функциях url_for(), link_to(), redirect();
- Кроме того, базовые (дефолтные) маршруты должны быть деактивированы (как минимум для приложений, которые не используют модули, созданные админ-генератором версии 1.0);
#9
- Все комментарии в коде, блоки phpdoc, файлы INSTALL, README, CHANGELOG, переменные, функции, классы и методы, имена таблиц в базе данны, а также документация разработчиков и т.д. и т.п. должны быть написаны на английском языке.
ПЛОХО
class factureActions extends sfActions { /** * Cette methode va recuperer toutes le factures et les afficher * @param sfWebRequest $requette La requette */ public function executeListerLesFactures( $requette ) { $this->factures = MesFactures::recupererToutesLesFactures( $requette->getParameter( 'type_de_factures' ) ); } }
ХОРОШО
class invoicesActions extends sfActions { /** * Lists all available invoices * @param sfWebRequest $request The HTTP request */ public function executeListInvoices( sfWebRequest $request ) { $this->invoices = InvoicePeer::retrieveAll( $request->getParameter( 'type' ) ); } }
Тут как говорится – без комментариев. А то порой наспех созданный класс/метод/таблица в базе с транслитерацией приживаются, и ампутировать их можно только через глубокий рефакторинг. И то не факт. Что же касается комментариев и документации – если над проектом работала, работает и будет работать русскоговорящая команда, то наверное лучше все-таки использовать русский язык (приличный русский будет лучше так-себе английского). Вообще говоря этот вопрос лучше один раз решить с командой коллегиально. // hudson
#10
- Перехватывайте и логируйте исключения. Отображайте читаемые и понятные сообщения об ошибках конечному пользователю.
ПЛОХО
// контроллер public function executeMakeSandwich() { try { $this->sandwich = SandwichMaker::makeSandwich(); $this->getUser()->setFlash( 'notice', 'Your sandwich is ready' ); } catch( HamException $e ) { $this->getUser()->setFlash( 'error', $e->getMessage() ); } $this->redirect( '@make_sandwich' ); }
ХОРОШО
// контроллер public function executeMakeSandwich() { try { $this->sandwich = SandwichMaker::makeSandwich(); $this->getUser()->setFlash( 'notice', 'Your sandwich is ready' ); } catch( HamException $e ) { $this->logMessage( 'Problem with ham: ' . $e->getMessage(), 'err' ); $this->getUser()->setFlash( 'error', 'Can't make sandwich, try with sudo' ); } $this->redirect( '@make_sandwich' ); }
За сим видимо придется откланяться, 30 практик не уложились в одну статью и один вечер перевода, так что на днях выйдет продолжение ))) see ya
Спасисбо! Полезные заметки. Я сам на симфони программил немного, один небольшой сайтик, но уже знаю какие моменты стоит изменить.
P.S> Можно вас попросить сменить надпись в сайдбаре Bezaras Viktoras на Byazaras Viktoras, или лучше просто CharnaD. Я хочу и18ь свою фамилию =)
Поправил ) Если не сложно, то и оценочку можно ))
Спасибо.
Конечно) Поставил везде, куда
дотянулся.
Перечитал сейчас с большим понимаем.
В пункте 3 оба раза forward, когда рекоммендуется redirect.
С 6м пунктом не согласен, но стандарт есть стандарт. Лично у меня от рябит в глазах.
П.3 поправил, спасибо!
Что касается п.6, то рябит не более чем от прочего спагетти-кода. Для view пока ничего приличного не придумали – может в 2.0 будут шаблоны с наследованием. Одно плохо, блоки не подсвечиваются в отличие от стандартных фигурных скобок: ‘{}’.
>>// Плохо, получаем Doctrine_Collection
всмысле рекомендуется всегда получать результат как ->toArray()?
почему?
Честно говоря я не люблю с массивами работать. Например потому что в get* / set* может быть реализована дополнительная логика. В общем для меня лично спорная рекомендация…
и кроме логики он там может подтягивать из БД связи, правда это плодит запросы, зато не вываливается с ошибкой.
В общем я этими рекомендациями пользуюсь через призму своих привычек и потребностей ) Не на экзамене всеж таки…