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

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

Окончание мини-цикла статей по best pactices в symfony – часть третья (часть первая, часть вторая).

#21

Пишите модульные и функциональные тесты:

  • Модульные тесты (unit tests) тестируют бизнес-логику вашего приложения (пожалуйста, не тестируйте symfony и Doctrine снова – это уже сделали до вас ))));
  • Функциональные тесты (Funtional, functionally tests) тестируют пользовательский интерфейс, когда это необходимо (например 404/403/401 HTTP коды, безопасность и аутентификацию пользователей, транзакции и формы и т.д.).

Хинт: вы можете написать свой собственный browser/tester для того чтобы не дублировать тестовый код:

class SecurityBrowser extends sfTestFunctional
{
    public function signin( $username, $password )
    {
        return $this
            ->get( '/logout' )
            ->get( '/login' )
            ->info( sprintf( 'Signing in with %s:%s', $username, $password ) )
            ->setField( 'signin[username]', $username )
            ->setField( 'signin[password]', $password )
            ->click( 'sign in' );
    }
}

// где-то в тесте
$browser = new SecurityBrowser( new sfBrowser() );
$browser
    ->signIn( 'toto', 'tata' )
    ->signIn( 'tutu', 'titi' );

Вообще говоря, тестировать нужно всегда. Может если вы делаете сайтики – по 10 штук на неделе, тогда вам ни юнит тесты, ни функциональные тесты, ни эти 30 практик не нужны. Некогда да за это и не платят. Во всех остальных случаях, думаю стоит присмотреться к этой практике // hudson

#22

  • Никогда не используйте абсолютные пути в коде. На худой конец поместите их в YAML-конфигурацию;
  • Делайте ваш проект переносимым: используйте dirname( __FILE__ ) (можно использовать в том числе и в конфигурации);
  • Вы можете поместить библиотеки symfony в lib/vendor.

ПЛОХО

require_once( '/Users/chucknorris/workspace/weapons/symfony/lib/autoload/sfCoreAutoload.class.php' );
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        date_default_timezone_set( 'Europe/Paris' );
        $this->enablePlugins( array( 'sfDoctrinePlugin', 'sfDoctrinePlugins' ) );
    }
}

ХОРОШО

require_once( dirname( __FILE__ ) . '/../lib/vendor/symfony/lib/autoload/sfCoreAutoload.class.php' );
sfCoreAutoload::register();

class ProjectConfiguration extends sfProjectConfiguration
{
    public function setup()
    {
        date_default_timezone_set( 'Europe/Paris' );
        $this->enablePlugins( array( 'sfDoctrinePlugin', 'sfDoctrinePlugins' ) );
    }
}

#23

Объекты всегда должны быть представлены slug-кодом в URL’ах, но никак не первичным ключом. Это нужно

  • Из соображений безопасности: числовой первичный ключ легко определяется, в отличие от slug.
  • Из соображений юзабилити и SEO: /article/symfony-rulez.html звучит круче чем /article/123.html

#24

  • Все переменные, специфичные для проекта, должны быть помещены в app.yml;
  • Вы можете дать доступ к переменным уровня проекта (для всех приложений) поместив app.yml в корневую config/ директорию.

ПЛОХО

// контроллер
public function executeList( sfWebRequest $request )
{
    $this->posts = PostTable::getRecent( 10 );
}

ХОРОШО

# app.yml
all:
  blog:
    max_items: 10

// in a controller
public function executeList( sfWebRequest $request )
{
    $this->posts = PostTable::getRecent( sfConfig::get( 'app_blog_max_items', 10 ) );
}

#25

  • Избегайте помещения под версионный контроль любые из генерированных классов вида Base*, в особенности, если вы хотите писать плагины для многократного использования или же хотите перераспределять вашу работу.
  • Почему? Возможно что-то захочет расширить ваши классы модели или же добавить к ним behavior другой.

Честно говоря, не понял сути данной рекомендации – по крайней мере для основного проекта. С плагинами разговор немного другой, а для основной модели проекта, если behavior нужен – он определяется и коммитится. Если не нужен – соответственно не определяется.

#26

  • Используйте систему тасков symfony для CLI-скриптов;
  • Таски имеют доступ ко всем возможностям symfony;
  • Таски созданы именно для CLI-задач:
    • вывод текста
    • красивая печать
class sfMooTask extends sfBaseTask
{
    public function configure()
    {
        $this->namespace = 'cow';
        $this->name = 'moo';
        $this->briefDescription = 'says moo';

        $this->addArgument( array(
                new sfCommandArgument( 'who', sfCommandArgument::OPTIONAL, 'your name', null ),
            )
        );
    }

    public function execute( $arguments = array(), $options = array() )
    {
        $this->log( isset( $arguments['who'] ? sprintf( 'Moo, %s!', $arguments['who'] ) : 'Moo!' ) );
    }
}
~/ $ ./symfony list cow
Usage:
  symfony [options] task_name [arguments]

Options:
  --dry-run                -n  Do a dry run without executing actions.
  --help                   -H  Display this help message.
  --quiet                  -q  Do not log messages to standard output.
  --trace                  -t  Turn on invoke/execute tracing, enable full backtrace.
  --version                -V  Display the program version.

Available tasks for the "cow" namespace:
  :moo  says moo

~/ $ ./symfony cow:moo Niko
Moo, Niko!

#27

  • Изменяйте запрос (request) и ответ (response) программно с использованием фильтров;
  • Вы также можете использовать события (events)  request.filter_parameters и response.filter_content;
  • Фильтры несложно настроить и отлаживать, в то время как события более правильный и мощный способ. Дело вкуса…

ХОРОШО

# filters.yml
rendering:  ~
security:   ~

# insert your own filters here
bottom:
  class:    myFilter

cache:      ~
execution:  ~

class myFilter extends sfFilter
{
    public function execute( $filterChain )
    {
        $filterChain->execute();
        $response = $this->getContext()->getResponse();
        $response-setContext( $response->getContent() . '<p>I am the bottom, dude</p>' );
    }
}

ХОРОШО

class mainConfiguration extends sfApplictionConfiguration
{
    public function configure()
    {
        $this->dispatcher->connect( 'response.filter_content', array( $this, 'filterResponse' ) );
    }

    public function filterResponse( sfEvent $event, $content )
    {
        return str_ireplace( '</body>', '<p>I am the bottom, dude.</p></body>' );
    }
}

#28

  • Всегда помещайте в корень проекта (или плагина) файлы: README, INSTALL и CHANGELOG. Если проект с открытым кодом, то также должен присутствовать файл LICENSE.
  • И пожалуйста, не релизьте проект под лицензией GPL: плагин symfony != операционная система ))

#29

  • Доступность фич и модулей для разных проектов лучше всего обеспечивать посредством плагинов;
  • Если вам это в кайф, то релизьте их публично )))

#30

  • Изучайте код других фреймворков!
    • Они могут иметь хорошие или даже лучшие идеи по решению тех или иных проблем;
    • Даже если фреймворк написан не на PHP (Django, Rails, Spring…).

Вот и все! Надеюсь было интересно и познавательно )) До новых встреч.

P.S. Если вы прочитали статью до этого места – поставьте пожалуйста оценку. Или даже обоснуйте ее в комментарии. Вам не сложно, а мне приятно и наука на будущее ))

Write a Comment

Comment

*

  1. По поводу №25

    Идея в том, чтобы не коммитить классы которые всё равно генерируются. Я отказался от этой практики уже давно – ещё когда работал с propel. Так вот, его генератор кода помещал в сгенеренный файл timestamp. Соответственно, при любом ребилде файл менялся и маячил перед глазами «закоммить меня».

    Кроме того, мергать schema.yml гораздо проще, чем Base*.class.php. Тем более, что последнее можно и не делать.

    • В общем я бы сказал разные стороны есть. И “за” и “против”. Мне нравится когда “все включено”. Вынес и забыл. А так придется пересобирать модель.

      Мержем Base* классов ни разу не занимался, да и сильно схема редко меняется – как правило один-два базовых классов слегка изменяются.

      Но бывало когда у меня генерируются одни классы, а у другого девелопера – похожие, но чуть чуть другие. И перекоммичивали туда-сюда всю модель.