Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
Best Practices in Symfony2
Search
Andreas Hucks
November 12, 2014
Programming
540
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Best Practices in Symfony2
Going beyond the Official Best Practices in Symfony2
Andreas Hucks
November 12, 2014
More Decks by Andreas Hucks
See All by Andreas Hucks
Divide and Conquer (LonghornPHP 2019)
meandmymonkey
0
210
Symfony Internals
meandmymonkey
3
950
Divide and Conquer
meandmymonkey
1
740
Deptrac - Keep Your Architecture Clean
meandmymonkey
0
820
Introduction to Docker at PHPBenelux2015
meandmymonkey
3
930
Introduction to Docker at PHPNW2014
meandmymonkey
4
440
O(ops), Authentication!
meandmymonkey
4
1k
Best Practices in Symfony2
meandmymonkey
15
1.8k
Best Practices in Symfony2
meandmymonkey
28
4.6k
Other Decks in Programming
See All in Programming
肥大化するレガシーコードに立ち向かうためのインターフェース分離と依存の逆転 / JJUG CCC 2026 Spring
hirokunimaeta
0
610
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
320
トークンをケチるな、設計しろ:GitHub Copilotを賢く使うコンテキスト戦略
ochtum
0
160
スマートグラスで並列バイブコーディング
hyshu
0
260
エンジニアと一緒にテストコードの設計と実装を改善した話
mototakatsu
0
220
技術記事、 専門家としてのプログラマ、 言語化
mizchi
13
6.5k
jQueryをバージョンアップする前に使いたいjQuery Migrate
matsuo_atsushi
0
590
Observability in Practice:Grafana 與 Edge Device SRE 的那些事
blueswen
0
170
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
410
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
160
鹿野さんに聞く!『TypeScriptコードレシピ集』で磨く実践力
tonkotsuboy_com
2
750
AI時代のUIはどこへ行く?その2!
yusukebe
22
7.5k
Featured
See All Featured
Why You Should Never Use an ORM
jnunemaker
PRO
61
9.9k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
620
The Cost Of JavaScript in 2023
addyosmani
55
10k
Tell your own story through comics
letsgokoyo
1
970
GraphQLとの向き合い方2022年版
quramy
50
15k
We Are The Robots
honzajavorek
0
250
Breaking role norms: Why Content Design is so much more than writing copy - Taylor Woolridge
uxyall
0
330
Measuring & Analyzing Core Web Vitals
bluesmoon
9
870
From π to Pie charts
rasagy
0
220
How to Ace a Technical Interview
jacobian
281
24k
Odyssey Design
rkendrick25
PRO
2
710
How Software Deployment tools have changed in the past 20 years
geshan
0
34k
Transcript
Best Practices in Symfony2 php[world] 2014 Washington D.C. Nov. 12th
Andreas Hucks
@meandmymonkey Andreas Hucks Software Architect & Trainer at SensioLabs
Best Practices
symfony.com/doc/current/best_practices/index.html
None
None
Bad Practices
symfony Day 2009
Chapter One A Clean Project
Naming Things in Symphony • Follow PSR-0, PSR-1, PSR-2 •
Find a common scheme for your team • Be explicit • Be consistent
Care about your coding style • Again - follow PSR-0,
PSR-1, PSR-2 • Use PHPCSFixer http://goo.gl/gQnc0N
.gitignore /web/bundles/ /app/bootstrap.php.cache /app/cache/* /app/config/parameters.yml /app/logs/*
/build/ /vendor/ /bin/ /composer.phar /data/uploads .idea add your own
.gitignore /web/bundles/ /app/bootstrap.php.cache /app/cache/* /app/config/parameters.yml /app/logs/*
/build/ /vendor/ /bin/ /composer.phar /data/uploads .idea should be in your global .gitignore
.gitignore /web/bundles/ /app/bootstrap.php.cache /app/cache/* /app/config/parameters.yml /app/logs/*
/build/ /vendor/ /bin/ /composer.phar /data/uploads .idea this too
.gitignore /web/bundles/ /app/bootstrap.php.cache /app/cache/* /app/config/parameters.yml /app/logs/*
/build/ /vendor/ /bin/ /composer.phar /data/uploads .idea this.
Committing parameters.yml is a Three Kitten Offense
Remove Acme\* (if present)
To Bundle or not to Bundle
If you DO need separate Bundles... Vendor\AwesomeBundle vs. Vendor\Bundle\AwesomeBundle
If you need separate Bundles... Vendor\AwesomeBundle vs. Vendor\Bundle\AwesomeBundle
FAIL a.k.a. „because I can“ • MyCompleteAppBundle (ok for small
projects) • MyAppNeedingGlobalResourcesBundle • MyBundleInsideAnotherBundleBundle
FAIL a.k.a. „because I can“ • MyCompleteAppBundle (ok for small
projects) • MyAppNeedingGlobalResourcesBundle • MyBundleInsideAnotherBundleBundle [sic]
What should go into a Bundle • Bundles should be
self-contained • Sets of Features • Examples: API, Worker Commands, Admin Panel… • Configured in /app/config
Your config options • YAML • XML • Annotations •
INI • PHP
Your config options • YAML • XML • Annotations •
INI • PHP meh.
Your config options • YAML • XML • Annotations •
INI • PHP special use cases
Your config options • YAML • XML • Annotations •
INI • PHP mostly good
Your config options • YAML • XML • Annotations •
INI • PHP Routing, Bundle Config, Parameters Services Routing, Validators, ORM/ODM
If not using Annotations… Nest your routing files # /app/config/routing.yml
acme_conference: resource: @AcmeConferenceBundle/[…]/routing.yml prefix: /conference # /src/Acme/Bundle/ConferenceBundle/[…]/routing.yml acme_conference_admin: resource: routing_admin.yml prefix: / acme_conference_frontend: resource: routing_frontend.yml prefix: /
Chapter Two Controllers
• Put Controllers on a Diet • Decouple them •
But don't go overboard (this is the application layer)
Losing the Boilerplate
Inject the Request into Controllers public function createAction(Request $request) {
// not: $request = $this->getRequest(); $session = $request->getSession(); // .. }
@Cache(...) /** * @Cache(smaxage=3600) */ public function listAction() { //
.. return $this->render( 'SensioConferenceBundle:Default:show.html.twig', array( 'conference' => $conference ) ); }
ParameterConverters public function showAction(Conference $conference) { return $this->render( 'SensioConferenceBundle:Default:show.html.twig', array(
'conference' => $conference ) ); }
@Template([name]) /** * @Template() */ public function listAction() { //
.. return array( 'conferences' => $conferences ); } compare to official best practices
Use handleRequest() public function createAction(Request $request) { // ..
$form->handleRequest($request); if ($form->isSubmitted() && $form->isValid()) { // ... persist data, redirect } // .. }
Events for add-on actions if ($form->isSubmitted() && $form->isValid()) { //
... persist data $this->get('event_dispatcher')->dispatch( ConferenceEvents::CREATED, new ConferenceEvent($conference) ); // .. redirect }
Service Layers – where sensible class ConferenceHandler { public function
__construct( ObjectManager $manager, EventDispatcherInterface $dispatcher ) { $this->manager = $manager; $this->dispatcher = $dispatcher; } public function save(Conference $conference) { $this->manager->persist($conference); $this->manager->flush(); $this->dispatcher->dispatch( // ... ); }
this->get('conference_handler')->save( $conference ); Service Layers – where sensible
Using the BaseController? Wrap calls to the Container to clean
up /** * @return GeocoderInterface */ private function getGeocoder() { return $this->get('geocoder'); }
Chapter Three Dependency Injection
Injecting the Container class MyWebserviceConnector { public function __construct( ContainerInterface
$container ) { // here be dragons } }
But... but Symfony is doing it!
...\TwigBundle\Extension\AssetsExtension class AssetsExtension extends \Twig_Extension { private $container; public
function __construct( ContainerInterface $container ) { $this->container = $container; }
So why not… Because Scopes! class MyMailer { public function
__construct(Request $request) { // here be dragons } }
Solution: RequestStack (>= 2.4) class MyMailer { public function __construct(RequestStack
$stack) { $this->stack = $stack; } public function doSomething() { $request = $this->stack->getCurrentRequest(); } }
Alternative: Providing the Request (< 2.4) class RequestProvider { public
function __construct( ContainerInterface $container ) { $this->container = $container; } public function getRequest() { $this->container->get('request'); } }
Service Organization
Split up Service Definitions public function load( array $configs, ContainerBuilder
$container ) { // … $loader->load('storage.yml'); $loader->load('imageprocessing.yml'); }
Dynamic Loading of Service Definitions public function load( array $configs,
ContainerBuilder $container ) { // … $loader->load('storage.yml'); $loader->load('imageprocessing.yml'); if (isset($config['logging'])) { $loader->load('logging.yml'); } }
Semantic Configuration (for reusable Bundles) acme_conference: logging: ~ thumbnails: width:
300 height: 200
The Container as a Registry public function listAction() { $max
= $this->container->getParameter( 'max_conferences' ); $conferences = $someStorage->getConferences($max); // .. }
Instead: Proper Service Configuration services: my_storage: class: "Sensio\[…]\Storage\MyStorage" arguments: ["%max_conferences%"]
Binding to the Environment public function listAction() { $env =
$this->container->getParameter( 'kernel.environment' ); if ('dev' === $env) { // ... } // ... }
Instead: Use Your config files # /app/config/config.yml acme_conference: thumbnails: width:
300 height: 200 # /app/config/config_dev.yml acme_conference: logging: ~
Miscellaneous • Use YAML for Service Definitions • Remember you
can (and should) use Environment Variables • Use %kernel.root_dir% as a reference
Intermezzo Random Tips out of Context
PHP • Please drop 5.3 (it‘s also faster) • Use
an Opcode Cache
Composer • Install globally • Use the --optimize-autoloader option for
production
Doctrine • Activate Metadata Cache • Activate Query Cache •
Pro Level only: Use factory-service to register Repos & Managers as Services • Do NOT inject the EntityManager into your entities
Security • Make sure there are no leaks in the
security.yml access_control section! • Better: Check Authorization in Controller, use the @Security annotation
Translation • Work with translation keys instead of full text
to avoid breaking translations
Searching • Look for „Symfony2“ (without the space)
PHPStorm • The Symfony Plugin is pretty stable, use
it! • Check out the PHP Annotations Plugin
Read the Documentation (and the Changelogs)
Chapter Four Forms
Forms in Controllers public function createAction(Request $request) { $form =
$this->createFormBuilder() ->add('name') ->add('startDate', 'date') ->add('endDate', 'date') ->add('location', 'textarea') ->getForm() ; // ..
• Couples Form setup to Controller • No reusability
Better: Use Type Classes class ConferenceType extends AbstractType { public
function buildForm( FormBuilderInterface $builder, array $options ) { $builder ->add('name') ->add('startDate', 'date') ->add('endDate', 'date') ->add('location', 'textarea') ; }
Always set the data_class public function setDefaultOptions( OptionsResolverInterface $resolver )
{ $resolver->setDefaults( array( 'data_class' => 'Acme\Conference' ) ); }
Using Data in Constructors class ConferenceType extends AbstractType { public
function __construct(Venue $venue) { $this->venue = $venue; } public function buildForm( FormBuilderInterface $builder, array $options ) { // ... if ($this->venue->hasWifi()) { $builder->add('ssid'); } }
Use Form Events for Related Data public function buildForm(FormBuilderInterface $builder,
array $options) { // ... $builder->addEventListener( FormEvents::PRE_SET_DATA, function (FormEvent $e) { if ($e->getData()->hasWifi()) { $e->getForm()->add('ssid'); } } );
Define Types as Services (for reusable Bundles) form_conference: class: "Sensio\[…]\Form\ConferenceType"
tags: - { name: twig.extension, alias: conference }
public function createAction(Request $request) { $form = $this->createForm('conference'); $form ->handleRequest();
// ... Define Types as Services (for reusable Bundles)
Don‘t disable CSRF public function setDefaultOptions( OptionsResolverInterface $resolver ) {
$resolver->setDefaults( array( // ... 'csrf_protection' => false ) ); }
Thanks! Questions? Please give feedback: https://joind.in/11884