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
JavaScriptツール群「UnJS」を5分で一気に駆け巡る!
k1tikurisu
9
1.8k
Boost Performance and Developer Productivity with Jakarta EE 11
ivargrimstad
0
250
技術を根付かせる / How to make technology take root
kubode
1
250
Rails アプリ地図考 Flush Cut
makicamel
1
120
Amazon Bedrock Multi Agentsを試してきた
tm2
1
290
チームリードになって変わったこと
isaka1022
0
200
Open source software: how to live long and go far
gaelvaroquaux
0
630
さいきょうのレイヤードアーキテクチャについて考えてみた
yahiru
3
750
Flutter × Firebase Genkit で加速する生成 AI アプリ開発
coborinai
0
160
2024年のWebフロントエンドのふりかえりと2025年
sakito
2
250
ソフトウェアエンジニアの成長
masuda220
PRO
10
1.1k
法律の脱レガシーに学ぶフロントエンド刷新
oguemon
5
740
Featured
See All Featured
Let's Do A Bunch of Simple Stuff to Make Websites Faster
chriscoyier
507
140k
A Philosophy of Restraint
colly
203
16k
Product Roadmaps are Hard
iamctodd
PRO
50
11k
Learning to Love Humans: Emotional Interface Design
aarron
273
40k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.1k
How to Think Like a Performance Engineer
csswizardry
22
1.3k
Raft: Consensus for Rubyists
vanstee
137
6.8k
Facilitating Awesome Meetings
lara
52
6.2k
GitHub's CSS Performance
jonrohan
1030
460k
Stop Working from a Prison Cell
hatefulcrawdad
267
20k
Become a Pro
speakerdeck
PRO
26
5.1k
Testing 201, or: Great Expectations
jmmastey
42
7.2k
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