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
430
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
160
Symfony Internals
meandmymonkey
3
850
Divide and Conquer
meandmymonkey
1
670
Deptrac - Keep Your Architecture Clean
meandmymonkey
0
700
Introduction to Docker at PHPBenelux2015
meandmymonkey
3
850
Introduction to Docker at PHPNW2014
meandmymonkey
4
410
O(ops), Authentication!
meandmymonkey
4
900
Best Practices in Symfony2
meandmymonkey
15
1.8k
Best Practices in Symfony2
meandmymonkey
28
4.6k
Other Decks in Programming
See All in Programming
AIを活用し、今後に備えるための技術知識 / Basic Knowledge to Utilize AI
kishida
22
5.7k
[FEConf 2025] 모노레포 절망편, 14개 레포로 부활하기까지 걸린 1년
mmmaxkim
0
1.6k
MCPでVibe Working。そして、結局はContext Eng(略)/ Working with Vibe on MCP And Context Eng
rkaga
5
2.3k
Android 16 × Jetpack Composeで縦書きテキストエディタを作ろう / Vertical Text Editor with Compose on Android 16
cc4966
1
200
Compose Multiplatform × AI で作る、次世代アプリ開発支援ツールの設計と実装
thagikura
0
150
How Android Uses Data Structures Behind The Scenes
l2hyunwoo
0
440
テストカバレッジ100%を10年続けて得られた学びと品質
mottyzzz
2
590
@Environment(\.keyPath)那么好我不允许你们不知道! / atEnvironment keyPath is so good and you should know it!
lovee
0
120
Oracle Database Technology Night 92 Database Connection control FAN-AC
oracle4engineer
PRO
1
440
Putting The Genie in the Bottle - A Crash Course on running LLMs on Android
iurysza
0
140
AIコーディングAgentとの向き合い方
eycjur
0
270
OSS開発者という働き方
andpad
5
1.7k
Featured
See All Featured
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
252
21k
Rebuilding a faster, lazier Slack
samanthasiow
83
9.2k
Facilitating Awesome Meetings
lara
55
6.5k
Scaling GitHub
holman
463
140k
Refactoring Trust on Your Teams (GOTO; Chicago 2020)
rmw
34
3.1k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
667
120k
Why You Should Never Use an ORM
jnunemaker
PRO
59
9.5k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
15
1.6k
Why Our Code Smells
bkeepers
PRO
339
57k
Building a Scalable Design System with Sketch
lauravandoore
462
33k
Done Done
chrislema
185
16k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
367
27k
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