in Профессиональное

30 лучших практик для Symfony – часть первая #1 – #10

Представлю вашему вниманию 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

Leave a Reply for hudson Cancel Reply

Write a Comment

Comment

ERROR: si-captcha.php plugin: GD image support not detected in PHP!

Contact your web host and ask them to enable GD image support for PHP.

ERROR: si-captcha.php plugin: imagepng function not detected in PHP!

Contact your web host and ask them to enable imagepng for PHP.

  1. Спасисбо! Полезные заметки. Я сам на симфони программил немного, один небольшой сайтик, но уже знаю какие моменты стоит изменить.

    P.S> Можно вас попросить сменить надпись в сайдбаре Bezaras Viktoras на Byazaras Viktoras, или лучше просто CharnaD. Я хочу и18ь свою фамилию =)

  2. Перечитал сейчас с большим понимаем.

    В пункте 3 оба раза forward, когда рекоммендуется redirect.

    С 6м пунктом не согласен, но стандарт есть стандарт. Лично у меня от рябит в глазах.

    • П.3 поправил, спасибо!

      Что касается п.6, то рябит не более чем от прочего спагетти-кода. Для view пока ничего приличного не придумали – может в 2.0 будут шаблоны с наследованием. Одно плохо, блоки не подсвечиваются в отличие от стандартных фигурных скобок: ‘{}’.

  3. >>// Плохо, получаем Doctrine_Collection
    всмысле рекомендуется всегда получать результат как ->toArray()?
    почему?

    • Честно говоря я не люблю с массивами работать. Например потому что в get* / set* может быть реализована дополнительная логика. В общем для меня лично спорная рекомендация…

      • и кроме логики он там может подтягивать из БД связи, правда это плодит запросы, зато не вываливается с ошибкой.

        • В общем я этими рекомендациями пользуюсь через призму своих привычек и потребностей ) Не на экзамене всеж таки…