SlideShare a Scribd company logo
1 of 197
Download to read offline
SYMFONY 
TIPS & TRICKS 
SymfonyCon 
Madrid - 27 November 2014 
Javier Eguiluz
About me 
evangelist and trainer at
My Symfony work
Why Symfony Tips and Tricks? 
I don’t work as 
a developer But I read and 
review code every 
single day 
I also read every 
single blog post 
published about 
Symfony These are the tips and 
tricks discovered 
during this year.
Thanks to my awesome co-workers for 
sharing their knowledge! 
Grégoire Nicolas Hugo 
Tugdual Sarah 
Julien 
Loïc Jérémy Romain 
Joseph 
FX
Agenda Assets Performance Logging 
Legacy Testing Config 
Parallel Doctrine Value 
Objects
Asset 
management
Named assets
Typical asset linking 
{% javascripts 
'@AppBundle/Resources/public/js/jquery.js' 
'@AppBundle/Resources/public/js/jquery.ui.js' 
'@AppBundle/Resources/public/js/bootstrap.js' 
'@AppBundle/Resources/public/js/*' %} 
<script src="{{ asset_url }}"></script> 
{% endjavascripts %}
Defining named assets 
# app/config/config.yml 
assetic: 
assets: 
# ... 
bootstrap_js: 
inputs: 
- '@AppBundle/Resources/public/js/jquery.js' 
- '@AppBundle/Resources/public/js/jquery.ui.js' 
- '@AppBundle/Resources/public/js/bootstrap.js'
Using named assets 
{% javascripts 
'@bootstrap_js' 
'@AppBundle/Resources/public/js/*' %} 
<script src="{{ asset_url }}"></script> 
{% endjavascripts %}
Asset 
packages
asset( ) adds a leading slash 
<img src="{{ asset('images/logo.png') }}" />
asset( ) adds a leading slash 
<img src="{{ asset('images/logo.png') }}" /> 
<img src="/images/logo.png" />
Easiest way to define a base URL 
# app/config/config.yml 
framework: 
templating: 
assets_base_urls: 
http: ['http://static.example.com/images'] 
ssl: ['https://static.example.com/images']
Easiest way to define a base URL 
# app/config/config.yml 
framework: 
templating: 
assets_base_urls: 
http: ['http://static.example.com/images'] 
ssl: ['https://static.example.com/images'] 
if you configure several 
base URLs, Symfony will 
select one each time to 
balance asset load
Protocol-relative base URL 
# app/config/config.yml 
framework: 
templating: 
assets_base_urls: '//static.example.com/images'
AWshast eif wt ep waanct ktoa mgodeifsy 
the base URL just for a few 
selected assets?
What if we want to modify 
the base URL just for a few 
selected assets? 
Asset packages
Asset packages 
Group assets 
logically. 
Define config for 
each package.
Defining asset packages 
# app/config/config.yml 
framework: 
templating: 
packages: 
avatars: 
base_urls: '//static.example.com/avatars/'
Asset packages in practice 
<img src="{{ asset('...', 'avatars') }}" />
Asset packages in practice 
<img src="{{ asset('...', 'avatars') }}" /> 
<img src="//static.example.com/ 
avatars/logo.png" />
Packages can define any asset option 
# app/config/config.yml 
framework: 
templating: 
packages: 
avatars: 
version: '20141127' 
base_urls: '//static.example.com/avatars/'
mini-tip
Using the Symfony console 
$ php app/console --env=prod 
assetic:dump
Using the Symfony console 
$ php app/console --env=prod 
assetic:dump 
WRONG RIGHT
Using the Symfony console 
$ ./app/console --env=prod 
assetic:dump 
WRONG RIGHT
Using the Symfony console 
$ sf --env=prod assetic:dump 
alias sf = php app/console 
WRONG RIGHT
Using good Symfony console shortcuts 
$ dev ... 
$ prod ... 
alias dev = php app/console --env=dev 
alias prod = php app/console --env=prod
PROS 
$ prod a:d 
$ dev r:m / 
$ dev cont:d 
NEWCOMERS 
$ php app/console --env=prod assetic:dump 
$ php app/console router:match / 
$ php app/console container:debug
Performance
Optimize 
autoloading
Unoptimized auto-loading
Optimized auto-loading 
$ composer dump-autoload --optimize
Don't load test classes in production 
{ 
"autoload": { 
"psr-4": { "MyLibrary": "src/" } 
}, 
"autoload-dev": { 
"psr-4": { "MyLibraryTests": "tests/" } 
} 
}
Add classes to 
compile
Three important Symfony files 
• app/bootstrap.php.cache 
Internal Symfony classes. 
• app/cache/<env>/ 
appDevDebugProjectContainer.php 
Compiled container (services and parameters). 
• app/cache/<env>/classes.php 
Symfony bundles classes.
Adding your own classes for compiling 
namespace AppBundleDependencyInjection; 
! 
use SymfonyComponentConfigFileLocator; 
use SymfonyComponentDependencyInjectionContainerBuilder; 
use SymfonyComponentHttpKernelDependencyInjectionExtension; 
! 
class AppExtension extends Extension 
{ 
public function load(array $configs, ContainerBuilder $container) 
{ 
// ... 
! 
$this->addClassesToCompile([ 
'AppBundleFullNamespaceClass' 
]); 
} 
}
Adding your own classes for compiling 
namespace AppBundleDependencyInjection; 
! 
use SymfonyComponentConfigFileLocator; 
use SymfonyComponentDependencyInjectionContainerBuilder; 
use SymfonyComponentHttpKernelDependencyInjectionExtension; 
! 
class AppExtension extends Extension 
{ 
public function load(array $configs, ContainerBuilder $container) 
{ 
// ... 
! 
$this->addClassesToCompile([ 
'AppBundleFullNamespaceClass' 
]); 
} 
} 
WARNING 
It doesn't work if 
the classes define 
annotations
mini-tip
How can you enable the 
profiler on production for 
some specific users?
The easy way: restrict by path 
# app/config/config.yml 
framework: 
# ... 
profiler: 
matcher: 
path: ^/admin/
The right way: restrict by user (1/2) 
# app/config/config.yml 
framework: 
# ... 
profiler: 
matcher: 
service: app.profiler_matcher 
! 
services: 
app.profiler_matcher: 
class: AppBundleProfilerMatcher 
arguments: ["@security.context"]
The right way: restrict by user (2/2) 
namespace AppBundleProfiler; 
! 
use SymfonyComponentSecurityCoreSecurityContext; 
use SymfonyComponentHttpFoundationRequest; 
use SymfonyComponentHttpFoundationRequestMatcherInterface; 
! 
class Matcher implements RequestMatcherInterface 
{ 
protected $securityContext; 
! 
public function __construct(SecurityContext $securityContext) 
{ 
$this->securityContext = $securityContext; 
} 
! 
public function matches(Request $request) 
{ 
return $this->securityContext->isGranted('ROLE_ADMIN'); 
} 
}
Better logs with 
Monolog
De-clutter 
your logs
Symfony logs a lot of information 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] 
[2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerDebugHandlersListener::configure". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleDataCollectorRouterDataCollector::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerControllerListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] 
[2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, 
p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] 
[2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
Is this useful for your application? 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] 
[2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerDebugHandlersListener::configure". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleDataCollectorRouterDataCollector::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerControllerListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] 
[2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, 
p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] 
[2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
Is this useful for your application? 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] 
2014-[[2014-2014-11-24 12:24:22] security.11-INFO: Populated 24 SecurityContext 12:24:with an anonymous Token [] [] 
11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener 22] "SymfonyComponentevent.HttpKernelEventListenerDEBUG: DebugHandlersListener::Notified 
configure". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] 
[event 2014-11-24 12:24:22] event."DEBUG: kernel.Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentto HttpKernellistener EventListenerRouterListener::onKernelRequest". "Symfony 
[] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] 
[2014-Bundle11-24 12:24:22] event.DEBUG: FrameworkBundleNotified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleEventListener 
DataCollectorRouterDataCollector::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] 
[2014-SessionListener::11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener onKernelRequest". "SensioBundleFrameworkExtraBundleEventListenerControllerListener::[] onKernelController". [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] 
[] [] 
[] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] 
[2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, 
p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] 
[2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
Is this useful for your application? 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: ... 
[2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ...
Is this useful for your application? 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: ... 
[2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... 
[2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... 
log channel
Hide event logs (if you don't need them) 
# app/config/dev.yml 
monolog: 
handlers: 
main: 
type: stream 
path: "%kernel.logs_dir%/%kernel.environment%.log" 
level: debug 
channels: "!event"
De-cluttered log files 
[2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": 
"AppBundleControllerDefaultController::indexAction", "_route": "homepage") 
! 
[2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token 
! 
[2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug 
AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS 
authorEmail5, p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? 
ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] 
! 
[2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session
Better log 
emails
Email logs related to 5xx errors 
# app/config/config_prod.yml 
monolog: 
handlers: 
mail: 
type: fingers_crossed 
action_level: critical 
handler: buffered 
buffered: 
type: buffer 
handler: swift 
swift: 
type: swift_mailer 
from_email: error@example.com 
to_email: error@example.com 
subject: An Error Occurred! 
level: debug
Email logs related to 5xx errors 
# app/config/config_prod.yml 
monolog: 
handlers: 
mail: 
type: fingers_crossed 
action_level: critical 
handler: buffered 
buffered: 
type: buffer 
handler: swift 
swift: 
type: swift_mailer 
from_email: error@example.com 
to_email: error@example.com 
subject: An Error Occurred! 
level: debug
Email logs related to 4xx errors 
# app/config/config_prod.yml 
monolog: 
handlers: 
mail: 
type: fingers_crossed 
action_level: error 
handler: buffered 
buffered: 
# ... 
swift: 
# ...
Don't email 404 errors 
# app/config/config_prod.yml 
monolog: 
handlers: 
mail: 
type: fingers_crossed 
action_level: error 
excluded_404: 
- ^/ 
handler: buffered 
buffered: 
# ... 
swift: 
# ...
Organize your 
logs
Use different log files for each channel 
# app/config/config_prod.yml 
monolog: 
handlers: 
main: 
type: stream 
path: "%kernel.logs_dir%/%kernel.environment%.log" 
level: debug 
channels: ["!event"] 
security: 
type: stream 
path: "%kernel.logs_dir%/security-%kernel.environment%.log" 
level: debug 
channels: "security"
Format your 
log messages
Don't do this 
$logger->info(sprintf( 
"Order number %s processed for %s user", 
$number, $user 
));
Do this instead 
$logger->info( 
"Order number {num} processed for {name} user", 
['num' => $number, 'name' => $user] 
);
Do this instead 
$logger->info( 
"Order number {num} processed for {name} user", 
['num' => $number, 'name' => $user] 
); 
WARNING 
It doesn't work 
unless the PSR 
log processor is 
enabled.
Enable the PsrLogMessageProcessor 
# app/config/config_prod.yml 
services: 
monolog_processor: 
class: MonologProcessorPsrLogMessageProcessor 
tags: 
- { name: monolog.processor }
Monolog includes several processors 
# app/config/config_prod.yml 
services: 
monolog_processor: 
class: MonologProcessorIntrospectionProcessor 
tags: 
- { name: monolog.processor }
Create your own processor 
$logger->info( 
"Order processed successfully.", $order 
);
Create your own processor 
$logger->info( 
"Order processed successfully.", $order 
); 
[2014-11-24 15:15:58] app.INFO: Order processed 
successfully. { "order": { "number": "023924382982", 
"user":"John Smith", "total":"34.99","status":"PAID" }}
Custom monolog processor 
namespace AppBundleLoggerOrderProcessor; 
! 
use AppBundleEntityOrder; 
! 
class OrderProcessor 
{ 
public function __invoke(array $record) 
{ 
if (isset($record['context']['order']) && $record['context']['order'] instanceof Order) { 
$order = $record['context']['order']; 
$record['context']['order'] = [ 
'number' => $order->getNumber(), 
'user' => $order->get..., 
'total' => $order->get..., 
'status' => $order->get..., 
]; 
} 
! 
return $record; 
} 
# app/config/config_prod.yml 
services: 
monolog_processor: 
class: AppBundleLoggerOrderProcessor 
tags: 
- { name: monolog.processor }
mini-tip
Medium-sized and 
Large Applications 
Medium-sized and 
Small Applications 
Using your own mailer is OK.
Not all emails should be treated equally 
Sign Ups 
Lost password 
Notifications 
Newsletters 
Spam
Instant and delayed mailers 
# app/config/config.yml 
swiftmailer: 
default_mailer: delayed 
mailers: 
instant: 
# ... 
spool: ~ 
delayed: 
# ... 
spool: 
type: file 
path: "%kernel.cache_dir%/swiftmailer/spool"
Using prioritized mailers 
// high-priority emails 
$container->get('swiftmailer.mailer.instant')->... 
! 
// regular emails 
$container->get('swiftmailer.mailer')->... 
$container->get('swiftmailer.mailer.delayed')->...
Legacy 
applications
Pokemon™ 
strategy 
(catch’em all)
Controller that captures legacy URLs 
class LegacyController extends Controller 
{ 
/** 
* @Route("/{filename}.php", name="_legacy") 
*/ 
public function legacyAction($filename) 
{ 
$legacyPath = $this->container->getParameter('legacy.path'); 
! 
ob_start(); 
chdir($legacyAppPath); 
require_once $legacyAppPath.$filename.'.php'; 
$content = ob_get_clean(); 
! 
return new Response($content); 
} 
} 
Source: http://cvuorinen.net/slides/symfony-legacy-integration/
Embbedding 
legacy apps
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException 
kernel.exception
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException 
kernel.exception
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException 
kernel.exception
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException 
kernel.exception 
Response
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
NotFound 
HttpException 
kernel.exception 
Response 
Response
App 
Kernel 
Router 
Listener 
LegacyKernel Listener 
LegacyKernel Legacy App 
Request 
kernel.request 
Response 
NotFound 
HttpException 
kernel.exception 
Response 
Response
LegacyKernel to process requests 
class LegacyKernel implements HttpKernelInterface 
{ 
public function handle(Request $request, ...) 
{ 
ob_start(); 
! 
$legacyDir = dirname($this->legacyAppPath); 
chdir($legacyDir); 
require_once $this->legacyAppPath; 
! 
$content = ob_get_clean(); 
! 
return new Response($content); 
} 
} 
Source: http://cvuorinen.net/slides/symfony-legacy-integration/
Listener to treat 404 as legacy requests 
class LegacyKernelListener implements EventSubscriberInterface 
{ 
public function onKernelException($event) 
{ 
$exception = $event->getException(); 
! 
if ($exception instanceof NotFoundHttpException) { 
$request = $event->getRequest(); 
$response = $this->legacyKernel->handle($request); 
! 
$response->headers->set('X-Status-Code', 200); 
! 
$event->setResponse($response); 
} 
} 
} Source: http://cvuorinen.net/slides/symfony-legacy-integration/
mini-tip
Click-to-file in Symfony Exceptions 
Textmate
PHPStorm is now supported natively 
# app/config/config.yml 
framework: 
ide: "phpstorm://open?file=%%f&line=%%l"
Testing tips
Prophecy
Prophecy is a highly 
opinionated yet very 
powerful and flexible PHP 
object mocking framework.
PHPUnit has built-in 
Prophecy mocks support 
since 4.5 version.
A complete Prophecy mock 
class SubjectTest extends PHPUnit_Framework_TestCase 
{ 
public function testObserversAreUpdated() 
{ 
$subject = new Subject('My subject'); 
! 
$observer = $this->prophesize('Observer'); 
$observer->update('something')->shouldBeCalled(); 
! 
$subject->attach($observer->reveal()); 
! 
$subject->doSomething(); 
} 
}
Prophecy vs traditional mocks (1/2) 
! 
! 
$observer = $this->prophesize('Observer'); 
! 
! 
! 
$observer = $this->getMockBuilder('Observer') 
->setMethods(array('update')) 
->getMock(); 
Prophecy 
PHPUnit
Prophecy vs traditional mocks (2/2) 
! 
Prophecy 
! 
$observer->update('something')->shouldBeCalled(); 
! 
! 
! 
$observer->expects($this->once()) 
->method('update') 
->with($this->equalTo('something')); 
PHPUnit
Faster smoke 
testing
Smoke testing is preliminary 
testing to reveal simple 
failures severe enough to 
reject a prospective 
software release.
Unnecessarily slow tests 
namespace AppBundleTestsController; 
! 
use SymfonyBundleFrameworkBundleTestWebTestCase; 
! 
class DefaultControllerTest extends WebTestCase 
{ 
/** @dataProvider provideUrls */ 
public function testPageIsSuccessful($url) 
{ 
$client = self::createClient(); 
$client->request('GET', $url); 
$this->assertTrue($client->getResponse()->isSuccessful()); 
} 
! 
public function provideUrls() 
{ 
// ... 
} 
}
Functional tests without WebTestCase 
class SmokeTest extends PHPUnit_Framework_TestCase 
{ 
private $app; 
! 
protected function setUp() 
{ 
$this->app = new AppKernel('test', false); 
$this->app->boot(); 
} 
! 
/** @dataProvider provideUrls */ 
public function testUrl($url) 
{ 
$request = new Request::create($url, 'GET'); 
$response = $this->app->handle($request); 
! 
$this->assertTrue($response->isSuccessful()); 
} 
} Source: http://gnugat.github.io/2014/11/15/sf2-quick-functional-tests.html
mini-tip
Impersonating users 
# app/config/security.yml 
security: 
firewalls: 
main: 
# ... 
switch_user: true 
http://example.com/path?_switch_user=fabien
WARNING 
Impersonating is very 
dangerous and it can 
lead to severe errors.
Visual hints when impersonating users 
<body class=" 
{% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %} 
impersonating 
{% endif %} 
"> 
! 
.impersonating { 
background: rgba(204, 0, 0, 0.15); 
}
Be careful when impersonating users
Configuration
Yaml casting
Implicit YAML casting 
key: 
value1: 5 
value2: 2.7 
value3: 2014-11-27
Implicit YAML casting 
key: 
value1: 5 
value2: 2.7 
value3: 2014-11-27 
array(1) { 
["key"]=> array(3) { 
["value1"] => int(5) 
["value2"] => float(2.7) 
["value3"] => int(1417042800) 
} 
}
Explicit YAML casting 
key: 
value1: !str 5 
value2: ! 2.7 
value3: !str 2014-11-27
Explicit YAML casting 
key: 
value1: !str 5 
value2: ! 2.7 
value3: !str 2014-11-27 
array(1) { 
["key"]=> array(3) { 
["value1"] => string(1) "5" 
["value2"] => int(2) 
["value3"] => string(10) "2014-11-27" 
} 
}
Explicit YAML casting 
key: 
value1: !str 5 
value2: ! 2.7 
value3: !str 2014-11-27 
array(1) { 
["key"]=> array(3) { 
["value1"] => string(1) "5" 
["value2"] => int(2) 
["value3"] => string(10) "2014-11-27" 
} 
} 
! = int !str = string 
!!php/object: = serialized object
Custom 
Expressions
Expression Language in action 
/** 
* @Route("/post/{id}") 
* @Cache(smaxage="15", lastModified="post.getUpdatedAt()") 
*/ 
public function showAction(Post $post) { ... } 
! 
! 
/** 
* @AssertExpression("this.getFoo() == 'fo'", message="Not good!") 
*/ 
class Obj { ... }
Custom md5( ) function 
use SymfonyComponentExpressionLanguageExpressionFunction; 
use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; 
! 
class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface 
{ 
return array( 
new ExpressionFunction( 
'md5', 
function ($str) { 
return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); 
}, 
function ($arguments, $str) { 
return is_string($str) ? md5($str) : $str; 
} 
); 
); 
}
Custom md5( ) function 
use SymfonyComponentExpressionLanguageExpressionFunction; 
use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; 
! 
class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface 
{ 
return array( 
new ExpressionFunction( 
'md5', 
function ($str) { 
return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); 
}, 
function ($arguments, $str) { 
return is_string($str) ? md5($str) : $str; 
} 
); 
); 
} 
compiled expression
Custom md5( ) function 
use SymfonyComponentExpressionLanguageExpressionFunction; 
use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; 
! 
class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface 
{ 
return array( 
new ExpressionFunction( 
'md5', 
function ($str) { 
return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); 
}, 
function ($arguments, $str) { 
return is_string($str) ? md5($str) : $str; 
} 
); 
); 
} 
compiled expression 
executed expression
Using custom functions in expressions 
namespace AppBundleController; 
! 
use SymfonyBundleFrameworkBundleControllerController; 
use SensioBundleFrameworkExtraBundleConfigurationSecurity; 
! 
class BlogController extends Controller 
{ 
/** 
* @Security("md5(user.email) == post.author_id") 
* ... 
*/ 
public function editAction(...) 
{ 
// ... 
} 
} 
# app/config/services.yml 
services: 
app.expressions: 
class: AppBundleExpressionLanguageProvider 
tags: 
- { name: security.expression_language_provider }
Expression Language service tags 
services: 
app.expressions: 
class: ... 
tags: 
- { name: security.expression_language_provider } 
! 
! 
services: 
app.expressions: 
class: ... 
tags: 
- { name: routing.expression_language_provider }
mini-tip
Have you ever used the 
{% do %} Twig tag?
Using the {% do %} Twig tag 
{% do form.field.setRendered %} 
! 
{{ form_start(form) }} 
{{ form_errors(form) }} 
! 
{{ form_row(...) }} 
{{ form_end(form) }} 
Source: http://stackoverflow.com/q/10570002
Using the {% do %} Twig tag 
{% do form.field.setRendered %} 
! 
{{ form_start(form) }} 
{{ form_errors(form) }} 
! 
{{ form_row(...) }} 
{{ form_end(form) }} Makes form_rest( ) 
and form_end( ) not 
display the given field. Source: http://stackoverflow.com/q/10570002
Parallel 
everything
How many cores/threads 
has your CPU?
How many of them are used 
while developing the 
application?
Parallel tests
« TDD test suites should run 
in 10 seconds or less » 
Source: http://blog.ploeh.dk/2012/05/24/TDDtestsuitesshouldrunin10secondsorless/
Parallel test execution with Fastest 
# Install 
$ composer require --dev 'liuggio/fastest' 'dev-master' 
! 
# Execute tests in 4 parallel threads 
$ find src/ -name "*Test.php" | ./vendor/bin/fastest 
"phpunit -c app {};"
Database isolation for parallel tests 
getenv('ENV_TEST_CHANNEL'); 
getenv('ENV_TEST_CHANNEL_READABLE'); 
getenv('ENV_TEST_CHANNELS_NUMBER'); 
getenv('ENV_TEST_ARGUMENT'); 
getenv('ENV_TEST_INC_NUMBER'); 
getenv('ENV_TEST_IS_FIRST_ON_CHANNEL');
Database isolation for parallel tests 
getenv('ENV_TEST_CHANNEL'); 
getenv('ENV_TEST_CHANNEL_READABLE'); 
getenv('ENV_TEST_CHANNELS_NUMBER'); 
getenv('ENV_TEST_ARGUMENT'); 
getenv('ENV_TEST_INC_NUMBER'); 
getenv('ENV_TEST_IS_FIRST_ON_CHANNEL'); 
30 
minutes 
7 
minutes
Alternatives to Fastest 
• Paratest (Official PHPUnit plugin) 
github.com/brianium/paratest 
• Parallel PHPUnit 
github.com/socialpoint-labs/parallel-phpunit 
• Docker + PHPUnit 
marmelab.com/blog/2013/11/04/how-to-use-docker-to-run-phpunit-tests-in-parallel.html
Parallel assets
Dumping assets … slowly 
$ php app/console --env=prod 
assetic:dump
Parallel asset dumping 
$ php app/console --env=prod 
assetic:dump 
--forks=4
Parallel asset dumping 
$ php app/console --env=prod 
$ prod a:d --forks=4 
assetic:dump 
--forks=4
Parallel asset dumping 
$ php app/console --env=prod 
$ prod a:d --forks=4 
assetic:dump 
--forks=4 
# it requires to install the Spork library 
$ composer require kriswallsmith/spork
Parallel HTTP 
requests
Parallel HTTP requests with Guzzle 
use GuzzleHttpPool; 
use GuzzleHttpClient; 
! 
$client = new Client(); 
! 
$requests = [ 
$client->createRequest('GET', 'http://example.org'), 
$client->createRequest('DELETE', 'http://example.org/delete'), 
$client->createRequest('PUT', 'http://example.org/put', ['body' => 'test']) 
]; 
! 
(new Pool($client, $requests))->wait();
Rarely used 
Doctrine features
Ordering with 
expressions
This does not work 
SELECT t 
FROM Talks t 
ORDER BY t.comments + t.likes_count
This does work 
SELECT t, 
(t.comments + t.likes_count) AS HIDDEN score 
FROM Talks t 
ORDER BY score 
!
Partial queries
Selecting specific properties 
public function findPost() 
{ 
return $this 
->createQueryBuilder('p') 
->select('p.id, p.title') 
->where('p.id = :id')->setParameter('id', 1) 
->getQuery() 
->getResult() 
; 
}
The executed SQL query 
doctrine.DEBUG: 
! 
SELECT p0_.id AS id_0, 
p0_.title AS title_1 
FROM Post p0_ 
WHERE p0_.id = ?
The resulting "object" 
array(1) { 
[0]=> array(2) { 
["id"] => int(1) 
["title"] => string(7) "Post #1" 
} 
}
Using Doctrine partials 
public function findPost() 
{ 
return $this 
->createQueryBuilder('p') 
->select('partial p.{id, title}') 
->where('p.id = :id')->setParameter('id', 1) 
->getQuery() 
->getResult() 
; 
}
The executed SQL query 
doctrine.DEBUG: 
! 
SELECT p0_.id AS id_0, 
p0_.title AS title_1 
FROM Post p0_ 
WHERE p0_.id = ?
The resulting object 
array(1) { 
[0]=> 
object(stdClass)#676 (9) { 
["__CLASS__"] => string(21) "AppBundleEntityPost" 
["id"] => int(1) 
["title"] => string(7) "Post #1" 
["slug"] => NULL 
["summary"] => NULL 
["content"] => NULL 
["authorEmail"] => NULL 
["publishedAt"] => NULL 
["comments"] => string(8) "Array(5)" 
} 
}
The resulting object 
array(1) { 
[0]=> 
object(stdClass)#676 (9) { 
["__CLASS__"] => string(21) "AppBundleEntityPost" 
["id"] => int(1) 
["title"] => string(7) "Post #1" 
["slug"] => NULL 
["summary"] => NULL 
["content"] => NULL 
["authorEmail"] => NULL 
["publishedAt"] => NULL 
["comments"] => string(8) "Array(5)" 
} 
}
WARNING 
Doctrine Partials make 
your code very fragile. 
Use them with caution.
Legitimate use cases for Partials 
• Specific (and limited) parts of the 
application where performance is the top 
priority. 
• Never when developing the application 
(wrong premature optimization).
schema_filter 
option
Configuring schema_filter option 
# app/config/config.yml 
doctrine: 
dbal: 
default_connection: default 
# ... 
schema_filter: ~^(?!legacy_)~
Filtering tables managed by other apps 
# app/config/config.yml 
doctrine: 
dbal: 
default_connection: default 
# ... 
schema_filter: ~^(?!(django|auth)_)~ 
Source: http://javaguirre.net/2013/10/16/symfony-migrations-and-django-admin/
Using schema_filter to migrate gradually 
# app/config/config.yml 
doctrine: 
dbal: 
default_connection: default 
connections: 
default: 
dbname: "%database_name%" 
# ... 
legacy: 
# ... 
schema_filter: ~^(?!(prefix)_)~ 
Source: http://espend.de/artikel/doctrine-und-symfony2-orm-entities-aus-datenbank-erzeugen.html
Doctrine 
Filters
How do you deal with 
global SQL clauses?
Examples of global SQL clauses 
CMS applications 
Display only published 
contents. 
WHERE status = :published
Examples of global SQL clauses 
CMS applications 
Display only published 
Never display (soft) 
contents. 
deleted items. 
WHERE status = :published WHERE status = :not_deleted
Examples of global SQL clauses 
CMS applications 
Display only published 
Never display (soft) 
contents. 
deleted items. 
WHERE status = :published WHERE status = :not_deleted 
Commerce applications 
Display only invoices/ 
orders for the given user. WHERE user_id = :user_id
Doctrine filters allow to 
add SQL to the conditional 
clauses of queries, 
regardless the place where 
the SQL is generated.
Creating a global Locale filter 
namespace AppBundleFilterLocaleFilter; 
! 
use DoctrineORMMappingClassMetaData; 
use DoctrineORMQueryFilterSQLFilter; 
! 
class LocaleFilter extends SQLFilter 
{ 
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) 
{ 
return sprintf( 
'%s.%s = %s', 
$targetTableAlias, 'locale', $this->getParameter('locale') 
); 
} 
} 
WHERE locale = 'es'
Enabling the filter globally 
# app/config/config.yml 
doctrine: 
orm: 
filters: 
locale_filter: 
class: AppBundleFilterLocaleFilter 
enabled: true
Using the filter and setting its values 
$filter = $this->em->getFilters() 
->enable('locale_filter'); 
! 
$filter->setParameter('locale', 'es');
What's the point of using 
global filters if you have to 
set up values manually for 
each query?
Setting filter values automatically (1/2) 
services: 
app.locale_filter: 
class: AppBundleFilterLocaleFilter 
arguments: [@session] 
tags: 
- { 
name: kernel.event_listener, 
event: kernel.request, 
method: onKernelRequest 
} 
Source: http://stackoverflow.com/a/15792119
Setting filter values automatically (2/2) 
public function __construct(SessionInterface $session) 
{ 
$this->session = $session; 
} 
! 
// ... 
! 
public function onKernelRequest() 
{ 
$this->em->getConfiguration() 
->addFilter('locale_filter', 'AppBundleFilterLocaleFilter'); 
! 
$filter = $this->em->getFilters()->enable('locale_filter'); 
$filter->setParameter('locale', $this->session->get('user_locale')); 
} 
Source: http://stackoverflow.com/a/15792119
Constraining the scope of the filters 
class LocaleFilter extends SQLFilter 
{ 
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) 
{ 
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { 
return ''; 
} 
! 
// ... 
} 
}
Constraining the scope of the filters 
class LocaleFilter extends SQLFilter 
{ 
public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) 
{ 
if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { 
return ''; 
} 
! 
// ... 
} 
} 
/** @ORMEntity */ 
class Content implements LocalAware 
{ 
// ... 
}
Combining filters with annotations 
namespace AppBundleEntity; 
! 
use AppBundleAnnotationUserAware; 
/** 
* @UserAware(userFieldName="user_id") 
*/ 
class Order { ... }
Combining filters with annotations 
namespace AppBundleEntity; 
WHERE user_id = :id 
! 
use AppBundleAnnotationUserAware; 
/** 
* @UserAware(userFieldName="user_id") 
*/ 
class Order { ... }
Combining filters with annotations 
namespace AppBundleEntity; 
WHERE user_id = :id 
! 
use AppBundleAnnotationUserAware; 
/** 
* @UserAware(userFieldName="user_id") 
*/ 
class Order { ... } 
Learn how to do this at: 
http://blog.michaelperrin.fr/2014/07/25/doctrine-filters/
Value Objects
Value Objects 
in theory
A value object is a small 
object that represents a 
simple entity whose equality 
is not based on identity.
Two value objects are equal 
when they have the same 
value, not necessarily being 
the same object.
Identity is irrelevant for value objects 
object(AppBundleEntityMoney)#25 (3) { 
["id"] => int(25) 
["amount"] => int(10) 
["currency"] => string(3) "EUR" 
} 
! 
object(AppBundleEntityMoney)#267 (3) { 
["id"] => int(267) 
["amount"] => int(10) 
["currency"] => string(3) "EUR" 
} 
10 EUR 
10 EUR
Value Objects 
in Doctrine
A typical Doctrine entity 
namespace AppBundleEntity; 
! 
/** @Entity */ 
class Customer 
{ 
/** @Id @GeneratedValue @Column(type="integer") */ 
protected $id; 
! 
/** @Column(type="string") */ 
protected $name; 
! 
/** @Column(type="string", length=120) **/ 
protected $email_address; 
! 
protected $address_line_1; 
protected $address_line_2; 
protected $city; 
protected $state; 
protected $postalCode; 
protected $country; 
}
A typical Doctrine entity 
namespace AppBundleEntity; 
! 
/** @Entity */ 
class Customer 
{ 
/** @Id @GeneratedValue @Column(type="integer") */ 
protected $id; 
! 
/** @Column(type="string") */ 
protected $name; 
! 
/** @Column(type="string", length=120) **/ 
protected $email_address; 
! 
protected $address_line_1; 
protected $address_line_2; 
protected $city; 
protected $state; 
protected $postalCode; 
protected $country; 
} 
Address 
Value Object
Defining a Value Object in Doctrine 
namespace AppBundleValueObject; 
! 
use DoctrineORMMapping as ORM; 
! 
/** @ORMEmbeddable */ 
class Address 
{ 
/** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ 
protected $line_1; protected $line_2; 
! 
/** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ 
protected $city; protected $state; 
! 
/** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ 
protected $postalCode; protected $country; 
}
Using a Value Object in Doctrine 
namespace AppBundleEntity; 
! 
use DoctrineORMMapping as ORM; 
! 
/** @ORMEntity */ 
class Customer 
{ 
/** @ORMId @ORMGeneratedValue @ORMColumn(type="integer") */ 
protected $id; 
! 
/** @ORMColumn(type="string") */ 
protected $name = ''; 
! 
/** @ORMColumn(type="string", length=120) **/ 
protected $email_address = ''; 
! 
/** @ORMEmbedded(class="AppBundleValueObjectAddress") */ 
protected $address; 
}
Value Objects at SQL level 
CREATE TABLE Post ( 
id INTEGER NOT NULL, 
title VARCHAR(255) NOT NULL, 
slug VARCHAR(255) NOT NULL, 
summary VARCHAR(255) NOT NULL, 
content CLOB NOT NULL, 
authorEmail VARCHAR(255) NOT NULL, 
publishedAt DATETIME NOT NULL, 
address_line_1 VARCHAR(255) NOT NULL, 
address_line_2 VARCHAR(255) NOT NULL, 
address_city VARCHAR(255) NOT NULL, 
address_state VARCHAR(255) NOT NULL, 
address_postalCode VARCHAR(255) NOT NULL, 
address_country VARCHAR(255) NOT NULL, 
PRIMARY KEY(id))
Value Objects at query level 
SELECT c 
FROM Customer c 
WHERE c.address.city = :city
Embeddables are classes 
which are not entities 
themself, but are embedded 
in entities and can also be 
queried in DQL.
Agenda Assets Performance Logging 
Legacy Testing Config 
Parallel Doctrine Value 
Objects
Thank you.
Questions? http://joind.in/talk/view/12944
Contact info 
! 
! 
javier.eguiluz@sensiolabs.com
SymfonyCon 
Madrid - 27 November 2014

More Related Content

What's hot

Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVCNathaniel Richand
 
Asynchronous Programming at Netflix
Asynchronous Programming at NetflixAsynchronous Programming at Netflix
Asynchronous Programming at NetflixC4Media
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code SmellsMario Sangiorgio
 
CQRS / ES & DDD Demystified
CQRS / ES & DDD DemystifiedCQRS / ES & DDD Demystified
CQRS / ES & DDD DemystifiedVic Metcalfe
 
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...Edureka!
 
The Art of Clean code
The Art of Clean codeThe Art of Clean code
The Art of Clean codeVictor Rentea
 
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...Kuntal Bhowmick
 
Design patterns for microservice architecture
Design patterns for microservice architectureDesign patterns for microservice architecture
Design patterns for microservice architectureThe Software House
 
Concevoir, développer et sécuriser des micro-services avec Spring Boot
Concevoir, développer et sécuriser des micro-services avec Spring BootConcevoir, développer et sécuriser des micro-services avec Spring Boot
Concevoir, développer et sécuriser des micro-services avec Spring BootDNG Consulting
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring bootAntoine Rey
 
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...CodelyTV
 
AEM and Sling
AEM and SlingAEM and Sling
AEM and SlingLo Ki
 
jQuery - Chapter 1 - Introduction
 jQuery - Chapter 1 - Introduction jQuery - Chapter 1 - Introduction
jQuery - Chapter 1 - IntroductionWebStackAcademy
 
ASP.NET Web API
ASP.NET Web APIASP.NET Web API
ASP.NET Web APIhabib_786
 
SwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made EasySwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made EasyAnkit Goel
 

What's hot (20)

Site JEE de ECommerce Basé sur Spring IOC MVC Security JPA Hibernate
Site JEE de ECommerce  Basé sur Spring IOC MVC Security JPA HibernateSite JEE de ECommerce  Basé sur Spring IOC MVC Security JPA Hibernate
Site JEE de ECommerce Basé sur Spring IOC MVC Security JPA Hibernate
 
Presentation Spring, Spring MVC
Presentation Spring, Spring MVCPresentation Spring, Spring MVC
Presentation Spring, Spring MVC
 
Asynchronous Programming at Netflix
Asynchronous Programming at NetflixAsynchronous Programming at Netflix
Asynchronous Programming at Netflix
 
Oops in PHP
Oops in PHPOops in PHP
Oops in PHP
 
Clean code and Code Smells
Clean code and Code SmellsClean code and Code Smells
Clean code and Code Smells
 
Clean Code
Clean CodeClean Code
Clean Code
 
CQRS / ES & DDD Demystified
CQRS / ES & DDD DemystifiedCQRS / ES & DDD Demystified
CQRS / ES & DDD Demystified
 
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...
Python Flask Tutorial For Beginners | Flask Web Development Tutorial | Python...
 
Java
JavaJava
Java
 
The Art of Clean code
The Art of Clean codeThe Art of Clean code
The Art of Clean code
 
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...
Multiple Choice Questions on JAVA (object oriented programming) bank 7 -- abs...
 
Design patterns for microservice architecture
Design patterns for microservice architectureDesign patterns for microservice architecture
Design patterns for microservice architecture
 
Concevoir, développer et sécuriser des micro-services avec Spring Boot
Concevoir, développer et sécuriser des micro-services avec Spring BootConcevoir, développer et sécuriser des micro-services avec Spring Boot
Concevoir, développer et sécuriser des micro-services avec Spring Boot
 
Angular Directives
Angular DirectivesAngular Directives
Angular Directives
 
Introduction à spring boot
Introduction à spring bootIntroduction à spring boot
Introduction à spring boot
 
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
Acercándonos a la Programación Funcional a través de la Arquitectura Hexag...
 
AEM and Sling
AEM and SlingAEM and Sling
AEM and Sling
 
jQuery - Chapter 1 - Introduction
 jQuery - Chapter 1 - Introduction jQuery - Chapter 1 - Introduction
jQuery - Chapter 1 - Introduction
 
ASP.NET Web API
ASP.NET Web APIASP.NET Web API
ASP.NET Web API
 
SwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made EasySwtBot: Unit Testing Made Easy
SwtBot: Unit Testing Made Easy
 

Viewers also liked

Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Samuel ROZE
 
24 Productivity Habits of Successful People - by @prdotco
24 Productivity Habits of Successful People - by @prdotco24 Productivity Habits of Successful People - by @prdotco
24 Productivity Habits of Successful People - by @prdotcopr.co
 
Créer une API GraphQL avec Symfony
Créer une API GraphQL avec SymfonyCréer une API GraphQL avec Symfony
Créer une API GraphQL avec SymfonySébastien Rosset
 
Symfony2 & l'architecture Rest
Symfony2 & l'architecture Rest Symfony2 & l'architecture Rest
Symfony2 & l'architecture Rest Ahmed Ghali
 
Microservices architecture examples
Microservices architecture examplesMicroservices architecture examples
Microservices architecture examplesChanny Yun
 
Creating Successful MVPs in Agile Teams - Agile 2014
Creating Successful MVPs in Agile Teams - Agile 2014Creating Successful MVPs in Agile Teams - Agile 2014
Creating Successful MVPs in Agile Teams - Agile 2014Melissa Perri
 
Becoming a Better Programmer
Becoming a Better ProgrammerBecoming a Better Programmer
Becoming a Better ProgrammerPete Goodliffe
 
Modern JavaScript Applications: Design Patterns
Modern JavaScript Applications: Design PatternsModern JavaScript Applications: Design Patterns
Modern JavaScript Applications: Design PatternsVolodymyr Voytyshyn
 
20141206 4 q14_dataconference_i_am_your_db
20141206 4 q14_dataconference_i_am_your_db20141206 4 q14_dataconference_i_am_your_db
20141206 4 q14_dataconference_i_am_your_dbhyeongchae lee
 
I Don't Hate You, I Just Hate Your Code
I Don't Hate You, I Just Hate Your CodeI Don't Hate You, I Just Hate Your Code
I Don't Hate You, I Just Hate Your CodeBrian Richards
 
10 Great Customer Relationship Quotes
10 Great Customer Relationship Quotes10 Great Customer Relationship Quotes
10 Great Customer Relationship QuotesViabl
 
Back to basics: как ставить задачи?
Back to basics: как ставить задачи?Back to basics: как ставить задачи?
Back to basics: как ставить задачи?Nimax
 
Principles of microservices velocity
Principles of microservices   velocityPrinciples of microservices   velocity
Principles of microservices velocitySam Newman
 
DevOps модное слово или следующая ступень эволюции
DevOps модное слово или следующая ступень эволюцииDevOps модное слово или следующая ступень эволюции
DevOps модное слово или следующая ступень эволюцииAndrey Rebrov
 
A Beginners Guide to noSQL
A Beginners Guide to noSQLA Beginners Guide to noSQL
A Beginners Guide to noSQLMike Crabb
 
Hadoop Summit Europe 2014: Apache Storm Architecture
Hadoop Summit Europe 2014: Apache Storm ArchitectureHadoop Summit Europe 2014: Apache Storm Architecture
Hadoop Summit Europe 2014: Apache Storm ArchitectureP. Taylor Goetz
 

Viewers also liked (19)

Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer Symfony et serialization avec JMS serializer
Symfony et serialization avec JMS serializer
 
24 Productivity Habits of Successful People - by @prdotco
24 Productivity Habits of Successful People - by @prdotco24 Productivity Habits of Successful People - by @prdotco
24 Productivity Habits of Successful People - by @prdotco
 
Créer une API GraphQL avec Symfony
Créer une API GraphQL avec SymfonyCréer une API GraphQL avec Symfony
Créer une API GraphQL avec Symfony
 
Symfony2 & l'architecture Rest
Symfony2 & l'architecture Rest Symfony2 & l'architecture Rest
Symfony2 & l'architecture Rest
 
Microservices architecture examples
Microservices architecture examplesMicroservices architecture examples
Microservices architecture examples
 
Creating Successful MVPs in Agile Teams - Agile 2014
Creating Successful MVPs in Agile Teams - Agile 2014Creating Successful MVPs in Agile Teams - Agile 2014
Creating Successful MVPs in Agile Teams - Agile 2014
 
Becoming a Better Programmer
Becoming a Better ProgrammerBecoming a Better Programmer
Becoming a Better Programmer
 
Modern JavaScript Applications: Design Patterns
Modern JavaScript Applications: Design PatternsModern JavaScript Applications: Design Patterns
Modern JavaScript Applications: Design Patterns
 
20141206 4 q14_dataconference_i_am_your_db
20141206 4 q14_dataconference_i_am_your_db20141206 4 q14_dataconference_i_am_your_db
20141206 4 q14_dataconference_i_am_your_db
 
I Don't Hate You, I Just Hate Your Code
I Don't Hate You, I Just Hate Your CodeI Don't Hate You, I Just Hate Your Code
I Don't Hate You, I Just Hate Your Code
 
10 Great Customer Relationship Quotes
10 Great Customer Relationship Quotes10 Great Customer Relationship Quotes
10 Great Customer Relationship Quotes
 
Back to basics: как ставить задачи?
Back to basics: как ставить задачи?Back to basics: как ставить задачи?
Back to basics: как ставить задачи?
 
Principles of microservices velocity
Principles of microservices   velocityPrinciples of microservices   velocity
Principles of microservices velocity
 
Intro to DevOps
Intro to DevOpsIntro to DevOps
Intro to DevOps
 
DevOps модное слово или следующая ступень эволюции
DevOps модное слово или следующая ступень эволюцииDevOps модное слово или следующая ступень эволюции
DevOps модное слово или следующая ступень эволюции
 
A Beginners Guide to noSQL
A Beginners Guide to noSQLA Beginners Guide to noSQL
A Beginners Guide to noSQL
 
Hadoop Summit Europe 2014: Apache Storm Architecture
Hadoop Summit Europe 2014: Apache Storm ArchitectureHadoop Summit Europe 2014: Apache Storm Architecture
Hadoop Summit Europe 2014: Apache Storm Architecture
 
DevOps
DevOpsDevOps
DevOps
 
Introducing DevOps
Introducing DevOpsIntroducing DevOps
Introducing DevOps
 

Similar to Symfony tips and tricks

TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...
TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...
TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...Amazon Web Services
 
Symfony Under the Hood
Symfony Under the HoodSymfony Under the Hood
Symfony Under the HoodeZ Systems
 
20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdevFrank Rousseau
 
Dependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDiego Lewin
 
Maintaining a dependency graph with weaver
Maintaining a dependency graph with weaverMaintaining a dependency graph with weaver
Maintaining a dependency graph with weaverScribd
 
Single Page JavaScript WebApps... A Gradle Story
Single Page JavaScript WebApps... A Gradle StorySingle Page JavaScript WebApps... A Gradle Story
Single Page JavaScript WebApps... A Gradle StoryKon Soulianidis
 
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaMatthias Noback
 
The Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumMatthias Noback
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsMichael Peacock
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetAchieve Internet
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Ryan Weaver
 
The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014Matthias Noback
 
Symfony2 for Midgard Developers
Symfony2 for Midgard DevelopersSymfony2 for Midgard Developers
Symfony2 for Midgard DevelopersHenri Bergius
 
Automation with Ansible and Containers
Automation with Ansible and ContainersAutomation with Ansible and Containers
Automation with Ansible and ContainersRodolfo Carvalho
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - TryoutMatthias Noback
 
Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third PluginJustin Ryan
 
Symfony War Stories
Symfony War StoriesSymfony War Stories
Symfony War StoriesJakub Zalas
 
Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Jesus Manuel Olivas
 

Similar to Symfony tips and tricks (20)

TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...
TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...
TLS303 How to Deploy Python Applications on AWS Elastic Beanstalk - AWS re:In...
 
Symfony Under the Hood
Symfony Under the HoodSymfony Under the Hood
Symfony Under the Hood
 
20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev20130528 solution linux_frousseau_nopain_webdev
20130528 solution linux_frousseau_nopain_webdev
 
Symfony2 revealed
Symfony2 revealedSymfony2 revealed
Symfony2 revealed
 
Dependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony ContainerDependency Injection, Zend Framework and Symfony Container
Dependency Injection, Zend Framework and Symfony Container
 
Maintaining a dependency graph with weaver
Maintaining a dependency graph with weaverMaintaining a dependency graph with weaver
Maintaining a dependency graph with weaver
 
Single Page JavaScript WebApps... A Gradle Story
Single Page JavaScript WebApps... A Gradle StorySingle Page JavaScript WebApps... A Gradle Story
Single Page JavaScript WebApps... A Gradle Story
 
The Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony Barcelona
 
The Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup Belgium
 
Phpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friendsPhpne august-2012-symfony-components-friends
Phpne august-2012-symfony-components-friends
 
Harmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and PuppetHarmonious Development: Via Vagrant and Puppet
Harmonious Development: Via Vagrant and Puppet
 
Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
 
The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014
 
Symfony2 for Midgard Developers
Symfony2 for Midgard DevelopersSymfony2 for Midgard Developers
Symfony2 for Midgard Developers
 
Automation with Ansible and Containers
Automation with Ansible and ContainersAutomation with Ansible and Containers
Automation with Ansible and Containers
 
The Naked Bundle - Tryout
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - Tryout
 
Writing your Third Plugin
Writing your Third PluginWriting your Third Plugin
Writing your Third Plugin
 
Symfony War Stories
Symfony War StoriesSymfony War Stories
Symfony War Stories
 
Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...Creating a modern web application using Symfony API Platform, ReactJS and Red...
Creating a modern web application using Symfony API Platform, ReactJS and Red...
 
Composer
ComposerComposer
Composer
 

More from Javier Eguiluz

deSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony
deSymfony 2017: Symfony 4, Symfony Flex y el futuro de SymfonydeSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony
deSymfony 2017: Symfony 4, Symfony Flex y el futuro de SymfonyJavier Eguiluz
 
Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)Javier Eguiluz
 
Twig, el nuevo motor de plantillas de Drupal 8
Twig, el nuevo motor de plantillas de Drupal 8Twig, el nuevo motor de plantillas de Drupal 8
Twig, el nuevo motor de plantillas de Drupal 8Javier Eguiluz
 
Silex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHPSilex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHPJavier Eguiluz
 
Twig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadasTwig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadasJavier Eguiluz
 
Twig avanzado (sf2Vigo)
Twig avanzado (sf2Vigo)Twig avanzado (sf2Vigo)
Twig avanzado (sf2Vigo)Javier Eguiluz
 
Desymfony 2012 - Concurso de diseño
Desymfony 2012 - Concurso de diseñoDesymfony 2012 - Concurso de diseño
Desymfony 2012 - Concurso de diseñoJavier Eguiluz
 
Desymfony 2011 - Tutorial #5: Backend
Desymfony 2011 - Tutorial #5: BackendDesymfony 2011 - Tutorial #5: Backend
Desymfony 2011 - Tutorial #5: BackendJavier Eguiluz
 
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasosDesymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasosJavier Eguiluz
 
Desymfony 2011 - Introducción a Symfony2
Desymfony 2011 - Introducción a Symfony2Desymfony 2011 - Introducción a Symfony2
Desymfony 2011 - Introducción a Symfony2Javier Eguiluz
 
Symfony2, Jornadas Symfony
Symfony2, Jornadas SymfonySymfony2, Jornadas Symfony
Symfony2, Jornadas SymfonyJavier Eguiluz
 
Curso Symfony - Anexos
Curso Symfony - AnexosCurso Symfony - Anexos
Curso Symfony - AnexosJavier Eguiluz
 
Curso Symfony - Clase 5
Curso Symfony - Clase 5Curso Symfony - Clase 5
Curso Symfony - Clase 5Javier Eguiluz
 
Curso Symfony - Clase 4
Curso Symfony - Clase 4Curso Symfony - Clase 4
Curso Symfony - Clase 4Javier Eguiluz
 
Curso Symfony - Clase 3
Curso Symfony - Clase 3Curso Symfony - Clase 3
Curso Symfony - Clase 3Javier Eguiluz
 

More from Javier Eguiluz (20)

deSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony
deSymfony 2017: Symfony 4, Symfony Flex y el futuro de SymfonydeSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony
deSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony
 
Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)
 
Twig, el nuevo motor de plantillas de Drupal 8
Twig, el nuevo motor de plantillas de Drupal 8Twig, el nuevo motor de plantillas de Drupal 8
Twig, el nuevo motor de plantillas de Drupal 8
 
Silex al límite
Silex al límiteSilex al límite
Silex al límite
 
Twig tips and tricks
Twig tips and tricksTwig tips and tricks
Twig tips and tricks
 
Silex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHPSilex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHP
 
Twig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadasTwig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadas
 
Wallpaper Notifier
Wallpaper NotifierWallpaper Notifier
Wallpaper Notifier
 
Backend (sf2Vigo)
Backend (sf2Vigo)Backend (sf2Vigo)
Backend (sf2Vigo)
 
Twig avanzado (sf2Vigo)
Twig avanzado (sf2Vigo)Twig avanzado (sf2Vigo)
Twig avanzado (sf2Vigo)
 
Desymfony 2012 - Concurso de diseño
Desymfony 2012 - Concurso de diseñoDesymfony 2012 - Concurso de diseño
Desymfony 2012 - Concurso de diseño
 
Desymfony 2011 - Twig
Desymfony 2011 - TwigDesymfony 2011 - Twig
Desymfony 2011 - Twig
 
Desymfony 2011 - Tutorial #5: Backend
Desymfony 2011 - Tutorial #5: BackendDesymfony 2011 - Tutorial #5: Backend
Desymfony 2011 - Tutorial #5: Backend
 
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasosDesymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasos
 
Desymfony 2011 - Introducción a Symfony2
Desymfony 2011 - Introducción a Symfony2Desymfony 2011 - Introducción a Symfony2
Desymfony 2011 - Introducción a Symfony2
 
Symfony2, Jornadas Symfony
Symfony2, Jornadas SymfonySymfony2, Jornadas Symfony
Symfony2, Jornadas Symfony
 
Curso Symfony - Anexos
Curso Symfony - AnexosCurso Symfony - Anexos
Curso Symfony - Anexos
 
Curso Symfony - Clase 5
Curso Symfony - Clase 5Curso Symfony - Clase 5
Curso Symfony - Clase 5
 
Curso Symfony - Clase 4
Curso Symfony - Clase 4Curso Symfony - Clase 4
Curso Symfony - Clase 4
 
Curso Symfony - Clase 3
Curso Symfony - Clase 3Curso Symfony - Clase 3
Curso Symfony - Clase 3
 

Recently uploaded

Effective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConEffective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConNatan Silnitsky
 
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...Neo4j
 
GraphSummit Milan - Visione e roadmap del prodotto Neo4j
GraphSummit Milan - Visione e roadmap del prodotto Neo4jGraphSummit Milan - Visione e roadmap del prodotto Neo4j
GraphSummit Milan - Visione e roadmap del prodotto Neo4jNeo4j
 
Test Automation Design Patterns_ A Comprehensive Guide.pdf
Test Automation Design Patterns_ A Comprehensive Guide.pdfTest Automation Design Patterns_ A Comprehensive Guide.pdf
Test Automation Design Patterns_ A Comprehensive Guide.pdfkalichargn70th171
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckMarc Lester
 
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...drm1699
 
Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Chirag Panchal
 
Software Engineering - Introduction + Process Models + Requirements Engineering
Software Engineering - Introduction + Process Models + Requirements EngineeringSoftware Engineering - Introduction + Process Models + Requirements Engineering
Software Engineering - Introduction + Process Models + Requirements EngineeringPrakhyath Rai
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationElement34
 
Auto Affiliate AI Earns First Commission in 3 Hours..pdf
Auto Affiliate  AI Earns First Commission in 3 Hours..pdfAuto Affiliate  AI Earns First Commission in 3 Hours..pdf
Auto Affiliate AI Earns First Commission in 3 Hours..pdfSelfMade bd
 
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit Milan
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit MilanWorkshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit Milan
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit MilanNeo4j
 
Microsoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdfMicrosoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdfMarkus Moeller
 
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Andrea Goulet
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdftimtebeek1
 
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with Graph
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with GraphGraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with Graph
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with GraphNeo4j
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbankkasambamuno
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Lisi Hocke
 

Recently uploaded (20)

Effective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeConEffective Strategies for Wix's Scaling challenges - GeeCon
Effective Strategies for Wix's Scaling challenges - GeeCon
 
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
CERVED e Neo4j su una nuvola, migrazione ed evoluzione di un grafo mission cr...
 
GraphSummit Milan - Visione e roadmap del prodotto Neo4j
GraphSummit Milan - Visione e roadmap del prodotto Neo4jGraphSummit Milan - Visione e roadmap del prodotto Neo4j
GraphSummit Milan - Visione e roadmap del prodotto Neo4j
 
Test Automation Design Patterns_ A Comprehensive Guide.pdf
Test Automation Design Patterns_ A Comprehensive Guide.pdfTest Automation Design Patterns_ A Comprehensive Guide.pdf
Test Automation Design Patterns_ A Comprehensive Guide.pdf
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
 
Abortion Clinic In Pretoria ](+27832195400*)[ 🏥 Safe Abortion Pills in Pretor...
Abortion Clinic In Pretoria ](+27832195400*)[ 🏥 Safe Abortion Pills in Pretor...Abortion Clinic In Pretoria ](+27832195400*)[ 🏥 Safe Abortion Pills in Pretor...
Abortion Clinic In Pretoria ](+27832195400*)[ 🏥 Safe Abortion Pills in Pretor...
 
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
Abortion Pills For Sale WhatsApp[[+27737758557]] In Birch Acres, Abortion Pil...
 
Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024
 
Software Engineering - Introduction + Process Models + Requirements Engineering
Software Engineering - Introduction + Process Models + Requirements EngineeringSoftware Engineering - Introduction + Process Models + Requirements Engineering
Software Engineering - Introduction + Process Models + Requirements Engineering
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test Automation
 
Auto Affiliate AI Earns First Commission in 3 Hours..pdf
Auto Affiliate  AI Earns First Commission in 3 Hours..pdfAuto Affiliate  AI Earns First Commission in 3 Hours..pdf
Auto Affiliate AI Earns First Commission in 3 Hours..pdf
 
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit Milan
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit MilanWorkshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit Milan
Workshop: Enabling GenAI Breakthroughs with Knowledge Graphs - GraphSummit Milan
 
Microsoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdfMicrosoft365_Dev_Security_2024_05_16.pdf
Microsoft365_Dev_Security_2024_05_16.pdf
 
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
 
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
Abortion Pill Prices Germiston ](+27832195400*)[ 🏥 Women's Abortion Clinic in...
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with Graph
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with GraphGraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with Graph
GraphSummit Milan & Stockholm - Neo4j: The Art of the Possible with Graph
 
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank
^Clinic ^%[+27788225528*Abortion Pills For Sale In witbank
 
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
Team Transformation Tactics for Holistic Testing and Quality (NewCrafts Paris...
 

Symfony tips and tricks

  • 1. SYMFONY TIPS & TRICKS SymfonyCon Madrid - 27 November 2014 Javier Eguiluz
  • 2. About me evangelist and trainer at
  • 4. Why Symfony Tips and Tricks? I don’t work as a developer But I read and review code every single day I also read every single blog post published about Symfony These are the tips and tricks discovered during this year.
  • 5. Thanks to my awesome co-workers for sharing their knowledge! Grégoire Nicolas Hugo Tugdual Sarah Julien Loïc Jérémy Romain Joseph FX
  • 6. Agenda Assets Performance Logging Legacy Testing Config Parallel Doctrine Value Objects
  • 9. Typical asset linking {% javascripts '@AppBundle/Resources/public/js/jquery.js' '@AppBundle/Resources/public/js/jquery.ui.js' '@AppBundle/Resources/public/js/bootstrap.js' '@AppBundle/Resources/public/js/*' %} <script src="{{ asset_url }}"></script> {% endjavascripts %}
  • 10. Defining named assets # app/config/config.yml assetic: assets: # ... bootstrap_js: inputs: - '@AppBundle/Resources/public/js/jquery.js' - '@AppBundle/Resources/public/js/jquery.ui.js' - '@AppBundle/Resources/public/js/bootstrap.js'
  • 11. Using named assets {% javascripts '@bootstrap_js' '@AppBundle/Resources/public/js/*' %} <script src="{{ asset_url }}"></script> {% endjavascripts %}
  • 13. asset( ) adds a leading slash <img src="{{ asset('images/logo.png') }}" />
  • 14. asset( ) adds a leading slash <img src="{{ asset('images/logo.png') }}" /> <img src="/images/logo.png" />
  • 15. Easiest way to define a base URL # app/config/config.yml framework: templating: assets_base_urls: http: ['http://static.example.com/images'] ssl: ['https://static.example.com/images']
  • 16. Easiest way to define a base URL # app/config/config.yml framework: templating: assets_base_urls: http: ['http://static.example.com/images'] ssl: ['https://static.example.com/images'] if you configure several base URLs, Symfony will select one each time to balance asset load
  • 17. Protocol-relative base URL # app/config/config.yml framework: templating: assets_base_urls: '//static.example.com/images'
  • 18. AWshast eif wt ep waanct ktoa mgodeifsy the base URL just for a few selected assets?
  • 19. What if we want to modify the base URL just for a few selected assets? Asset packages
  • 20. Asset packages Group assets logically. Define config for each package.
  • 21. Defining asset packages # app/config/config.yml framework: templating: packages: avatars: base_urls: '//static.example.com/avatars/'
  • 22. Asset packages in practice <img src="{{ asset('...', 'avatars') }}" />
  • 23. Asset packages in practice <img src="{{ asset('...', 'avatars') }}" /> <img src="//static.example.com/ avatars/logo.png" />
  • 24. Packages can define any asset option # app/config/config.yml framework: templating: packages: avatars: version: '20141127' base_urls: '//static.example.com/avatars/'
  • 26. Using the Symfony console $ php app/console --env=prod assetic:dump
  • 27. Using the Symfony console $ php app/console --env=prod assetic:dump WRONG RIGHT
  • 28. Using the Symfony console $ ./app/console --env=prod assetic:dump WRONG RIGHT
  • 29. Using the Symfony console $ sf --env=prod assetic:dump alias sf = php app/console WRONG RIGHT
  • 30. Using good Symfony console shortcuts $ dev ... $ prod ... alias dev = php app/console --env=dev alias prod = php app/console --env=prod
  • 31. PROS $ prod a:d $ dev r:m / $ dev cont:d NEWCOMERS $ php app/console --env=prod assetic:dump $ php app/console router:match / $ php app/console container:debug
  • 35. Optimized auto-loading $ composer dump-autoload --optimize
  • 36. Don't load test classes in production { "autoload": { "psr-4": { "MyLibrary": "src/" } }, "autoload-dev": { "psr-4": { "MyLibraryTests": "tests/" } } }
  • 37. Add classes to compile
  • 38. Three important Symfony files • app/bootstrap.php.cache Internal Symfony classes. • app/cache/<env>/ appDevDebugProjectContainer.php Compiled container (services and parameters). • app/cache/<env>/classes.php Symfony bundles classes.
  • 39. Adding your own classes for compiling namespace AppBundleDependencyInjection; ! use SymfonyComponentConfigFileLocator; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentHttpKernelDependencyInjectionExtension; ! class AppExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { // ... ! $this->addClassesToCompile([ 'AppBundleFullNamespaceClass' ]); } }
  • 40. Adding your own classes for compiling namespace AppBundleDependencyInjection; ! use SymfonyComponentConfigFileLocator; use SymfonyComponentDependencyInjectionContainerBuilder; use SymfonyComponentHttpKernelDependencyInjectionExtension; ! class AppExtension extends Extension { public function load(array $configs, ContainerBuilder $container) { // ... ! $this->addClassesToCompile([ 'AppBundleFullNamespaceClass' ]); } } WARNING It doesn't work if the classes define annotations
  • 42. How can you enable the profiler on production for some specific users?
  • 43. The easy way: restrict by path # app/config/config.yml framework: # ... profiler: matcher: path: ^/admin/
  • 44. The right way: restrict by user (1/2) # app/config/config.yml framework: # ... profiler: matcher: service: app.profiler_matcher ! services: app.profiler_matcher: class: AppBundleProfilerMatcher arguments: ["@security.context"]
  • 45. The right way: restrict by user (2/2) namespace AppBundleProfiler; ! use SymfonyComponentSecurityCoreSecurityContext; use SymfonyComponentHttpFoundationRequest; use SymfonyComponentHttpFoundationRequestMatcherInterface; ! class Matcher implements RequestMatcherInterface { protected $securityContext; ! public function __construct(SecurityContext $securityContext) { $this->securityContext = $securityContext; } ! public function matches(Request $request) { return $this->securityContext->isGranted('ROLE_ADMIN'); } }
  • 46. Better logs with Monolog
  • 48. Symfony logs a lot of information [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] [2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerDebugHandlersListener::configure". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleDataCollectorRouterDataCollector::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerControllerListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] [2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] [2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
  • 49. Is this useful for your application? [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] [2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerDebugHandlersListener::configure". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleDataCollectorRouterDataCollector::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerControllerListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] [2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] [2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
  • 50. Is this useful for your application? [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") [] [] 2014-[[2014-2014-11-24 12:24:22] security.11-INFO: Populated 24 SecurityContext 12:24:with an anonymous Token [] [] 11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener 22] "SymfonyComponentevent.HttpKernelEventListenerDEBUG: DebugHandlersListener::Notified configure". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleFrameworkBundleEventListenerSessionListener::onKernelRequest". [] [] [event 2014-11-24 12:24:22] event."DEBUG: kernel.Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerFragmentListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentto HttpKernellistener EventListenerRouterListener::onKernelRequest". "Symfony [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelRequest". [] [] [2014-Bundle11-24 12:24:22] event.DEBUG: FrameworkBundleNotified event "kernel.request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener "SymfonyBundleAsseticBundleEventListenerRequestListener::onKernelRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyBundleFrameworkBundleEventListener DataCollectorRouterDataCollector::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SymfonyComponentHttpKernelDataCollectorRequestDataCollector::onKernelController". [] [] [2014-SessionListener::11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener onKernelRequest". "SensioBundleFrameworkExtraBundleEventListenerControllerListener::[] onKernelController". [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerParamConverterListener::onKernelController". [] [] [] [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerSecurityListener::onKernelController". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.controller" to listener "SensioBundleFrameworkExtraBundleEventListenerTemplateListener::onKernelController". [] [] [2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] [] [2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpFirewallContextListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentSecurityHttpRememberMeResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SensioBundleFrameworkExtraBundleEventListenerHttpCacheListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyBundleWebProfilerBundleEventListenerWebDebugToolbarListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.response" to listener "SymfonyComponentHttpKernelEventListenerStreamedResponseListener::onKernelResponse". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerLocaleListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentHttpKernelEventListenerRouterListener::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.finish_request" to listener "SymfonyComponentSecurityHttpFirewall::onKernelFinishRequest". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyBundleSwiftmailerBundleEventListenerEmailSenderListener::onTerminate". [] [] [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.terminate" to listener "SymfonyComponentHttpKernelEventListenerProfilerListener::onKernelTerminate". [] []
  • 51. Is this useful for your application? [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: ... [2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ...
  • 52. Is this useful for your application? [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: ... [2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... [2014-11-24 12:24:22] event.DEBUG: Notified event "kernel.request" to listener ... log channel
  • 53. Hide event logs (if you don't need them) # app/config/dev.yml monolog: handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug channels: "!event"
  • 54. De-cluttered log files [2014-11-24 12:24:22] request.INFO: Matched route "homepage" (parameters: "_controller": "AppBundleControllerDefaultController::indexAction", "_route": "homepage") ! [2014-11-24 12:24:22] security.INFO: Populated SecurityContext with an anonymous Token ! [2014-11-24 12:24:22] doctrine.DEBUG: SELECT p0_.id AS id0, p0_.title AS title1, p0_.slug AS slug2, p0_.summary AS summary3, p0_.content AS content4, p0_.authorEmail AS authorEmail5, p0_.publishedAt AS publishedAt6 FROM Post p0_ WHERE p0_.publishedAt <= ? ORDER BY p0_.publishedAt DESC LIMIT 10 ["2014-11-24 12:24:22"] ! [2014-11-24 12:24:22] security.DEBUG: Write SecurityContext in the session
  • 56. Email logs related to 5xx errors # app/config/config_prod.yml monolog: handlers: mail: type: fingers_crossed action_level: critical handler: buffered buffered: type: buffer handler: swift swift: type: swift_mailer from_email: error@example.com to_email: error@example.com subject: An Error Occurred! level: debug
  • 57. Email logs related to 5xx errors # app/config/config_prod.yml monolog: handlers: mail: type: fingers_crossed action_level: critical handler: buffered buffered: type: buffer handler: swift swift: type: swift_mailer from_email: error@example.com to_email: error@example.com subject: An Error Occurred! level: debug
  • 58. Email logs related to 4xx errors # app/config/config_prod.yml monolog: handlers: mail: type: fingers_crossed action_level: error handler: buffered buffered: # ... swift: # ...
  • 59. Don't email 404 errors # app/config/config_prod.yml monolog: handlers: mail: type: fingers_crossed action_level: error excluded_404: - ^/ handler: buffered buffered: # ... swift: # ...
  • 61. Use different log files for each channel # app/config/config_prod.yml monolog: handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug channels: ["!event"] security: type: stream path: "%kernel.logs_dir%/security-%kernel.environment%.log" level: debug channels: "security"
  • 62. Format your log messages
  • 63. Don't do this $logger->info(sprintf( "Order number %s processed for %s user", $number, $user ));
  • 64. Do this instead $logger->info( "Order number {num} processed for {name} user", ['num' => $number, 'name' => $user] );
  • 65. Do this instead $logger->info( "Order number {num} processed for {name} user", ['num' => $number, 'name' => $user] ); WARNING It doesn't work unless the PSR log processor is enabled.
  • 66. Enable the PsrLogMessageProcessor # app/config/config_prod.yml services: monolog_processor: class: MonologProcessorPsrLogMessageProcessor tags: - { name: monolog.processor }
  • 67. Monolog includes several processors # app/config/config_prod.yml services: monolog_processor: class: MonologProcessorIntrospectionProcessor tags: - { name: monolog.processor }
  • 68. Create your own processor $logger->info( "Order processed successfully.", $order );
  • 69. Create your own processor $logger->info( "Order processed successfully.", $order ); [2014-11-24 15:15:58] app.INFO: Order processed successfully. { "order": { "number": "023924382982", "user":"John Smith", "total":"34.99","status":"PAID" }}
  • 70. Custom monolog processor namespace AppBundleLoggerOrderProcessor; ! use AppBundleEntityOrder; ! class OrderProcessor { public function __invoke(array $record) { if (isset($record['context']['order']) && $record['context']['order'] instanceof Order) { $order = $record['context']['order']; $record['context']['order'] = [ 'number' => $order->getNumber(), 'user' => $order->get..., 'total' => $order->get..., 'status' => $order->get..., ]; } ! return $record; } # app/config/config_prod.yml services: monolog_processor: class: AppBundleLoggerOrderProcessor tags: - { name: monolog.processor }
  • 72. Medium-sized and Large Applications Medium-sized and Small Applications Using your own mailer is OK.
  • 73. Not all emails should be treated equally Sign Ups Lost password Notifications Newsletters Spam
  • 74. Instant and delayed mailers # app/config/config.yml swiftmailer: default_mailer: delayed mailers: instant: # ... spool: ~ delayed: # ... spool: type: file path: "%kernel.cache_dir%/swiftmailer/spool"
  • 75. Using prioritized mailers // high-priority emails $container->get('swiftmailer.mailer.instant')->... ! // regular emails $container->get('swiftmailer.mailer')->... $container->get('swiftmailer.mailer.delayed')->...
  • 78. Controller that captures legacy URLs class LegacyController extends Controller { /** * @Route("/{filename}.php", name="_legacy") */ public function legacyAction($filename) { $legacyPath = $this->container->getParameter('legacy.path'); ! ob_start(); chdir($legacyAppPath); require_once $legacyAppPath.$filename.'.php'; $content = ob_get_clean(); ! return new Response($content); } } Source: http://cvuorinen.net/slides/symfony-legacy-integration/
  • 80. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App
  • 81. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request
  • 82. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request
  • 83. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException
  • 84. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException kernel.exception
  • 85. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException kernel.exception
  • 86. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException kernel.exception
  • 87. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException kernel.exception Response
  • 88. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request NotFound HttpException kernel.exception Response Response
  • 89. App Kernel Router Listener LegacyKernel Listener LegacyKernel Legacy App Request kernel.request Response NotFound HttpException kernel.exception Response Response
  • 90. LegacyKernel to process requests class LegacyKernel implements HttpKernelInterface { public function handle(Request $request, ...) { ob_start(); ! $legacyDir = dirname($this->legacyAppPath); chdir($legacyDir); require_once $this->legacyAppPath; ! $content = ob_get_clean(); ! return new Response($content); } } Source: http://cvuorinen.net/slides/symfony-legacy-integration/
  • 91. Listener to treat 404 as legacy requests class LegacyKernelListener implements EventSubscriberInterface { public function onKernelException($event) { $exception = $event->getException(); ! if ($exception instanceof NotFoundHttpException) { $request = $event->getRequest(); $response = $this->legacyKernel->handle($request); ! $response->headers->set('X-Status-Code', 200); ! $event->setResponse($response); } } } Source: http://cvuorinen.net/slides/symfony-legacy-integration/
  • 93. Click-to-file in Symfony Exceptions Textmate
  • 94. PHPStorm is now supported natively # app/config/config.yml framework: ide: "phpstorm://open?file=%%f&line=%%l"
  • 97. Prophecy is a highly opinionated yet very powerful and flexible PHP object mocking framework.
  • 98. PHPUnit has built-in Prophecy mocks support since 4.5 version.
  • 99. A complete Prophecy mock class SubjectTest extends PHPUnit_Framework_TestCase { public function testObserversAreUpdated() { $subject = new Subject('My subject'); ! $observer = $this->prophesize('Observer'); $observer->update('something')->shouldBeCalled(); ! $subject->attach($observer->reveal()); ! $subject->doSomething(); } }
  • 100. Prophecy vs traditional mocks (1/2) ! ! $observer = $this->prophesize('Observer'); ! ! ! $observer = $this->getMockBuilder('Observer') ->setMethods(array('update')) ->getMock(); Prophecy PHPUnit
  • 101. Prophecy vs traditional mocks (2/2) ! Prophecy ! $observer->update('something')->shouldBeCalled(); ! ! ! $observer->expects($this->once()) ->method('update') ->with($this->equalTo('something')); PHPUnit
  • 103. Smoke testing is preliminary testing to reveal simple failures severe enough to reject a prospective software release.
  • 104. Unnecessarily slow tests namespace AppBundleTestsController; ! use SymfonyBundleFrameworkBundleTestWebTestCase; ! class DefaultControllerTest extends WebTestCase { /** @dataProvider provideUrls */ public function testPageIsSuccessful($url) { $client = self::createClient(); $client->request('GET', $url); $this->assertTrue($client->getResponse()->isSuccessful()); } ! public function provideUrls() { // ... } }
  • 105. Functional tests without WebTestCase class SmokeTest extends PHPUnit_Framework_TestCase { private $app; ! protected function setUp() { $this->app = new AppKernel('test', false); $this->app->boot(); } ! /** @dataProvider provideUrls */ public function testUrl($url) { $request = new Request::create($url, 'GET'); $response = $this->app->handle($request); ! $this->assertTrue($response->isSuccessful()); } } Source: http://gnugat.github.io/2014/11/15/sf2-quick-functional-tests.html
  • 107. Impersonating users # app/config/security.yml security: firewalls: main: # ... switch_user: true http://example.com/path?_switch_user=fabien
  • 108. WARNING Impersonating is very dangerous and it can lead to severe errors.
  • 109. Visual hints when impersonating users <body class=" {% if app.user and is_granted('ROLE_PREVIOUS_ADMIN') %} impersonating {% endif %} "> ! .impersonating { background: rgba(204, 0, 0, 0.15); }
  • 110. Be careful when impersonating users
  • 113. Implicit YAML casting key: value1: 5 value2: 2.7 value3: 2014-11-27
  • 114. Implicit YAML casting key: value1: 5 value2: 2.7 value3: 2014-11-27 array(1) { ["key"]=> array(3) { ["value1"] => int(5) ["value2"] => float(2.7) ["value3"] => int(1417042800) } }
  • 115. Explicit YAML casting key: value1: !str 5 value2: ! 2.7 value3: !str 2014-11-27
  • 116. Explicit YAML casting key: value1: !str 5 value2: ! 2.7 value3: !str 2014-11-27 array(1) { ["key"]=> array(3) { ["value1"] => string(1) "5" ["value2"] => int(2) ["value3"] => string(10) "2014-11-27" } }
  • 117. Explicit YAML casting key: value1: !str 5 value2: ! 2.7 value3: !str 2014-11-27 array(1) { ["key"]=> array(3) { ["value1"] => string(1) "5" ["value2"] => int(2) ["value3"] => string(10) "2014-11-27" } } ! = int !str = string !!php/object: = serialized object
  • 119. Expression Language in action /** * @Route("/post/{id}") * @Cache(smaxage="15", lastModified="post.getUpdatedAt()") */ public function showAction(Post $post) { ... } ! ! /** * @AssertExpression("this.getFoo() == 'fo'", message="Not good!") */ class Obj { ... }
  • 120. Custom md5( ) function use SymfonyComponentExpressionLanguageExpressionFunction; use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; ! class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface { return array( new ExpressionFunction( 'md5', function ($str) { return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); }, function ($arguments, $str) { return is_string($str) ? md5($str) : $str; } ); ); }
  • 121. Custom md5( ) function use SymfonyComponentExpressionLanguageExpressionFunction; use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; ! class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface { return array( new ExpressionFunction( 'md5', function ($str) { return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); }, function ($arguments, $str) { return is_string($str) ? md5($str) : $str; } ); ); } compiled expression
  • 122. Custom md5( ) function use SymfonyComponentExpressionLanguageExpressionFunction; use SymfonyComponentExpressionLanguageExpressionFunctionProviderInterface; ! class AppExpressionLanguageProvider implements ExpressionFunctionProviderInterface { return array( new ExpressionFunction( 'md5', function ($str) { return sprintf('(is_string(%1$s) ? md5(%1$s) : %1$s)', $str); }, function ($arguments, $str) { return is_string($str) ? md5($str) : $str; } ); ); } compiled expression executed expression
  • 123. Using custom functions in expressions namespace AppBundleController; ! use SymfonyBundleFrameworkBundleControllerController; use SensioBundleFrameworkExtraBundleConfigurationSecurity; ! class BlogController extends Controller { /** * @Security("md5(user.email) == post.author_id") * ... */ public function editAction(...) { // ... } } # app/config/services.yml services: app.expressions: class: AppBundleExpressionLanguageProvider tags: - { name: security.expression_language_provider }
  • 124. Expression Language service tags services: app.expressions: class: ... tags: - { name: security.expression_language_provider } ! ! services: app.expressions: class: ... tags: - { name: routing.expression_language_provider }
  • 126. Have you ever used the {% do %} Twig tag?
  • 127. Using the {% do %} Twig tag {% do form.field.setRendered %} ! {{ form_start(form) }} {{ form_errors(form) }} ! {{ form_row(...) }} {{ form_end(form) }} Source: http://stackoverflow.com/q/10570002
  • 128. Using the {% do %} Twig tag {% do form.field.setRendered %} ! {{ form_start(form) }} {{ form_errors(form) }} ! {{ form_row(...) }} {{ form_end(form) }} Makes form_rest( ) and form_end( ) not display the given field. Source: http://stackoverflow.com/q/10570002
  • 130. How many cores/threads has your CPU?
  • 131. How many of them are used while developing the application?
  • 133. « TDD test suites should run in 10 seconds or less » Source: http://blog.ploeh.dk/2012/05/24/TDDtestsuitesshouldrunin10secondsorless/
  • 134. Parallel test execution with Fastest # Install $ composer require --dev 'liuggio/fastest' 'dev-master' ! # Execute tests in 4 parallel threads $ find src/ -name "*Test.php" | ./vendor/bin/fastest "phpunit -c app {};"
  • 135. Database isolation for parallel tests getenv('ENV_TEST_CHANNEL'); getenv('ENV_TEST_CHANNEL_READABLE'); getenv('ENV_TEST_CHANNELS_NUMBER'); getenv('ENV_TEST_ARGUMENT'); getenv('ENV_TEST_INC_NUMBER'); getenv('ENV_TEST_IS_FIRST_ON_CHANNEL');
  • 136. Database isolation for parallel tests getenv('ENV_TEST_CHANNEL'); getenv('ENV_TEST_CHANNEL_READABLE'); getenv('ENV_TEST_CHANNELS_NUMBER'); getenv('ENV_TEST_ARGUMENT'); getenv('ENV_TEST_INC_NUMBER'); getenv('ENV_TEST_IS_FIRST_ON_CHANNEL'); 30 minutes 7 minutes
  • 137. Alternatives to Fastest • Paratest (Official PHPUnit plugin) github.com/brianium/paratest • Parallel PHPUnit github.com/socialpoint-labs/parallel-phpunit • Docker + PHPUnit marmelab.com/blog/2013/11/04/how-to-use-docker-to-run-phpunit-tests-in-parallel.html
  • 139. Dumping assets … slowly $ php app/console --env=prod assetic:dump
  • 140. Parallel asset dumping $ php app/console --env=prod assetic:dump --forks=4
  • 141. Parallel asset dumping $ php app/console --env=prod $ prod a:d --forks=4 assetic:dump --forks=4
  • 142. Parallel asset dumping $ php app/console --env=prod $ prod a:d --forks=4 assetic:dump --forks=4 # it requires to install the Spork library $ composer require kriswallsmith/spork
  • 144. Parallel HTTP requests with Guzzle use GuzzleHttpPool; use GuzzleHttpClient; ! $client = new Client(); ! $requests = [ $client->createRequest('GET', 'http://example.org'), $client->createRequest('DELETE', 'http://example.org/delete'), $client->createRequest('PUT', 'http://example.org/put', ['body' => 'test']) ]; ! (new Pool($client, $requests))->wait();
  • 145. Rarely used Doctrine features
  • 147. This does not work SELECT t FROM Talks t ORDER BY t.comments + t.likes_count
  • 148. This does work SELECT t, (t.comments + t.likes_count) AS HIDDEN score FROM Talks t ORDER BY score !
  • 150. Selecting specific properties public function findPost() { return $this ->createQueryBuilder('p') ->select('p.id, p.title') ->where('p.id = :id')->setParameter('id', 1) ->getQuery() ->getResult() ; }
  • 151. The executed SQL query doctrine.DEBUG: ! SELECT p0_.id AS id_0, p0_.title AS title_1 FROM Post p0_ WHERE p0_.id = ?
  • 152. The resulting "object" array(1) { [0]=> array(2) { ["id"] => int(1) ["title"] => string(7) "Post #1" } }
  • 153. Using Doctrine partials public function findPost() { return $this ->createQueryBuilder('p') ->select('partial p.{id, title}') ->where('p.id = :id')->setParameter('id', 1) ->getQuery() ->getResult() ; }
  • 154. The executed SQL query doctrine.DEBUG: ! SELECT p0_.id AS id_0, p0_.title AS title_1 FROM Post p0_ WHERE p0_.id = ?
  • 155. The resulting object array(1) { [0]=> object(stdClass)#676 (9) { ["__CLASS__"] => string(21) "AppBundleEntityPost" ["id"] => int(1) ["title"] => string(7) "Post #1" ["slug"] => NULL ["summary"] => NULL ["content"] => NULL ["authorEmail"] => NULL ["publishedAt"] => NULL ["comments"] => string(8) "Array(5)" } }
  • 156. The resulting object array(1) { [0]=> object(stdClass)#676 (9) { ["__CLASS__"] => string(21) "AppBundleEntityPost" ["id"] => int(1) ["title"] => string(7) "Post #1" ["slug"] => NULL ["summary"] => NULL ["content"] => NULL ["authorEmail"] => NULL ["publishedAt"] => NULL ["comments"] => string(8) "Array(5)" } }
  • 157. WARNING Doctrine Partials make your code very fragile. Use them with caution.
  • 158. Legitimate use cases for Partials • Specific (and limited) parts of the application where performance is the top priority. • Never when developing the application (wrong premature optimization).
  • 160. Configuring schema_filter option # app/config/config.yml doctrine: dbal: default_connection: default # ... schema_filter: ~^(?!legacy_)~
  • 161. Filtering tables managed by other apps # app/config/config.yml doctrine: dbal: default_connection: default # ... schema_filter: ~^(?!(django|auth)_)~ Source: http://javaguirre.net/2013/10/16/symfony-migrations-and-django-admin/
  • 162. Using schema_filter to migrate gradually # app/config/config.yml doctrine: dbal: default_connection: default connections: default: dbname: "%database_name%" # ... legacy: # ... schema_filter: ~^(?!(prefix)_)~ Source: http://espend.de/artikel/doctrine-und-symfony2-orm-entities-aus-datenbank-erzeugen.html
  • 164. How do you deal with global SQL clauses?
  • 165. Examples of global SQL clauses CMS applications Display only published contents. WHERE status = :published
  • 166. Examples of global SQL clauses CMS applications Display only published Never display (soft) contents. deleted items. WHERE status = :published WHERE status = :not_deleted
  • 167. Examples of global SQL clauses CMS applications Display only published Never display (soft) contents. deleted items. WHERE status = :published WHERE status = :not_deleted Commerce applications Display only invoices/ orders for the given user. WHERE user_id = :user_id
  • 168. Doctrine filters allow to add SQL to the conditional clauses of queries, regardless the place where the SQL is generated.
  • 169. Creating a global Locale filter namespace AppBundleFilterLocaleFilter; ! use DoctrineORMMappingClassMetaData; use DoctrineORMQueryFilterSQLFilter; ! class LocaleFilter extends SQLFilter { public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { return sprintf( '%s.%s = %s', $targetTableAlias, 'locale', $this->getParameter('locale') ); } } WHERE locale = 'es'
  • 170. Enabling the filter globally # app/config/config.yml doctrine: orm: filters: locale_filter: class: AppBundleFilterLocaleFilter enabled: true
  • 171. Using the filter and setting its values $filter = $this->em->getFilters() ->enable('locale_filter'); ! $filter->setParameter('locale', 'es');
  • 172. What's the point of using global filters if you have to set up values manually for each query?
  • 173. Setting filter values automatically (1/2) services: app.locale_filter: class: AppBundleFilterLocaleFilter arguments: [@session] tags: - { name: kernel.event_listener, event: kernel.request, method: onKernelRequest } Source: http://stackoverflow.com/a/15792119
  • 174. Setting filter values automatically (2/2) public function __construct(SessionInterface $session) { $this->session = $session; } ! // ... ! public function onKernelRequest() { $this->em->getConfiguration() ->addFilter('locale_filter', 'AppBundleFilterLocaleFilter'); ! $filter = $this->em->getFilters()->enable('locale_filter'); $filter->setParameter('locale', $this->session->get('user_locale')); } Source: http://stackoverflow.com/a/15792119
  • 175. Constraining the scope of the filters class LocaleFilter extends SQLFilter { public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { return ''; } ! // ... } }
  • 176. Constraining the scope of the filters class LocaleFilter extends SQLFilter { public function addFilterConstraint(ClassMetadata $targetEntity, $targetTableAlias) { if (!$targetEntity->reflClass->implementsInterface('LocaleAware')) { return ''; } ! // ... } } /** @ORMEntity */ class Content implements LocalAware { // ... }
  • 177. Combining filters with annotations namespace AppBundleEntity; ! use AppBundleAnnotationUserAware; /** * @UserAware(userFieldName="user_id") */ class Order { ... }
  • 178. Combining filters with annotations namespace AppBundleEntity; WHERE user_id = :id ! use AppBundleAnnotationUserAware; /** * @UserAware(userFieldName="user_id") */ class Order { ... }
  • 179. Combining filters with annotations namespace AppBundleEntity; WHERE user_id = :id ! use AppBundleAnnotationUserAware; /** * @UserAware(userFieldName="user_id") */ class Order { ... } Learn how to do this at: http://blog.michaelperrin.fr/2014/07/25/doctrine-filters/
  • 181. Value Objects in theory
  • 182. A value object is a small object that represents a simple entity whose equality is not based on identity.
  • 183. Two value objects are equal when they have the same value, not necessarily being the same object.
  • 184. Identity is irrelevant for value objects object(AppBundleEntityMoney)#25 (3) { ["id"] => int(25) ["amount"] => int(10) ["currency"] => string(3) "EUR" } ! object(AppBundleEntityMoney)#267 (3) { ["id"] => int(267) ["amount"] => int(10) ["currency"] => string(3) "EUR" } 10 EUR 10 EUR
  • 185. Value Objects in Doctrine
  • 186. A typical Doctrine entity namespace AppBundleEntity; ! /** @Entity */ class Customer { /** @Id @GeneratedValue @Column(type="integer") */ protected $id; ! /** @Column(type="string") */ protected $name; ! /** @Column(type="string", length=120) **/ protected $email_address; ! protected $address_line_1; protected $address_line_2; protected $city; protected $state; protected $postalCode; protected $country; }
  • 187. A typical Doctrine entity namespace AppBundleEntity; ! /** @Entity */ class Customer { /** @Id @GeneratedValue @Column(type="integer") */ protected $id; ! /** @Column(type="string") */ protected $name; ! /** @Column(type="string", length=120) **/ protected $email_address; ! protected $address_line_1; protected $address_line_2; protected $city; protected $state; protected $postalCode; protected $country; } Address Value Object
  • 188. Defining a Value Object in Doctrine namespace AppBundleValueObject; ! use DoctrineORMMapping as ORM; ! /** @ORMEmbeddable */ class Address { /** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ protected $line_1; protected $line_2; ! /** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ protected $city; protected $state; ! /** @ORMColumn(type = "string") */ /** @ORMColumn(type = "string") */ protected $postalCode; protected $country; }
  • 189. Using a Value Object in Doctrine namespace AppBundleEntity; ! use DoctrineORMMapping as ORM; ! /** @ORMEntity */ class Customer { /** @ORMId @ORMGeneratedValue @ORMColumn(type="integer") */ protected $id; ! /** @ORMColumn(type="string") */ protected $name = ''; ! /** @ORMColumn(type="string", length=120) **/ protected $email_address = ''; ! /** @ORMEmbedded(class="AppBundleValueObjectAddress") */ protected $address; }
  • 190. Value Objects at SQL level CREATE TABLE Post ( id INTEGER NOT NULL, title VARCHAR(255) NOT NULL, slug VARCHAR(255) NOT NULL, summary VARCHAR(255) NOT NULL, content CLOB NOT NULL, authorEmail VARCHAR(255) NOT NULL, publishedAt DATETIME NOT NULL, address_line_1 VARCHAR(255) NOT NULL, address_line_2 VARCHAR(255) NOT NULL, address_city VARCHAR(255) NOT NULL, address_state VARCHAR(255) NOT NULL, address_postalCode VARCHAR(255) NOT NULL, address_country VARCHAR(255) NOT NULL, PRIMARY KEY(id))
  • 191. Value Objects at query level SELECT c FROM Customer c WHERE c.address.city = :city
  • 192. Embeddables are classes which are not entities themself, but are embedded in entities and can also be queried in DQL.
  • 193. Agenda Assets Performance Logging Legacy Testing Config Parallel Doctrine Value Objects
  • 196. Contact info ! ! javier.eguiluz@sensiolabs.com
  • 197. SymfonyCon Madrid - 27 November 2014