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
320
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
110
Symfony Internals
meandmymonkey
3
740
Divide and Conquer
meandmymonkey
1
610
Deptrac - Keep Your Architecture Clean
meandmymonkey
0
590
Introduction to Docker at PHPBenelux2015
meandmymonkey
3
790
Introduction to Docker at PHPNW2014
meandmymonkey
4
390
O(ops), Authentication!
meandmymonkey
4
780
Best Practices in Symfony2
meandmymonkey
15
1.7k
Best Practices in Symfony2
meandmymonkey
28
4.5k
Other Decks in Programming
See All in Programming
「Chatwork」Android版アプリを 支える単体テストの現在
okuzawats
0
180
バグを見つけた?それAppleに直してもらおう!
uetyo
0
180
ソフトウェアの振る舞いに着目し 複雑な要件の開発に立ち向かう
rickyban
0
890
rails stats で紐解く ANDPAD のイマを支える技術たち
andpad
1
290
KMP와 kotlinx.rpc로 서버와 클라이언트 동기화
kwakeuijin
0
140
From Translations to Multi Dimension Entities
alexanderschranz
2
130
見えないメモリを観測する: PHP 8.4 `pg_result_memory_size()` とSQL結果のメモリ管理
kentaroutakeda
0
160
Cloudflare MCP ServerでClaude Desktop からWeb APIを構築
kutakutat
1
540
今年のアップデートで振り返るCDKセキュリティのシフトレフト/2024-cdk-security-shift-left
tomoki10
0
190
RWC 2024 DICOM & ISO/IEC 2022
m_seki
0
210
たのしいparse.y
ydah
3
120
アクターシステムに頼らずEvent Sourcingする方法について
j5ik2o
4
210
Featured
See All Featured
Navigating Team Friction
lara
183
15k
Imperfection Machines: The Place of Print at Facebook
scottboms
266
13k
Exploring the Power of Turbo Streams & Action Cable | RailsConf2023
kevinliebholz
28
4.4k
Raft: Consensus for Rubyists
vanstee
137
6.7k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Building Your Own Lightsaber
phodgson
103
6.1k
Documentation Writing (for coders)
carmenintech
66
4.5k
Done Done
chrislema
181
16k
How to train your dragon (web standard)
notwaldorf
88
5.7k
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
2
170
A Tale of Four Properties
chriscoyier
157
23k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
169
50k
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