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
0
360
Best Practices in Symfony2
Going beyond the Official Best Practices in Symfony2
Andreas Hucks
November 12, 2014
Tweet
Share
More Decks by Andreas Hucks
See All by Andreas Hucks
Divide and Conquer (LonghornPHP 2019)
meandmymonkey
0
120
Symfony Internals
meandmymonkey
3
770
Divide and Conquer
meandmymonkey
1
630
Deptrac - Keep Your Architecture Clean
meandmymonkey
0
620
Introduction to Docker at PHPBenelux2015
meandmymonkey
3
800
Introduction to Docker at PHPNW2014
meandmymonkey
4
390
O(ops), Authentication!
meandmymonkey
4
800
Best Practices in Symfony2
meandmymonkey
15
1.7k
Best Practices in Symfony2
meandmymonkey
28
4.5k
Other Decks in Programming
See All in Programming
ファインディLT_ポケモン対戦の定量的分析
fufufukakaka
0
700
Amazon Q Developer Proで効率化するAPI開発入門
seike460
PRO
0
110
PHPのバージョンアップ時にも役立ったAST
matsuo_atsushi
0
100
データの整合性を保つ非同期処理アーキテクチャパターン / Async Architecture Patterns
mokuo
46
17k
XStateを用いた堅牢なReact Components設計~複雑なClient Stateをシンプルに~ @React Tokyo ミートアップ #2
kfurusho
1
900
『テスト書いた方が開発が早いじゃん』を解き明かす #phpcon_nagoya
o0h
PRO
2
170
Compose でデザインと実装の差異を減らすための取り組み
oidy
1
310
SpringBoot3.4の構造化ログ #kanjava
irof
2
990
Honoのおもしろいミドルウェアをみてみよう
yusukebe
1
210
iOSエンジニアから始める visionOS アプリ開発
nao_randd
3
130
負債になりにくいCSSをデザイナとつくるには?
fsubal
9
2.4k
GAEログのコスト削減
mot_techtalk
0
120
Featured
See All Featured
GitHub's CSS Performance
jonrohan
1030
460k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
120k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
How STYLIGHT went responsive
nonsquared
98
5.4k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
Typedesign – Prime Four
hannesfritz
40
2.5k
Performance Is Good for Brains [We Love Speed 2024]
tammyeverts
7
630
Optimizing for Happiness
mojombo
376
70k
Building Your Own Lightsaber
phodgson
104
6.2k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
366
25k
Principles of Awesome APIs and How to Build Them.
keavy
126
17k
CSS Pre-Processors: Stylus, Less & Sass
bermonpainter
356
29k
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