Окончание мини-цикла статей по 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. Если вы прочитали статью до этого места – поставьте пожалуйста оценку. Или даже обоснуйте ее в комментарии. Вам не сложно, а мне приятно и наука на будущее ))
По поводу №25
Идея в том, чтобы не коммитить классы которые всё равно генерируются. Я отказался от этой практики уже давно – ещё когда работал с propel. Так вот, его генератор кода помещал в сгенеренный файл timestamp. Соответственно, при любом ребилде файл менялся и маячил перед глазами «закоммить меня».
Кроме того, мергать schema.yml гораздо проще, чем Base*.class.php. Тем более, что последнее можно и не делать.
В общем я бы сказал разные стороны есть. И “за” и “против”. Мне нравится когда “все включено”. Вынес и забыл. А так придется пересобирать модель.
Мержем Base* классов ни разу не занимался, да и сильно схема редко меняется – как правило один-два базовых классов слегка изменяются.
Но бывало когда у меня генерируются одни классы, а у другого девелопера – похожие, но чуть чуть другие. И перекоммичивали туда-сюда всю модель.