New Symfony Tips & Tricks (SymfonyCon Paris 2015)

Javier Eguiluz
Javier EguiluzFormador especializado en nuevas tecnologías at SensioLabs
SYMFONY
TIPS & TRICKS
SymfonyCon
Paris - December 4, 2015
Javier Eguiluz
NEW
License of this presentation
creativecommons.org/licenses/by-nc-sa/3.0
About me
Javier Eguiluz evangelist and trainer at
About
this talk
What we'll talk about
New
features
Tips &
tricks
Rarely used
options
Doctrine
Twig
Monolog
Composer
PHPUnit
Symfony
Doctrine
Twig Monolog
Composer
PHPUnit
Symfony
Before we
start
Thanks to my awesome co-workers
for sharing their knowledge!
Grégoire Nicolas Hugo
SarahTugdual
JérémyJérémy Romain
Julien
FX
Charles
And thanks to the amazing
Symfony Community too
Christophe Benjamin Tobias
BernhardChristian
JordiKevin Jakub
Ryan
Lukas
Wouter
Agenda Doctrine Composer Twig
Config Monolog Security
Tests Services Console
mini-tip
Accessing request parameters
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
/** @Route("/blog") */
class BlogController extends Controller
{
/** @Route("/", name="blog_index") */
public function indexAction()
{
$value1 = $request->query->get('parameter1');
$value2 = $request->request->get('parameter2');
// ...
}
}
Accessing request parameters
use SensioBundleFrameworkExtraBundleConfigurationRoute;
use SymfonyBundleFrameworkBundleControllerController;
use SymfonyComponentHttpFoundationRequest;
/** @Route("/blog") */
class BlogController extends Controller
{
/** @Route("/", name="blog_index") */
public function indexAction()
{
$value1 = $request->query->get('parameter1');
$value2 = $request->request->get('parameter2');
// ...
}
}
everybody uses the get() method,
but there are other useful methods
ParameterBag defines some useful methods
// very useful methods
$pageNumber = $request->query->getInt('page');
$isPublished = $request->query->getBoolean('published');
ParameterBag defines some useful methods
// very useful methods
$pageNumber = $request->query->getInt('page');
$isPublished = $request->query->getBoolean('published');
// 'ABC+12.34DEF' -> 'ABCDEF'
$request->query->getAlpha('parameter');
// 'ABC+12.34DEF' -> 'ABC1234DEF'
$request->query->getAlnum('parameter');
// 'ABC+12.34DEF' -> '1234'
$request->query->getDigits('parameter');
Doctrine
Naming
strategies
I learned this from
Ruud
Bijnen
More information
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/
namingstrategy.html
Setting custom table/properties names
namespace AppBundleEntity;
use DoctrineORMMapping as ORM;
/** @ORMTable(name="api_users") */
class ApiUsers
{
/** @ORMColumn(type="string", name="api_token") */
private $apiToken;
/** @ORMColumn(type="datetime", name="created_at") */
private $createdAt;
}
Setting custom table/properties names
namespace AppBundleEntity;
use DoctrineORMMapping as ORM;
/** @ORMTable(name="api_users") */
class ApiUsers
{
/** @ORMColumn(type="string", name="api_token") */
private $apiToken;
/** @ORMColumn(type="datetime", name="created_at") */
private $createdAt;
}
Setting custom table/properties names
namespace AppBundleEntity;
use DoctrineORMMapping as ORM;
/** @ORMTable(name="api_users") */
class ApiUsers
{
/** @ORMColumn(type="string", name="api_token") */
private $apiToken;
/** @ORMColumn(type="datetime", name="created_at") */
private $createdAt;
}
ApiUsers api_users
apiToken api_token
createdAt created_at
Entity Database
Using a built-in Doctrine naming strategy
namespace AppBundleEntity;
use DoctrineORMMapping as ORM;
/** @ORMTable */
class ApiUsers
{
/** @ORMColumn(type="string") */
private $apiToken;
/** @ORMColumn(type="datetime") */
private $createdAt;
}
# app/config/config.yml
doctrine:
dbal:
# ...
orm:
naming_strategy: doctrine.orm.naming_strategy.underscore
this is automatically
translated into api_token
Defining a custom naming strategy
namespace DoctrineORMMapping;
interface NamingStrategy
{
function classToTableName($className);
function propertyToColumnName($propertyName);
function referenceColumnName();
function joinColumnName($propertyName);
function joinTableName($sourceEntity, $targetEntity, $propertyName);
function joinKeyColumnName($entityName, $referencedColumnName);
}
Abstract
relationships
I learned this from
Ryan
Weaver
More information
http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html
Most entity relations are well defined
Invoice
entity
User
entity
ManyToOne
What if the target entity can change?
Invoice
entity
User
entityManyToOne
Company
entity
Our needs
• Create a generic InvoiceBundle able to
work both with User and Company
entities.
• No code change or special configuration is
needed for the bundle.
1. Define an abstract "subject" interface
namespace AcmeInvoiceBundleModel;
interface InvoiceSubjectInterface
{
// Add here your custom methods
// ...
}
2. Make entities implement this interface
use DoctrineORMMapping as ORM;
use AcmeInvoiceBundleModelInvoiceSubjectInterface;
/** @ORMEntity */
class User implements InvoiceSubjectInterface
{
// ...
}
/** @ORMEntity */
class Company implements InvoiceSubjectInterface
{
// ...
}
different
applications
3. Configure the entity association
namespace AcmeInvoiceBundleEntity;
use DoctrineORMMapping AS ORM;
use AcmeInvoiceBundleModelInvoiceSubjectInterface;
/** @ORMEntity */
class Invoice
{
/**
* @ORMManyToOne(
targetEntity="AcmeInvoiceBundleModelInvoiceSubjectInterface")
*/
protected $subject;
}
3. Configure the entity association
namespace AcmeInvoiceBundleEntity;
use DoctrineORMMapping AS ORM;
use AcmeInvoiceBundleModelInvoiceSubjectInterface;
/** @ORMEntity */
class Invoice
{
/**
* @ORMManyToOne(
targetEntity="AcmeInvoiceBundleModelInvoiceSubjectInterface")
*/
protected $subject;
}
abstract target entity
4. Define the target entity (at each application)
# app/config/config.yml
doctrine:
# ...
orm:
# ...
resolve_target_entities:
AcmeInvoiceBundleModelInvoiceSubjectInterface:
AcmeAppBundleEntityCustomer
this is where magic
happens: dynamic entity
resolution at runtime
Improved
WHERE … IN
I learned this from More information
http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/changelog/

migration_2_5.html#query-api-where-in-query-using-a-collection-as-parameter
Marco
Pivetta
Common code when using collections
$categories = ...
$categoryIds = array();
foreach ($categories as $category) {
$categoryIds[] = $category->getId();
}
$queryBuilder = $this
->where('model.category IN (:category_ids)')
->setParameter('category_ids', $categoryIds)
;
Common code when using collections
$categories = ...
$categoryIds = array();
foreach ($categories as $category) {
$categoryIds[] = $category->getId();
}
$queryBuilder = $this
->where('model.category IN (:category_ids)')
->setParameter('category_ids', $categoryIds)
;
transform the
ArrayCollection
into an array of IDs
In Doctrine 2.5 this is no longer needed
$categories = ...
$categoryIds = array();
foreach ($categories as $category) {
$categoryIds[] = $category->getId();
}
$queryBuilder = $this
->where('model.category IN (:categories)')
->setParameter('categories', $categories)
;
WHERE..IN
supports the use of
ArrayCollection
Default table
options
I learned this from More information
https://github.com/doctrine/DoctrineBundle/pull/420Darien
Hager
Define the default table options
# app/config/config.yml
doctrine:
dbal:
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
engine: InnoDB
DoctrineBundle 1.6NEW
Define the default table options
# app/config/config.yml
doctrine:
dbal:
default_table_options:
charset: utf8mb4
collate: utf8mb4_unicode_ci
engine: InnoDB
DoctrineBundle 1.6NEW
required to
support emojis
Savepoints
I learned this from More information
https://github.com/doctrine/DoctrineBundle/pull/451
http://doctrine.readthedocs.org/en/latest/en/manual/transactions.html
Christopher
Davis
Normal Doctrine transaction
try {
$conn->beginTransaction();
// do something ...
$conn->commit();
} catch(Exception $e) {
$conn->rollback();
}
Doctrine transaction with save points
try {
$conn->beginTransaction('create_user');
// do something ...
$conn->commit('create_user');
} catch(Exception $e) {
$conn->rollback('create_user');
}
Give a name to a
transaction to create
a "save point"
Nesting Doctrine transactions
try {
$conn->beginTransaction();
// do something ...
$conn->beginTransaction('create_user');
try {
// do something ...
$conn->commit('create_user');
} catch(Exception $e) { $conn->rollback('create_user'); }
$conn->commit();
} catch(Exception $e) { $conn->rollback(); }
Nested transaction
with a save point
Adding "save points" automatically
# app/config/config.yml
doctrine:
dbal:
use_savepoints: true
DoctrineBundle 1.6NEW
Adding "save points" automatically
# app/config/config.yml
doctrine:
dbal:
use_savepoints: true
protected function _getNestedTransactionSavePointName()
{
return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel;
}
DoctrineDBALConnection
DoctrineBundle 1.6NEW
mini-tip
Optional
things
I learned this from
Hugo
Hamon
Christophe
Coevoet
Grégoire
Pineau
The "action" suffix is optional for annotations
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function index(Request $request)
{
// ...
}
}
no need to call this
method indexAction()
The "action" suffix is optional for annotations
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function index(Request $request)
{
// ...
}
}
no need to call this
method indexAction()
this only works when using
routing annotations
The "action" suffix is optional for annotations
class DefaultController extends Controller
{
/**
* @Route("/", name="homepage")
*/
public function index(Request $request)
{
// ...
}
}
no need to call this
method indexAction()
this only works when using
routing annotations
best used when the
controller class only
contains action methods
The "method" param is optional for listeners
# app/config/services.yml
services:
app.exception_listener:
class: AppBundleEventListenerExceptionListener
tags:
- {
name: kernel.event_listener,
event: kernel.exception,
method: 'onKernelException'
}
no need to define the
method parameter
The "method" param is optional for listeners
# app/config/services.yml
services:
app.exception_listener:
class: AppBundleEventListenerExceptionListener
tags:
- {
name: kernel.event_listener,
event: kernel.exception,
method: 'onKernelException'
}
no need to define the
method parameter
on + camelCased event name
The is_granted() check in error pages
{# app/Resources/TwigBundle/views/Exception/error.html.twig #}
{% if app.user and is_granted('ROLE_ADMIN') %}
...
{% endif %}
Symfony 2.7 and previous need
this check to avoid issues in
pages not covered by a firewall.
The is_granted() check in error pages
{# app/Resources/TwigBundle/views/Exception/error.html.twig #}
{% if app.user and is_granted('ROLE_ADMIN') %}
...
{% endif %}
Symfony 2.7 and previous need
this check to avoid issues in
pages not covered by a firewall.
{% if is_granted('ROLE_ADMIN') %}
...
{% endif %}
Symfony 2.8 no longer
requires this check
Composer
Improve class
loading
performance
I learned this from More information
https://github.com/symfony/symfony/pull/16655Tobias
Schultze
This is what we
said during
SymfonyCon 2014
Don't load test classes in production
{
"autoload": {
"psr-4": { "MyLibrary": "src/" }
},
"autoload-dev": {
"psr-4": { "MyLibraryTests": "tests/" }
}
}
New "exclude-from-classmap" option
// composer.json
{
"autoload": {
"exclude-from-classmap": ["/Tests/", "/test/", "/tests/"]
}
}
These classes are excluded
from the optimized autoloader
New "exclude-from-classmap" option
// composer.json
{
"autoload": {
"exclude-from-classmap": ["/Tests/", "/test/", "/tests/"]
}
}
$ composer dump-autoload --optimize
These classes are excluded
from the optimized autoloader
New "exclude-from-classmap" option
// composer.json
{
"autoload": {
"exclude-from-classmap": ["**/Tests/", "/test/*"]
}
}
** matches anything
* matches anything except /
github.com/symfony/symfony/pull/16397
Twig
Deprecated
filters
I learned this from More information
http://twig.sensiolabs.org/doc/advanced.html#deprecated-filtersFabien
Potencier
Applications evolve in time…
class AppExtension extends Twig_Extension
{
public function getFilters()
{
return array(
new Twig_SimpleFilter('old_filter', ...),
new Twig_SimpleFilter('new_filter', ...),
);
}
}
How can you deprecate
"old_filter" without
breaking the application?
Filters can be deprecated
class AppExtension extends Twig_Extension
{
public function getFilters()
{
return array(
new Twig_SimpleFilter('old_filter', ..., array(
'deprecated' => true,
'alternative' => 'new_filter',
)),
new Twig_SimpleFilter('new_filter', ...),
);
}
}
Twig 1.23NEW
This is how you deprecate
filters and alert developers
without breaking things
Check if some
block exists
I learned this from More information
https://github.com/twigphp/Twig/pull/1831Martin
Hasoň
Avoid missing blocks
{% if 'title' is block %}
<title>{{ block('title') }}<title>
{% endif %}
Twig 1.2XNEW
not merged yet
Variadic
filters
I learned this from More information
https://github.com/twigphp/Twig/pull/1699Martin
Hasoň
The problem with filter arguments
{{ product.photo|image(400, 150, 0.9) }}
The problem with filter arguments
{{ product.photo|image(400, 150, 0.9) }}
What if I need to define more arguments?
The problem with filter arguments
{{ product.photo|image(400, 150, 0.9) }}
{{ product.photo|image(
width = 400, height = 150, opacity = 0.9
) }}
What if I need to define more arguments?
this is a valid solution for Twig, but the
underlying PHP code is still very complex
Defining a filter with lots of arguments
<?php
$filter = new Twig_SimpleFilter('image', function (
$path, $width, $height, $opacity
) {
$path = ...
$width = ...
$height = ...
$opacity = ...
});
Defining a variadic filter
$filter = new Twig_SimpleFilter('image', function (
$path, $options = array()
) {
// ...
}, array('is_variadic' => true));
Defining a variadic filter
$filter = new Twig_SimpleFilter('image', function (
$path, $options = array()
) {
// ...
}, array('is_variadic' => true));
a single variadic parameter holds any
number of passed parameters (unlimited)
Getting the values passed
$filter = new Twig_SimpleFilter('image', function (
$path, $options = array()
) {
$path = ...
$width = $options['width'];
$height = $options['height'];
$opacity = $options['opacity'];
}, array('is_variadic' => true));
Template
source code
I learned this from More information
https://github.com/twigphp/Twig/pull/1813
https://github.com/twigphp/Twig/pull/1807
Nicolas
Grekas
The Template class added a new method
abstract class Twig_Template implements Twig_TemplateInterface
{
// ...
public function getSource()
{
// ...
}
}
Twig 1.22NEW
How to display the source code of the template
{% set template = _self %}
{{ template.source|e }}
How to display the source code of the template
{% set template = _self %}
{{ template.source|e }}
having the source code allows for
example to test if the template is
backwards/forwards compatible
The (dirty) secret of the getSource() method
/* default/index.html.twig */
class __TwigTemplate_c917e55e0a4ad9b0ed28003c15c48de756d875a69e121aca97d5a53e84eaef4f extends Twig_Template
{
public function __construct(Twig_Environment $env) { }
protected function doDisplay(array $context, array $blocks = array()) { // ... }
public function getTemplateName()
{
return "default/index.html.twig";
}
// ...
public function getDebugInfo()
{
return array ( 115 => 54, 109 => 53, 93 => 43, 68 => 22, 66 => 21, 57 => 15, 46 => 7, 41 => 4, 35 => 3, 11 => 1,);
}
}
/* {% extends 'base.html.twig' %}*/
/* */
/* {% block body %}*/
/* <div id="wrapper">*/
/* <div id="container">*/
/* <div id="welcome">*/
/* <h1><span>Welcome to</span> Symfony {{ constant('SymfonyComponentHttpKernelKernel::VERSION') }}</h1>*/
/* </div>*/
/* ... */
Compiled Twig templates include
its full source code at the bottom
mini-tip
Email delivery
whitelist
I learned this from More information
https://github.com/symfony/symfony-docs/pull/4924
https://github.com/symfony/symfony-docs/pull/4925
Terje
Bråten
E.
Weimann
In dev environment, redirect all emails
# app/config/config_dev.yml
swiftmailer:
delivery_address: dev@example.com
Whitelist some emails in dev environment
# app/config/config_dev.yml
swiftmailer:
delivery_address: dev@example.com
delivery_whitelist:
- "/notifications@example.com$/"
- "/^admin@*$/""
Whitelist some emails in dev environment
# app/config/config_dev.yml
swiftmailer:
delivery_address: dev@example.com
delivery_whitelist:
- "/notifications@example.com$/"
- "/^admin@*$/""
email addresses that match
the regular expression will be
sent (even in dev environment)
Configuration
Environment
variables
I learned this from More information
http://github.com/symfony/symfony/pull/16403Fabien
Potencier
Setting ENV variables via the web server
<VirtualHost *:80>
ServerName Symfony
DocumentRoot "/path/to/symfony_2_app/web"
DirectoryIndex index.php index.html
SetEnv SYMFONY__DATABASE_USER user
SetEnv SYMFONY__DATABASE_PASSWORD secret
<Directory "/path/to/symfony_2_app/web">
AllowOverride All
Allow from All
</Directory>
</VirtualHost>
same as setting
database_user and
database_password
in parameters.yml
ENV variables are dumped into the container
// app/cache/dev/appDevDebugProjectContainer.xml
<container>
<parameters>
<parameter key="database_user">root</parameter>
<parameter key="database_password"> __secret__ </parameter>
<!-- ... -->
</parameters>
</container>
// app/cache/prod/appProdProjectContainer.php
protected function getDefaultParameters()
{
return array(
// ...
'database_user' => 'root',
'database_password' => __secret__,
);
}
DEV
environment
PROD
environment
ENV variables are dumped into the container
// app/cache/dev/appDevDebugProjectContainer.xml
<container>
<parameters>
<parameter key="database_user">root</parameter>
<parameter key="database_password"> __secret__ </parameter>
<!-- ... -->
</parameters>
</container>
// app/cache/prod/appProdProjectContainer.php
protected function getDefaultParameters()
{
return array(
// ...
'database_user' => 'root',
'database_password' => __secret__,
);
}
DEV
environment
PROD
environment
You can see the password.
ENV variables are dumped into the container
// app/cache/dev/appDevDebugProjectContainer.xml
<container>
<parameters>
<parameter key="database_user">root</parameter>
<parameter key="database_password"> __secret__ </parameter>
<!-- ... -->
</parameters>
</container>
// app/cache/prod/appProdProjectContainer.php
protected function getDefaultParameters()
{
return array(
// ...
'database_user' => 'root',
'database_password' => __secret__,
);
}
DEV
environment
PROD
environment
You can see the password.
It's static (if ENV variable
changes, app breaks)
This is what you want…
// app/cache/prod/appProdProjectContainer.php
protected function getDefaultParameters()
{
return array(
// ...
'database_user' => 'root',
'database_password' => getenv("SYMFONY__DATABASE_PASSWORD");
);
}
This is what you want…
// app/cache/prod/appProdProjectContainer.php
protected function getDefaultParameters()
{
return array(
// ...
'database_user' => 'root',
'database_password' => getenv("SYMFONY__DATABASE_PASSWORD");
);
}
… but we failed at
implementing this feature.
github.com/symfony/symfony/pull/16403
github.com/Incenteev/DynamicParametersBundle
Alternative to
environment
variables
1. Create a "credentials" file in the prod server
# /etc/credentials/example.com.yml
parameters:
database_user: ...
database_password: ...
mailer_user: ...
mailer_password: ...
secret: ...
github_client_id: ...
github_client_secret: ...
2. Load the credentials file in the application
# app/config/config.yml
imports:
- { resource: parameters.yml }
- { resource: services.yml }
- { resource: security.yml }
- { resource: admin.yml }
- { resource: '/etc/credentials/example.com.yml',
ignore_errors: true }
# ...
3. In "dev" machine
• Credentials file doesn't exist but no error is
triggered (because of ignore_errors).
• App uses the regular parameters.yml file.
4. In "prod" machine
• Credentials file overrides parameters.yml
• The right parameters are available
anywhere (e.g. console commands)
• Developers can't see the production
configuration options.
• Requires discipline to add/remove options.
Monolog
Custom
channels
Creating a custom channel is painless
# app/config/config.yml
monolog:
channels: ["marketing"]
Creating a custom channel is painless
# app/config/config.yml
monolog:
channels: ["marketing"]
$this->get('monolog.logger.marketing')->info('......');
Creating a custom channel is painless
# app/config/config.yml
monolog:
channels: ["marketing"]
$this->get('monolog.logger.marketing')->info('......');
# app/logs/dev.log
[2015-11-30 17:44:19] marketing.INFO: ...... [] []
Creating a custom channel is painless
# app/config/config.yml
monolog:
channels: ["marketing"]
now you can process these
messages apart from the
rest of logs (e.g. save them
in a different file)
$this->get('monolog.logger.marketing')->info('......');
# app/logs/dev.log
[2015-11-30 17:44:19] marketing.INFO: ...... [] []
The channel name can be a parameter
# app/config/config.yml
parameters:
app_channel: 'marketing'
monolog:
channels: [%app_channel%]
services:
app_logger:
class: ...
tags: [{ name: monolog.logger, channel: %app_channel% }]
MonologBundle 2.8NEW
Custom
formatters
Grégoire
Pineau
I learned this from More information
http://symfony.com/doc/current/cookbook/logging/monolog.html#changing-

the-formatter
This is what we
said during
SymfonyCon 2014
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 }
Creating simple custom formatters is painless
# app/config/config.yml
services:
app.log.formatter:
class: 'MonologFormatterLineFormatter'
public: false
arguments:
- "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n

%%context%%nn"
- 'H:i:s'
- false
Creating simple custom formatters is painless
# app/config/config.yml
services:
app.log.formatter:
class: 'MonologFormatterLineFormatter'
public: false
arguments:
- "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n

%%context%%nn"
- 'H:i:s'
- false
the format of
each log line
Creating simple custom formatters is painless
# app/config/config.yml
services:
app.log.formatter:
class: 'MonologFormatterLineFormatter'
public: false
arguments:
- "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n

%%context%%nn"
- 'H:i:s'
- false
the format of
each log line
the format of
%datetime% placeholder
Creating simple custom formatters is painless
# app/config/config.yml
services:
app.log.formatter:
class: 'MonologFormatterLineFormatter'
public: false
arguments:
- "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n

%%context%%nn"
- 'H:i:s'
- false
the format of
each log line
the format of
%datetime% placeholder
Creating simple custom formatters is painless
# app/config/config.yml
services:
app.log.formatter:
class: 'MonologFormatterLineFormatter'
public: false
arguments:
- "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n

%%context%%nn"
- 'H:i:s'
- false
the format of
each log line
the format of
%datetime% placeholder
ignore n inside the
log messages?
Enable the custom log formatter
# app/config/config_dev.yml
monolog:
handlers:
main:
type: stream
path: "%kernel.logs_dir%/%kernel.environment%.log"
level: debug
formatter: app.log.formatter
add the formatter option
Custom log format Default log format
INFO [18:38:21] [php] The SymfonyComponent
DependencyInjectionReference::isStrict method is
deprecated since version 2.8 and will be removed in 3.0.
{"type":16384,"file":"myproject/vendor/symfony/symfony/
src/Symfony/Component/DependencyInjection/
Reference.php","line":73,"level":28928}
INFO [18:38:21] [request] Matched route "homepage".
{"route_parameters":{"_controller":"AppBundle
Controller
DefaultController::indexAction","_route":"homepage"},"r
equest_uri":"http://127.0.0.1:8000/"}
INFO [18:38:21] [security] Populated the TokenStorage
with an anonymous Token.
[]
DEBUG [18:38:21] [event] Notified event "kernel.request"
to listener "SymfonyComponentHttpKernelEventListener
DebugHandlersListener::configure".
[]
DEBUG [18:38:21] [event] Notified event "kernel.request"
to listener "SymfonyComponentHttpKernelEventListener
ProfilerListener::onKernelRequest".
[2015-11-30 18:38:21] php.INFO: The SymfonyComponent
DependencyInjectionReference::isStrict method is
deprecated since version 2.8 and will be removed in 3.0.
{"type":16384,"file":"myproject/vendor/symfony/symfony/
src/Symfony/Component/DependencyInjection/
Reference.php","line":73,"level":28928} []
[2015-11-30 18:38:21] request.INFO: Matched route
"homepage". {"route_parameters":
{"_controller":"AppBundleController
DefaultController::indexAction","_route":"homepage"},"r
equest_uri":"http://127.0.0.1:8000/"} []
[2015-11-30 18:38:21] security.INFO: Populated the
TokenStorage with an anonymous Token. [] []
[2015-11-30 18:38:21] event.DEBUG: Notified event
"kernel.request" to listener "SymfonyComponent
HttpKernelEventListener
DebugHandlersListener::configure". [] []
[2015-11-30 18:38:21] event.DEBUG: Notified event
"kernel.request" to listener "SymfonyComponent
HttpKernelEventListener
ProfilerListener::onKernelRequest". [] []
[2015-11-30 18:38:21] event.DEBUG: Notified event
"kernel.request" to listener "SymfonyComponent
HttpKernelEventListenerDumpListener::configure". []
[]
Monolog
"plugins"
Jordi
Boggiano
I learned this from More information
http://github.com/Seldaek/monolog/wiki/Third-Party-Packages
github.com/Seldaek/monolog/wiki/Third-Party-Packages
bramus/monolog-colored-line-formatter
• Shows log messages in color according to
their level.
• It uses ANSI Escape Sequences.
Colored logs in action
if your editor or
console supports
ANSI escape
sequences
Colored logs in action
if your editor or
console doesn't
support ANSI
escape sequences
mini-tip
Christophe
Coevoet
Tweaking the
web debug
toolbar
I learned this from More information
http://symfony.com/doc/current/cookbook/profiler/data_collector.html
The new web debug toolbar
the new toolbar is less cluttered because it
hides panels that don't contain information
Reordering toolbar panels
# app/config/services.yml
services:
data_collector.request:
class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector
tags:
- { name: "data_collector", template: "@WebProfiler/Collector/request.html.twig",
id: "request", priority: "-512" }
data_collector.security:
class: 'SymfonyBundleSecurityBundleDataCollectorSecurityDataCollector'
arguments: ['@security.token_storage', '@security.role_hierarchy', '@security.logout_url_generator']
tags:
- { name: "data_collector", template: "@Security/Collector/security.html.twig",
id: "security", priority: "512" }
Reordering toolbar panels
# app/config/services.yml
services:
data_collector.request:
class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector
tags:
- { name: "data_collector", template: "@WebProfiler/Collector/request.html.twig",
id: "request", priority: "-512" }
data_collector.security:
class: 'SymfonyBundleSecurityBundleDataCollectorSecurityDataCollector'
arguments: ['@security.token_storage', '@security.role_hierarchy', '@security.logout_url_generator']
tags:
- { name: "data_collector", template: "@Security/Collector/security.html.twig",
id: "security", priority: "512" }
Built-in panels priorities
Panel Priority
request 335
time 330
memory 325
ajax 315
form 310
exception 305
logger 300
events 290
Panel Priority
router 285
translation 275
security 270
twig 257
swiftmailer 245
dump 240
config -255
Adding your panels between the built-in panels
services:
app.data_collector:
# ...
tags:
- { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY)
Adding your panels between the built-in panels
services:
app.data_collector:
# ...
tags:
- { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY)
tags:
- { name: "data_collector", template: "...", id: "...", priority: "273" }
Adding your panels between the built-in panels
services:
app.data_collector:
# ...
tags:
- { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY)
tags:
- { name: "data_collector", template: "...", id: "...", priority: "273" }
tags:
- { name: "data_collector", template: "...", id: "...", priority: "320" }
Remove panels
services:
data_collector.translation:
class: SymfonyComponentTranslationDataCollectorTranslationDataCollector
arguments: ['@translator.data_collector']
tags:
- { name: "data_collector" }
data_collector.request:
class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector
tags:
- { name: "data_collector" }
to remove a panel, define its service without
any argument in its data_collector tag
Remove panels
services:
data_collector.translation:
class: SymfonyComponentTranslationDataCollectorTranslationDataCollector
arguments: ['@translator.data_collector']
tags:
- { name: "data_collector" }
data_collector.request:
class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector
tags:
- { name: "data_collector" }
to remove a panel, define its service without
any argument in its data_collector tag
remove the Translation panel
remove the Request panel
Security
Success and
failure handlers
I learned this from More information
https://github.com/symfony/symfony-docs/issues/4258Fabien
Potencier
Success/failure handlers are
available since Symfony 2.0
(2011) …
Success/failure handlers are
available since Symfony 2.0
(2011) …
… but lots of developers
don't use them because they
are not documented.
The security.interactive_login event
services:
login_listener:
class: AppBundleListenerLoginListener
arguments: ['@security.token_storage', '@doctrine']
tags:
- { name: 'kernel.event_listener', event: 'security.interactive_login' }
the most common event to "do things"
after the user successfully logs in
Defining a success handler
namespace AppBundleSecurity;
use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface;
class LoginHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// do something ...
return $this->redirect('...');
return new Response('...');
}
}
Defining a success handler
namespace AppBundleSecurity;
use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface;
class LoginHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// do something ...
return $this->redirect('...');
return new Response('...');
}
}
you just need to implement
this interface…
Defining a success handler
namespace AppBundleSecurity;
use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface;
class LoginHandler implements AuthenticationSuccessHandlerInterface
{
public function onAuthenticationSuccess(Request $request, TokenInterface $token)
{
// do something ...
return $this->redirect('...');
return new Response('...');
}
}
you just need to implement
this interface…
…and return a Response
instance
Enabling the success handler
# app/config/services.yml
services:
app.login_handler:
class: AppBundleSecurityLoginHandler
arguments: ...
# app/config/security.yml
firewalls:
main:
pattern: ^/
form_login:
success_handler: app.login_handler
Enabling the success handler
# app/config/services.yml
services:
app.login_handler:
class: AppBundleSecurityLoginHandler
arguments: ...
# app/config/security.yml
firewalls:
main:
pattern: ^/
form_login:
success_handler: app.login_handler
the LoginHandler class is
executed when the user logs
in successfully and after the
event.interactive_login
Tests
Meaningful
data providers
I learned this from More information
http://www.thisprogrammingthing.com/2015/making-dataproviders-more-maintainableScott
Warren
Common data providers
public function getUserData()
{
return array(
array(true, false, false),
array(true, true, false),
array(true, true, true),
);
}
/** @dataProvider getUserData */
public function testRegistration($register, $enable, $notify)
{
// ...
}
When an error happens…
$ phpunit -c app
F..
Time: 137 ms, Memory: 12.50Mb
There was 1 failure:
1) AppBundleTestsControllerDefaultControllerTest::testRegistration
with data set #0 (true, false, false)
very hard to understand
the exact error
Meaningful data providers
public function getUserData()
{
return array(
'register user only' => array(true, false, false),
'register and enable' => array(true, true, false),
'register, enable and notify' => array(true, true, true),
);
}
/** @dataProvider getUserData */
public function testRegistration($register, $enable, $notify)
{
// ...
}
array keys are the
labels of each data set
When an error happens…
$ phpunit -c app
F..
Time: 137 ms, Memory: 12.50Mb
There was 1 failure:
1) AppBundleTestsControllerDefaultControllerTest::testRegistration
with data set "register user only" (true, false, false)
meaningful error
messages
Generating
data providers
I learned this from More information
http://php.net/manual/en/language.generators.overview.phpTugdual
Saunier
Common data providers
public function dataProvider()
{
public function providerBasicDrivers()
{
return array(
array('doctrine.orm.cache.apc.class', array('type' => 'apc')),
array('doctrine.orm.cache.array.class', array('type' => 'array')),
array('doctrine.orm.cache.xcache.class', array('type' => 'xcache')),
array('doctrine.orm.cache.wincache.class', array('type' => 'wincache')),
array('doctrine.orm.cache.zenddata.class', array('type' => 'zenddata')),
);
}
}
Data providers using PHP generators
public function dataProvider()
{
public function providerBasicDrivers()
{
yield ['doctrine.orm.cache.apc.class', ['type' => 'apc']];
yield ['doctrine.orm.cache.array.class', ['type' => 'array']];
yield ['doctrine.orm.cache.xcache.class', ['type' => 'xcache']];
yield ['doctrine.orm.cache.wincache.class', ['type' => 'wincache']];
yield ['doctrine.orm.cache.zenddata.class', ['type' => 'zenddata']];
}
}
PHP 5.5NEW
Data providers using PHP generators
public function dataProvider()
{
public function providerBasicDrivers()
{
yield ['doctrine.orm.cache.apc.class', ['type' => 'apc']];
yield ['doctrine.orm.cache.array.class', ['type' => 'array']];
yield ['doctrine.orm.cache.xcache.class', ['type' => 'xcache']];
yield ['doctrine.orm.cache.wincache.class', ['type' => 'wincache']];
yield ['doctrine.orm.cache.zenddata.class', ['type' => 'zenddata']];
}
}
PHP 5.5NEW
it reduces memory
consumption
Better setUp
and tearDown
I learned this from More information
https://phpunit.de/manual/current/en/appendixes.annotations.html
#appendixes.annotations.before
Sebastian
Bergmann
Common use of setUp( ) method in tests
class IntegrationTest extends PHPUnit_Framework_TestCase
{
private $rootDir;
private $fs;
public function setUp()
{
$this->rootDir = realpath(__DIR__.'/../../../../');
$this->fs = new Filesystem();
if (!$this->fs->exists($this->rootDir.'/symfony.phar')) {
throw new RuntimeException("...");
}
}
}
Common use of setUp( ) method in tests
class IntegrationTest extends PHPUnit_Framework_TestCase
{
private $rootDir;
private $fs;
public function setUp()
{
$this->rootDir = realpath(__DIR__.'/../../../../');
$this->fs = new Filesystem();
if (!$this->fs->exists($this->rootDir.'/symfony.phar')) {
throw new RuntimeException("...");
}
}
}
public function setUp()
public function tearDown()
public static function setUpBeforeClass()
public static function tearDownAfterClass()
Better alternative: @before annotation
class IntegrationTest extends PHPUnit_Framework_TestCase
{
private $rootDir;
private $fs;
/** @before */
public function initialize()
{
$this->rootDir = realpath(__DIR__.'/../../../../');
$this->fs = new Filesystem();
}
/** @before */
public function checkPharFile() {
if (!$this->fs->exists($this->rootDir.'/symfony.phar')) {
throw new RuntimeException("...");
}
}
}
Better alternative: @before annotation
class IntegrationTest extends PHPUnit_Framework_TestCase
{
private $rootDir;
private $fs;
/** @before */
public function initialize()
{
$this->rootDir = realpath(__DIR__.'/../../../../');
$this->fs = new Filesystem();
}
/** @before */
public function checkPharFile() {
if (!$this->fs->exists($this->rootDir.'/symfony.phar')) {
throw new RuntimeException("...");
}
}
}
you can define any number
of @before methods
Better alternative: @before annotation
class IntegrationTest extends PHPUnit_Framework_TestCase
{
private $rootDir;
private $fs;
/** @before */
public function initialize()
{
$this->rootDir = realpath(__DIR__.'/../../../../');
$this->fs = new Filesystem();
}
/** @before */
public function checkPharFile() {
if (!$this->fs->exists($this->rootDir.'/symfony.phar')) {
throw new RuntimeException("...");
}
}
}
you can define any number
of @before methods
order matters (first
method is executed first)
Alternatives to setUp and tearDown
class IntegrationTest extends PHPUnit_Framework_TestCase
{
/** @before */
public function initialize() { }
/** @after */
public function clean() { }
/** @beforeClass */
public static function initialize() { }
/** @afterClass */
public static function initialize() { }
}
Avoid risky
tests
I learned this from More information
https://phpunit.de/manual/current/en/risky-tests.htmlSebastian
Bergmann
Common PHPUnit configuration
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
>
...
</phpunit>
Common PHPUnit configuration
<?xml version="1.0" encoding="UTF-8"?>
<phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd"
backupGlobals="false"
colors="true"
bootstrap="vendor/autoload.php"
beStrictAboutTestsThatDoNotTestAnything="true"
checkForUnintentionallyCoveredCode="true"
beStrictAboutOutputDuringTests="true"
beStrictAboutTestSize="true"
beStrictAboutChangesToGlobalState="true"
>
...
</phpunit>
this strict configuration
will force you to create
better tests
mini-tip
Nicolas
Grekas
Deprecation
stack traces
I learned this from More information
https://github.com/symfony/symfony/pull/16709
Use the PHPUnit Bridge to detect deprecations
$ composer require --dev symfony/phpunit-bridge
$ phpunit -c app
PHPUnit by Sebastian Bergmann and contributors.
.................
Time: 3.7 seconds, Memory: 75.00Mb
OK (17 tests, 21 assertions)
Remaining deprecation notices (368)
OK (17 tests, 21 assertions)
Remaining deprecation notices (368)
When a deprecation occurs…
This information is
useful, but incomplete.
Often you need the
stack trace of the code
that triggered the
deprecation.
On-demand deprecation stack traces
// BEFORE
$ phpunit -c app
// AFTER
$ SYMFONY_DEPRECATIONS_HELPER=/Blog::index/ 
phpunit -c app
A regular expression that
is matched against
className::methodName
On-demand deprecation stack traces
$ SYMFONY_DEPRECATIONS_HELPER=/.*/ phpunit -c app
The class "SymfonyBundleAsseticBundleConfigAsseticResource" is performing resource

checking through ResourceInterface::isFresh(), which is deprecated since 2.8 and will be removed in 3.0
Stack trace:

#0 vendor/symfony/symfony/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php(32):

trigger_error()

#1 app/bootstrap.php.cache(3061): SymfonyComponentConfigResourceBCResourceInterfaceChecker->isFresh()

#2 vendor/symfony/symfony/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php(45):

SymfonyComponentConfigResourceCheckerConfigCache->isFresh()

#3 vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php(302): SymfonyComponentConfigResourceCheckerConfigCacheFactory->cache()

#4 vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php(250): SymfonyComponentRoutingRouter->getMatcher()

#5 vendor/symfony/symfony/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php(154): SymfonyComponentRoutingRouter->matchRequest()

#6 [internal function]: SymfonyComponentHttpKernelEventListenerRouterListener->onKernelRequest()

#7 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php(61): call_user_func()

#8 [internal function]: SymfonyComponentEventDispatcherDebugWrappedListener->__invoke()

#9 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(181): call_user_func()

#10 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(46): SymfonyComponentEventDispatcherEventDispatcher->doDispatch()

#11 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php(132): SymfonyComponentEventDispatcherEventDispatcher-
>dispatch()

#12 app/bootstrap.php.cache(3178): SymfonyComponentEventDispatcherDebugTraceableEventDispatcher->dispatch()

#13 app/bootstrap.php.cache(3151): SymfonyComponentHttpKernelHttpKernel->handleRaw()

#14 app/bootstrap.php.cache(3302): SymfonyComponentHttpKernelHttpKernel->handle()

#15 app/bootstrap.php.cache(2498): SymfonyComponentHttpKernelDependencyInjectionContainerAwareHttpKernel->handle()

#16 vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Client.php(79): SymfonyComponentHttpKernelKernel->handle()

#17 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Client.php(131): SymfonyComponentHttpKernelClient->doRequest()

#18 vendor/symfony/symfony/src/Symfony/Component/BrowserKit/Client.php(317): SymfonyBundleFrameworkBundleClient->doRequest()

#19 src/AppBundle/Tests/Controller/Admin/BlogControllerTest.php(42): SymfonyComponentBrowserKitClient->request()

#20 [internal function]: AppBundleTestsControllerAdminBlogControllerTest->testRegularUsersCannotAccessToTheBackend()

#21 {main}
It stops at the first
deprecation and shows
its stack trace
Services
Smoke testing
for services
Benjamin
Eberlei
I learned this from More information
http://www.whitewashing.de/2015/06/12/the_containertest.html
«Smoke Tests are an
early warning for when
things go wrong»
Automatic "smoke testing" for services
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
$service = $client->getContainer()->get($serviceId);
$this->assertNotNull($service);
}
}
Automatic "smoke testing" for services
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
$service = $client->getContainer()->get($serviceId);
$this->assertNotNull($service);
}
}
get all services defined
by the container
Automatic "smoke testing" for services
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
$service = $client->getContainer()->get($serviceId);
$this->assertNotNull($service);
}
}
get all services defined
by the container
try to instantiate all of
them to look for errors
Automatic "smoke testing" for services
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
try {
$service = $client->getContainer()->get($serviceId);
$this->assertNotNull($service);
} catch(InactiveScopeException $e) { }
}
}
you may need this try … catch to avoid
issues with request related services
Manual "smoke testing" for important services
public static function dataServices()
{
return [
['app.datetimepicker', 'AppBundleFormTypeDateTimePickerType'],
['app.redirect_listener', 'AppBundleEventListenerRedirectToPreferredLocaleListener'],
];
}
/** @dataProvider dataServices */
public function testImportantServices($id, $class)
{
$client = static::createClient();
$service = $client->getContainer()->get($id);
$this->assertInstanceOf($class, $service);
}
check that the service
is instantiable and that
the returned class is
the right one
Services
benchmark
Alexandre
Salomé
I learned this from More information
https://speakerdeck.com/alexandresalome/french-symfony2-et-performances
Service instantiation should not take too long
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
try {
$startedAt = microtime(true);
$service = $client->getContainer()->get($serviceId);
$elapsed = (microtime(true) - $startedAt) * 1000;
$this->assertLessThan(50, $elapsed);
} catch(InactiveScopeException $e) {
}
}
}
Service instantiation should not take too long
public function testContainerServices()
{
$client = static::createClient();
foreach ($client->getContainer()->getServiceIds() as $serviceId) {
try {
$startedAt = microtime(true);
$service = $client->getContainer()->get($serviceId);
$elapsed = (microtime(true) - $startedAt) * 1000;
$this->assertLessThan(50, $elapsed);
} catch(InactiveScopeException $e) {
}
}
}
services should be
created in 50ms or less
Most expensive
services in
Symfony Standard
Service Time (ms)
cache_warmer 42.95
doctrine.orm.default_entity_manager 14.05
web_profiler.controller.router 11.88
swiftmailer.mailer.default 6.65
profiler 5.45
annotation_reader 4.53
doctrine.dbal.default_connection 4.22
form.type_extension.form.validator 4.13
routing.loader 4.02
form.type.choice 3.10
mini-tip
Simpler
command
interactions
Nikita
Gusakov
I learned this from More information
https://github.com/symfony/swiftmailer-bundle/blob/master/Command/
NewEmailCommand.php
Common lifecycle of a command
class MyCustomCommand extends ContainerAwareCommand
{
protected function configure()
{
}
protected function execute(InputInterface $input, OutputInterface $output)
{
}
}
Full lifecycle of a command
class MyCustomCommand extends ContainerAwareCommand
{
protected function configure()
{ }
protected function initialize(InputInterface $input, OutputInterface $output)
{ }
protected function interact(InputInterface $input, OutputInterface $output)
{ }
protected function execute(InputInterface $input, OutputInterface $output)
{ }
}
here you can ask the user for
any missing option or argument
Ask for any missing command argument
class MyCommand extends ContainerAwareCommand
{
// ...
protected function interact(InputInterface $input, OutputInterface $output)
{
$dialog = $this->getHelper('dialog');
foreach ($input->getArguments() as $argument => $value) {
if ($value === null) {
$input->setArgument($argument, $dialog->ask($output,
sprintf('<question>%s</question>: ', ucfirst($argument))
));
}
}
}
}
the interact() method is usually
very verbose. This is the minimal
useful interact() method.
Console
Console Style
Guide
Most commands are a mess
$description = $this->formatSection(
'container', sprintf('Information for service <info>%s</info>', $options['id']))
."n".sprintf('<comment>Service Id</comment> %s', isset($options['id']) ? $options['id'] : '-')
."n".sprintf('<comment>Class</comment> %s', get_class($service)
);
$description[] = sprintf('<comment>Scope</comment> %s', $definition->getScope(false));
$description[] = sprintf('<comment>Public</comment> %s', $definition->isPublic() ? 'yes' : 'no');
$description[] = sprintf('<comment>Synthetic</comment> %s', $definition->isSynthetic() ? 'yes' : 'no');
$description[] = sprintf('<comment>Lazy</comment> %s', $definition->isLazy() ? 'yes' : 'no');
$this->writeText($description, $options);
Most commands are a mess
$description = $this->formatSection(
'container', sprintf('Information for service <info>%s</info>', $options['id']))
."n".sprintf('<comment>Service Id</comment> %s', isset($options['id']) ? $options['id'] : '-')
."n".sprintf('<comment>Class</comment> %s', get_class($service)
);
$description[] = sprintf('<comment>Scope</comment> %s', $definition->getScope(false));
$description[] = sprintf('<comment>Public</comment> %s', $definition->isPublic() ? 'yes' : 'no');
$description[] = sprintf('<comment>Synthetic</comment> %s', $definition->isSynthetic() ? 'yes' : 'no');
$description[] = sprintf('<comment>Lazy</comment> %s', $definition->isLazy() ? 'yes' : 'no');
$this->writeText($description, $options);
you are mixing content with
presentation, like in the good
old days of HTML+CSS
The new "Style Guide"
makes built-in commands
visually consistent.
You can use it in your own commands too
use SymfonyComponentConsoleStyleSymfonyStyle;
class MyCustomCommand
{
// ...
protected function execute(InputInterface $input, OutputInterface $output)
{
$io = new SymfonyStyle($input, $output);
$io->...
// ...
}
}
Displaying a title (BEFORE)
// alternative 1
$formatter = $this->getHelperSet()->get('formatter');
$formattedBlock = $formatter->formatBlock($title, '', true);
$output->writeln($formattedBlock);
// alternative 2
$title = 'Lorem Ipsum Dolor Sit Amet';
$output->writeln('<info>'.$title.'</info>');
$output->writeln(str_repeat('=', strlen($title)));
// alternative 3
$output->writeln('');
$output->writeln('Add User Command Interactive Wizard');
$output->writeln('-----------------------------------');
Displaying a title (AFTER)
use SymfonyComponentConsoleStyleSymfonyStyle;
$io = new SymfonyStyle($input, $output);
$io->title('Lorem Ipsum Dolor Sit Amet');
Displaying a title (AFTER)
use SymfonyComponentConsoleStyleSymfonyStyle;
$io = new SymfonyStyle($input, $output);
$io->title('Lorem Ipsum Dolor Sit Amet');
Displaying a table (BEFORE)
$table = new Table($this->getOutput());
$table->setStyle('compact');
$table->setHeaders(['Parameter', 'Value']);
$table->addRow(['...', '...']);
$table->addRow(['...', '...']);
$table->addRow(['...', '...']);
$table->render();
Displaying a table (AFTER)
use SymfonyComponentConsoleStyleSymfonyStyle;
$headers = ['Parameter', 'Value'];
$rows = [ ... ];
$io = new SymfonyStyle($input, $output);
$io->table($headers, $rows);
Displaying a table (AFTER)
use SymfonyComponentConsoleStyleSymfonyStyle;
$headers = ['Parameter', 'Value'];
$rows = [ ... ];
$io = new SymfonyStyle($input, $output);
$io->table($headers, $rows);
All the common features are covered
$io = new SymfonyStyle($input, $output);
$io->title();
$io->section();
$io->text();
$io->listing();
$io->table();
$io->choice();
$io->success();
$io->error();
$io->warning();
$io->ask();
$io->askHidden();
$io->confirm();
Create consistent commands effortlesslyFull Sample Command
$ php app/console command
Lorem Ipsum Dolor Sit Amet
==========================
// Duis aute irure dolor in reprehenderit in voluptate velit esse
// cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat
Name Method Scheme Host Path
----------------- ------ ------ ---- ---------------------
admin_post_new ANY ANY ANY /admin/post/new
admin_post_show GET ANY ANY /admin/post/{id}
admin_post_edit ANY ANY ANY /admin/post/{id}/edit
admin_post_delete DELETE ANY ANY /admin/post/{id}
----------------- ------ ------ ---- ---------------------
! [CAUTION] Lorem ipsum dolor sit amet, consectetur adipisicing elit,
! sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.
! Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Consectetur Adipisicing Elit Sed Do Eiusmod
-------------------------------------------
* Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo.
* Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
Bundle namespace:
> AppBundle <Enter>
Bundle name [AcmeAppBundle]:
> <Enter>
Configuration format (yml, xml, php, annotation) [annotation]:
> <Enter>
Command title
All types of questions
Section title
List
Info message
Table
Caution admonition
! Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris.
Consectetur Adipisicing Elit Sed Do Eiusmod
-------------------------------------------
* Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod
tempor incididunt ut labore et dolore magna aliqua.
* Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
aliquip ex ea commodo.
* Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt
mollit anim id est laborum.
Bundle namespace:
> AppBundle <Enter>
Bundle name [AcmeAppBundle]:
> <Enter>
Configuration format (yml, xml, php, annotation) [annotation]:
> <Enter>
Do you want to enable the bundle? (yes/no) [yes]:
> <Enter>
Configuration format (select one) [annotation]:
> yml
> xml
> php
> annotation
! [NOTE] Duis aute irure dolor in reprehenderit in voluptate velit esse
! cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat.
[OK] Lorem ipsum dolor sit amet, consectetur adipisicing elit
[ERROR] Duis aute irure dolor in reprehenderit in voluptate velit esse.
[WARNING] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi.
All ty
Secti
List
All ty
resul
Note
Contact
information
Contact information
javier.eguiluz@sensiolabs.com
License of this presentation
creativecommons.org/licenses/by-nc-sa/3.0
New Symfony Tips & Tricks (SymfonyCon Paris 2015)
1 of 204

Recommended

Symfony tips and tricks by
Symfony tips and tricksSymfony tips and tricks
Symfony tips and tricksJavier Eguiluz
54.3K views197 slides
Workshop spring session 2 - La persistance au sein des applications Java by
Workshop spring   session 2 - La persistance au sein des applications JavaWorkshop spring   session 2 - La persistance au sein des applications Java
Workshop spring session 2 - La persistance au sein des applications JavaAntoine Rey
10.4K views22 slides
Support programmation orientée objet c# .net version f8 by
Support programmation orientée objet c#  .net version f8Support programmation orientée objet c#  .net version f8
Support programmation orientée objet c# .net version f8ENSET, Université Hassan II Casablanca
42.3K views198 slides
Bài 5: Làm quen với lập trình CSDL ASP.NET - Giáo trình FPT - Có ví dụ kèm theo by
Bài 5: Làm quen với lập trình CSDL ASP.NET - Giáo trình FPT - Có ví dụ kèm theoBài 5: Làm quen với lập trình CSDL ASP.NET - Giáo trình FPT - Có ví dụ kèm theo
Bài 5: Làm quen với lập trình CSDL ASP.NET - Giáo trình FPT - Có ví dụ kèm theoMasterCode.vn
14.8K views48 slides
Tp1 - WS avec JAXWS by
Tp1 - WS avec JAXWSTp1 - WS avec JAXWS
Tp1 - WS avec JAXWSLilia Sfaxi
6.3K views10 slides
Support de cours technologie et application m.youssfi by
Support de cours technologie et application m.youssfiSupport de cours technologie et application m.youssfi
Support de cours technologie et application m.youssfiENSET, Université Hassan II Casablanca
31.4K views199 slides

More Related Content

What's hot

Support programmation orientée aspect mohamed youssfi (aop) by
Support programmation orientée aspect mohamed youssfi (aop)Support programmation orientée aspect mohamed youssfi (aop)
Support programmation orientée aspect mohamed youssfi (aop)ENSET, Université Hassan II Casablanca
125.6K views78 slides
Oracle Data Guard by
Oracle Data GuardOracle Data Guard
Oracle Data GuardWashington Luiz Vaz
1.9K views44 slides
Pertemuan 10 (database client-server) by
Pertemuan 10 (database client-server)Pertemuan 10 (database client-server)
Pertemuan 10 (database client-server)Rifky A Ayub
1.9K views20 slides
Spring 2.5 by
Spring 2.5Spring 2.5
Spring 2.5Pasquale Paola
2.5K views278 slides
Support de cours entrepise java beans ejb m.youssfi by
Support de cours entrepise java beans ejb m.youssfiSupport de cours entrepise java beans ejb m.youssfi
Support de cours entrepise java beans ejb m.youssfiENSET, Université Hassan II Casablanca
46.5K views267 slides
Support JEE Spring Inversion de Controle IOC et Spring MVC by
Support JEE Spring Inversion de Controle IOC et Spring MVCSupport JEE Spring Inversion de Controle IOC et Spring MVC
Support JEE Spring Inversion de Controle IOC et Spring MVCENSET, Université Hassan II Casablanca
38K views122 slides

What's hot(20)

Pertemuan 10 (database client-server) by Rifky A Ayub
Pertemuan 10 (database client-server)Pertemuan 10 (database client-server)
Pertemuan 10 (database client-server)
Rifky A Ayub1.9K views
Diagramme de classe by Ilhem Daoudi
Diagramme de classeDiagramme de classe
Diagramme de classe
Ilhem Daoudi3.4K views
Failles de sécurité Web et Symfony by Chamalaine Soufi
Failles de sécurité Web et SymfonyFailles de sécurité Web et Symfony
Failles de sécurité Web et Symfony
Chamalaine Soufi510 views
FIDO2 Specifications Overview by FIDO Alliance
FIDO2 Specifications OverviewFIDO2 Specifications Overview
FIDO2 Specifications Overview
FIDO Alliance1.4K views

Viewers also liked

30 Symfony Best Practices by
30 Symfony Best Practices30 Symfony Best Practices
30 Symfony Best PracticesNicolas Perriault
33.9K views82 slides
Twig tips and tricks by
Twig tips and tricksTwig tips and tricks
Twig tips and tricksJavier Eguiluz
199.3K views185 slides
Symfony Best Practices by
Symfony Best PracticesSymfony Best Practices
Symfony Best PracticesBaptiste Donaux
10.4K views75 slides
Mastering Twig (DrupalCon Barcelona 2015) by
Mastering Twig (DrupalCon Barcelona 2015)Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)Javier Eguiluz
7.1K views204 slides
The Wonderful World of Symfony Components by
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony ComponentsRyan Weaver
47.4K views160 slides
Behat 3.0 meetup (March) by
Behat 3.0 meetup (March)Behat 3.0 meetup (March)
Behat 3.0 meetup (March)Konstantin Kudryashov
7.5K views102 slides

Viewers also liked(20)

Twig tips and tricks by Javier Eguiluz
Twig tips and tricksTwig tips and tricks
Twig tips and tricks
Javier Eguiluz199.3K views
Mastering Twig (DrupalCon Barcelona 2015) by Javier Eguiluz
Mastering Twig (DrupalCon Barcelona 2015)Mastering Twig (DrupalCon Barcelona 2015)
Mastering Twig (DrupalCon Barcelona 2015)
Javier Eguiluz7.1K views
The Wonderful World of Symfony Components by Ryan Weaver
The Wonderful World of Symfony ComponentsThe Wonderful World of Symfony Components
The Wonderful World of Symfony Components
Ryan Weaver47.4K views
Guard Authentication: Powerful, Beautiful Security by Ryan Weaver
Guard Authentication: Powerful, Beautiful SecurityGuard Authentication: Powerful, Beautiful Security
Guard Authentication: Powerful, Beautiful Security
Ryan Weaver9.4K views
Migrating to Symfony 3.0 by nicolas.grekas
Migrating to Symfony 3.0Migrating to Symfony 3.0
Migrating to Symfony 3.0
nicolas.grekas10.5K views
Symfony: Your Next Microframework (SymfonyCon 2015) by Ryan Weaver
Symfony: Your Next Microframework (SymfonyCon 2015)Symfony: Your Next Microframework (SymfonyCon 2015)
Symfony: Your Next Microframework (SymfonyCon 2015)
Ryan Weaver7.6K views
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014 by Matthias Noback
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
High Quality Symfony Bundles tutorial - Dutch PHP Conference 2014
Matthias Noback12.4K views
Creating Mobile Apps With PHP & Symfony2 by Pablo Godel
Creating Mobile Apps With PHP & Symfony2Creating Mobile Apps With PHP & Symfony2
Creating Mobile Apps With PHP & Symfony2
Pablo Godel46K views
Desymfony 2011 - Introducción a Symfony2 by Javier Eguiluz
Desymfony 2011 - Introducción a Symfony2Desymfony 2011 - Introducción a Symfony2
Desymfony 2011 - Introducción a Symfony2
Javier Eguiluz9.1K views
Get Soaked - An In Depth Look At PHP Streams by Davey Shafik
Get Soaked - An In Depth Look At PHP StreamsGet Soaked - An In Depth Look At PHP Streams
Get Soaked - An In Depth Look At PHP Streams
Davey Shafik12.1K views
Automation using-phing by Rajat Pandit
Automation using-phingAutomation using-phing
Automation using-phing
Rajat Pandit4.1K views
PHP5.5 is Here by julien pauli
PHP5.5 is HerePHP5.5 is Here
PHP5.5 is Here
julien pauli13.6K views
Electrify your code with PHP Generators by Mark Baker
Electrify your code with PHP GeneratorsElectrify your code with PHP Generators
Electrify your code with PHP Generators
Mark Baker3.7K views

Similar to New Symfony Tips & Tricks (SymfonyCon Paris 2015)

The Naked Bundle - Symfony Usergroup Belgium by
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup BelgiumMatthias Noback
1.1K views111 slides
The Naked Bundle - Symfony Live London 2014 by
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014Matthias Noback
4K views110 slides
The Naked Bundle - Symfony Barcelona by
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony BarcelonaMatthias Noback
3.3K views111 slides
Working With The Symfony Admin Generator by
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin GeneratorJohn Cleveley
31.2K views66 slides
The Naked Bundle - Tryout by
The Naked Bundle - TryoutThe Naked Bundle - Tryout
The Naked Bundle - TryoutMatthias Noback
913 views104 slides
Dependency injection in Drupal 8 by
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8Alexei Gorobets
3.8K views57 slides

Similar to New Symfony Tips & Tricks (SymfonyCon Paris 2015)(20)

The Naked Bundle - Symfony Usergroup Belgium by Matthias Noback
The Naked Bundle - Symfony Usergroup BelgiumThe Naked Bundle - Symfony Usergroup Belgium
The Naked Bundle - Symfony Usergroup Belgium
Matthias Noback1.1K views
The Naked Bundle - Symfony Live London 2014 by Matthias Noback
The Naked Bundle - Symfony Live London 2014The Naked Bundle - Symfony Live London 2014
The Naked Bundle - Symfony Live London 2014
Matthias Noback4K views
The Naked Bundle - Symfony Barcelona by Matthias Noback
The Naked Bundle - Symfony BarcelonaThe Naked Bundle - Symfony Barcelona
The Naked Bundle - Symfony Barcelona
Matthias Noback3.3K views
Working With The Symfony Admin Generator by John Cleveley
Working With The Symfony Admin GeneratorWorking With The Symfony Admin Generator
Working With The Symfony Admin Generator
John Cleveley31.2K views
Dependency injection in Drupal 8 by Alexei Gorobets
Dependency injection in Drupal 8Dependency injection in Drupal 8
Dependency injection in Drupal 8
Alexei Gorobets3.8K views
JavaScript and UI Architecture Best Practices by Siarhei Barysiuk
JavaScript and UI Architecture Best PracticesJavaScript and UI Architecture Best Practices
JavaScript and UI Architecture Best Practices
Siarhei Barysiuk8.9K views
Bring the fun back to java by ciklum_ods
Bring the fun back to javaBring the fun back to java
Bring the fun back to java
ciklum_ods640 views
symfony on action - WebTech 207 by patter
symfony on action - WebTech 207symfony on action - WebTech 207
symfony on action - WebTech 207
patter1.9K views
[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules by Srijan Technologies
[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules
[Srijan Wednesday Webinars] Ruling Drupal 8 with #d8rules
Srijan Technologies1.1K views
Symfony internals [english] by Raul Fraile
Symfony internals [english]Symfony internals [english]
Symfony internals [english]
Raul Fraile4.8K views
Zend Framework 1.9 Setup & Using Zend_Tool by Gordon Forsythe
Zend Framework 1.9 Setup & Using Zend_ToolZend Framework 1.9 Setup & Using Zend_Tool
Zend Framework 1.9 Setup & Using Zend_Tool
Gordon Forsythe8.5K views
Simplify your professional web development with symfony by Francois Zaninotto
Simplify your professional web development with symfonySimplify your professional web development with symfony
Simplify your professional web development with symfony
Francois Zaninotto2.7K views
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition by Nate Abele
Lithium: The Framework for People Who Hate Frameworks, Tokyo EditionLithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Lithium: The Framework for People Who Hate Frameworks, Tokyo Edition
Nate Abele1.6K views
Symfony Under the Hood by eZ Systems
Symfony Under the HoodSymfony Under the Hood
Symfony Under the Hood
eZ Systems407 views
Introducing PHP Latest Updates by Iftekhar Eather
Introducing PHP Latest UpdatesIntroducing PHP Latest Updates
Introducing PHP Latest Updates
Iftekhar Eather1.3K views
Living With Legacy Code by Rowan Merewood
Living With Legacy CodeLiving With Legacy Code
Living With Legacy Code
Rowan Merewood25.3K views
What's New In Laravel 5 by Darren Craig
What's New In Laravel 5What's New In Laravel 5
What's New In Laravel 5
Darren Craig2.2K views

More from Javier Eguiluz

deSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony by
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
13.2K views188 slides
Twig, el nuevo motor de plantillas de Drupal 8 by
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
4.6K views205 slides
Silex al límite by
Silex al límiteSilex al límite
Silex al límiteJavier Eguiluz
8.3K views265 slides
Silex, desarrollo web ágil y profesional con PHP by
Silex, desarrollo web ágil y profesional con PHPSilex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHPJavier Eguiluz
15.1K views150 slides
Twig, los mejores trucos y técnicas avanzadas by
Twig, los mejores trucos y técnicas avanzadasTwig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadasJavier Eguiluz
6.3K views175 slides
Wallpaper Notifier by
Wallpaper NotifierWallpaper Notifier
Wallpaper NotifierJavier Eguiluz
1.4K views19 slides

More from Javier Eguiluz(19)

deSymfony 2017: Symfony 4, Symfony Flex y el futuro de Symfony by Javier Eguiluz
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
Javier Eguiluz13.2K views
Twig, el nuevo motor de plantillas de Drupal 8 by Javier Eguiluz
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
Javier Eguiluz4.6K views
Silex, desarrollo web ágil y profesional con PHP by Javier Eguiluz
Silex, desarrollo web ágil y profesional con PHPSilex, desarrollo web ágil y profesional con PHP
Silex, desarrollo web ágil y profesional con PHP
Javier Eguiluz15.1K views
Twig, los mejores trucos y técnicas avanzadas by Javier Eguiluz
Twig, los mejores trucos y técnicas avanzadasTwig, los mejores trucos y técnicas avanzadas
Twig, los mejores trucos y técnicas avanzadas
Javier Eguiluz6.3K views
Desymfony 2012 - Concurso de diseño by Javier Eguiluz
Desymfony 2012 - Concurso de diseñoDesymfony 2012 - Concurso de diseño
Desymfony 2012 - Concurso de diseño
Javier Eguiluz1K views
Desymfony 2011 - Tutorial #5: Backend by Javier Eguiluz
Desymfony 2011 - Tutorial #5: BackendDesymfony 2011 - Tutorial #5: Backend
Desymfony 2011 - Tutorial #5: Backend
Javier Eguiluz1.8K views
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasos by Javier Eguiluz
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasosDesymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Desymfony 2011 - Tutorial #1: Instalacion y primeros pasos
Javier Eguiluz8K views
Symfony2, Jornadas Symfony by Javier Eguiluz
Symfony2, Jornadas SymfonySymfony2, Jornadas Symfony
Symfony2, Jornadas Symfony
Javier Eguiluz2.2K views

Recently uploaded

Ransomware is Knocking your Door_Final.pdf by
Ransomware is Knocking your Door_Final.pdfRansomware is Knocking your Door_Final.pdf
Ransomware is Knocking your Door_Final.pdfSecurity Bootcamp
59 views46 slides
SAP Automation Using Bar Code and FIORI.pdf by
SAP Automation Using Bar Code and FIORI.pdfSAP Automation Using Bar Code and FIORI.pdf
SAP Automation Using Bar Code and FIORI.pdfVirendra Rai, PMP
23 views38 slides
SUPPLIER SOURCING.pptx by
SUPPLIER SOURCING.pptxSUPPLIER SOURCING.pptx
SUPPLIER SOURCING.pptxangelicacueva6
16 views1 slide
GDSC CTU First Meeting Party by
GDSC CTU First Meeting PartyGDSC CTU First Meeting Party
GDSC CTU First Meeting PartyNational Yang Ming Chiao Tung University
11 views25 slides
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N... by
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...James Anderson
92 views32 slides
Business Analyst Series 2023 - Week 3 Session 5 by
Business Analyst Series 2023 -  Week 3 Session 5Business Analyst Series 2023 -  Week 3 Session 5
Business Analyst Series 2023 - Week 3 Session 5DianaGray10
300 views20 slides

Recently uploaded(20)

SAP Automation Using Bar Code and FIORI.pdf by Virendra Rai, PMP
SAP Automation Using Bar Code and FIORI.pdfSAP Automation Using Bar Code and FIORI.pdf
SAP Automation Using Bar Code and FIORI.pdf
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N... by James Anderson
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
GDG Cloud Southlake 28 Brad Taylor and Shawn Augenstein Old Problems in the N...
James Anderson92 views
Business Analyst Series 2023 - Week 3 Session 5 by DianaGray10
Business Analyst Series 2023 -  Week 3 Session 5Business Analyst Series 2023 -  Week 3 Session 5
Business Analyst Series 2023 - Week 3 Session 5
DianaGray10300 views
Igniting Next Level Productivity with AI-Infused Data Integration Workflows by Safe Software
Igniting Next Level Productivity with AI-Infused Data Integration Workflows Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Igniting Next Level Productivity with AI-Infused Data Integration Workflows
Safe Software280 views
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院 by IttrainingIttraining
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院
【USB韌體設計課程】精選講義節錄-USB的列舉過程_艾鍗學院
The Forbidden VPN Secrets.pdf by Mariam Shaba
The Forbidden VPN Secrets.pdfThe Forbidden VPN Secrets.pdf
The Forbidden VPN Secrets.pdf
Mariam Shaba20 views
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f... by TrustArc
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc Webinar - Managing Online Tracking Technology Vendors_ A Checklist f...
TrustArc11 views
"Surviving highload with Node.js", Andrii Shumada by Fwdays
"Surviving highload with Node.js", Andrii Shumada "Surviving highload with Node.js", Andrii Shumada
"Surviving highload with Node.js", Andrii Shumada
Fwdays22 views
"Running students' code in isolation. The hard way", Yurii Holiuk by Fwdays
"Running students' code in isolation. The hard way", Yurii Holiuk "Running students' code in isolation. The hard way", Yurii Holiuk
"Running students' code in isolation. The hard way", Yurii Holiuk
Fwdays17 views
HTTP headers that make your website go faster - devs.gent November 2023 by Thijs Feryn
HTTP headers that make your website go faster - devs.gent November 2023HTTP headers that make your website go faster - devs.gent November 2023
HTTP headers that make your website go faster - devs.gent November 2023
Thijs Feryn22 views

New Symfony Tips & Tricks (SymfonyCon Paris 2015)

  • 1. SYMFONY TIPS & TRICKS SymfonyCon Paris - December 4, 2015 Javier Eguiluz NEW
  • 2. License of this presentation creativecommons.org/licenses/by-nc-sa/3.0
  • 4. Javier Eguiluz evangelist and trainer at
  • 6. What we'll talk about New features Tips & tricks Rarely used options
  • 10. Thanks to my awesome co-workers for sharing their knowledge! Grégoire Nicolas Hugo SarahTugdual JérémyJérémy Romain Julien FX Charles
  • 11. And thanks to the amazing Symfony Community too Christophe Benjamin Tobias BernhardChristian JordiKevin Jakub Ryan Lukas Wouter
  • 12. Agenda Doctrine Composer Twig Config Monolog Security Tests Services Console
  • 14. Accessing request parameters use SensioBundleFrameworkExtraBundleConfigurationRoute; use SymfonyBundleFrameworkBundleControllerController; use SymfonyComponentHttpFoundationRequest; /** @Route("/blog") */ class BlogController extends Controller { /** @Route("/", name="blog_index") */ public function indexAction() { $value1 = $request->query->get('parameter1'); $value2 = $request->request->get('parameter2'); // ... } }
  • 15. Accessing request parameters use SensioBundleFrameworkExtraBundleConfigurationRoute; use SymfonyBundleFrameworkBundleControllerController; use SymfonyComponentHttpFoundationRequest; /** @Route("/blog") */ class BlogController extends Controller { /** @Route("/", name="blog_index") */ public function indexAction() { $value1 = $request->query->get('parameter1'); $value2 = $request->request->get('parameter2'); // ... } } everybody uses the get() method, but there are other useful methods
  • 16. ParameterBag defines some useful methods // very useful methods $pageNumber = $request->query->getInt('page'); $isPublished = $request->query->getBoolean('published');
  • 17. ParameterBag defines some useful methods // very useful methods $pageNumber = $request->query->getInt('page'); $isPublished = $request->query->getBoolean('published'); // 'ABC+12.34DEF' -> 'ABCDEF' $request->query->getAlpha('parameter'); // 'ABC+12.34DEF' -> 'ABC1234DEF' $request->query->getAlnum('parameter'); // 'ABC+12.34DEF' -> '1234' $request->query->getDigits('parameter');
  • 19. Naming strategies I learned this from Ruud Bijnen More information http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/reference/ namingstrategy.html
  • 20. Setting custom table/properties names namespace AppBundleEntity; use DoctrineORMMapping as ORM; /** @ORMTable(name="api_users") */ class ApiUsers { /** @ORMColumn(type="string", name="api_token") */ private $apiToken; /** @ORMColumn(type="datetime", name="created_at") */ private $createdAt; }
  • 21. Setting custom table/properties names namespace AppBundleEntity; use DoctrineORMMapping as ORM; /** @ORMTable(name="api_users") */ class ApiUsers { /** @ORMColumn(type="string", name="api_token") */ private $apiToken; /** @ORMColumn(type="datetime", name="created_at") */ private $createdAt; }
  • 22. Setting custom table/properties names namespace AppBundleEntity; use DoctrineORMMapping as ORM; /** @ORMTable(name="api_users") */ class ApiUsers { /** @ORMColumn(type="string", name="api_token") */ private $apiToken; /** @ORMColumn(type="datetime", name="created_at") */ private $createdAt; } ApiUsers api_users apiToken api_token createdAt created_at Entity Database
  • 23. Using a built-in Doctrine naming strategy namespace AppBundleEntity; use DoctrineORMMapping as ORM; /** @ORMTable */ class ApiUsers { /** @ORMColumn(type="string") */ private $apiToken; /** @ORMColumn(type="datetime") */ private $createdAt; } # app/config/config.yml doctrine: dbal: # ... orm: naming_strategy: doctrine.orm.naming_strategy.underscore this is automatically translated into api_token
  • 24. Defining a custom naming strategy namespace DoctrineORMMapping; interface NamingStrategy { function classToTableName($className); function propertyToColumnName($propertyName); function referenceColumnName(); function joinColumnName($propertyName); function joinTableName($sourceEntity, $targetEntity, $propertyName); function joinKeyColumnName($entityName, $referencedColumnName); }
  • 25. Abstract relationships I learned this from Ryan Weaver More information http://symfony.com/doc/current/cookbook/doctrine/resolve_target_entity.html
  • 26. Most entity relations are well defined Invoice entity User entity ManyToOne
  • 27. What if the target entity can change? Invoice entity User entityManyToOne Company entity
  • 28. Our needs • Create a generic InvoiceBundle able to work both with User and Company entities. • No code change or special configuration is needed for the bundle.
  • 29. 1. Define an abstract "subject" interface namespace AcmeInvoiceBundleModel; interface InvoiceSubjectInterface { // Add here your custom methods // ... }
  • 30. 2. Make entities implement this interface use DoctrineORMMapping as ORM; use AcmeInvoiceBundleModelInvoiceSubjectInterface; /** @ORMEntity */ class User implements InvoiceSubjectInterface { // ... } /** @ORMEntity */ class Company implements InvoiceSubjectInterface { // ... } different applications
  • 31. 3. Configure the entity association namespace AcmeInvoiceBundleEntity; use DoctrineORMMapping AS ORM; use AcmeInvoiceBundleModelInvoiceSubjectInterface; /** @ORMEntity */ class Invoice { /** * @ORMManyToOne( targetEntity="AcmeInvoiceBundleModelInvoiceSubjectInterface") */ protected $subject; }
  • 32. 3. Configure the entity association namespace AcmeInvoiceBundleEntity; use DoctrineORMMapping AS ORM; use AcmeInvoiceBundleModelInvoiceSubjectInterface; /** @ORMEntity */ class Invoice { /** * @ORMManyToOne( targetEntity="AcmeInvoiceBundleModelInvoiceSubjectInterface") */ protected $subject; } abstract target entity
  • 33. 4. Define the target entity (at each application) # app/config/config.yml doctrine: # ... orm: # ... resolve_target_entities: AcmeInvoiceBundleModelInvoiceSubjectInterface: AcmeAppBundleEntityCustomer this is where magic happens: dynamic entity resolution at runtime
  • 34. Improved WHERE … IN I learned this from More information http://doctrine-orm.readthedocs.org/projects/doctrine-orm/en/latest/changelog/
 migration_2_5.html#query-api-where-in-query-using-a-collection-as-parameter Marco Pivetta
  • 35. Common code when using collections $categories = ... $categoryIds = array(); foreach ($categories as $category) { $categoryIds[] = $category->getId(); } $queryBuilder = $this ->where('model.category IN (:category_ids)') ->setParameter('category_ids', $categoryIds) ;
  • 36. Common code when using collections $categories = ... $categoryIds = array(); foreach ($categories as $category) { $categoryIds[] = $category->getId(); } $queryBuilder = $this ->where('model.category IN (:category_ids)') ->setParameter('category_ids', $categoryIds) ; transform the ArrayCollection into an array of IDs
  • 37. In Doctrine 2.5 this is no longer needed $categories = ... $categoryIds = array(); foreach ($categories as $category) { $categoryIds[] = $category->getId(); } $queryBuilder = $this ->where('model.category IN (:categories)') ->setParameter('categories', $categories) ; WHERE..IN supports the use of ArrayCollection
  • 38. Default table options I learned this from More information https://github.com/doctrine/DoctrineBundle/pull/420Darien Hager
  • 39. Define the default table options # app/config/config.yml doctrine: dbal: default_table_options: charset: utf8mb4 collate: utf8mb4_unicode_ci engine: InnoDB DoctrineBundle 1.6NEW
  • 40. Define the default table options # app/config/config.yml doctrine: dbal: default_table_options: charset: utf8mb4 collate: utf8mb4_unicode_ci engine: InnoDB DoctrineBundle 1.6NEW required to support emojis
  • 41. Savepoints I learned this from More information https://github.com/doctrine/DoctrineBundle/pull/451 http://doctrine.readthedocs.org/en/latest/en/manual/transactions.html Christopher Davis
  • 42. Normal Doctrine transaction try { $conn->beginTransaction(); // do something ... $conn->commit(); } catch(Exception $e) { $conn->rollback(); }
  • 43. Doctrine transaction with save points try { $conn->beginTransaction('create_user'); // do something ... $conn->commit('create_user'); } catch(Exception $e) { $conn->rollback('create_user'); } Give a name to a transaction to create a "save point"
  • 44. Nesting Doctrine transactions try { $conn->beginTransaction(); // do something ... $conn->beginTransaction('create_user'); try { // do something ... $conn->commit('create_user'); } catch(Exception $e) { $conn->rollback('create_user'); } $conn->commit(); } catch(Exception $e) { $conn->rollback(); } Nested transaction with a save point
  • 45. Adding "save points" automatically # app/config/config.yml doctrine: dbal: use_savepoints: true DoctrineBundle 1.6NEW
  • 46. Adding "save points" automatically # app/config/config.yml doctrine: dbal: use_savepoints: true protected function _getNestedTransactionSavePointName() { return 'DOCTRINE2_SAVEPOINT_'.$this->_transactionNestingLevel; } DoctrineDBALConnection DoctrineBundle 1.6NEW
  • 48. Optional things I learned this from Hugo Hamon Christophe Coevoet Grégoire Pineau
  • 49. The "action" suffix is optional for annotations class DefaultController extends Controller { /** * @Route("/", name="homepage") */ public function index(Request $request) { // ... } } no need to call this method indexAction()
  • 50. The "action" suffix is optional for annotations class DefaultController extends Controller { /** * @Route("/", name="homepage") */ public function index(Request $request) { // ... } } no need to call this method indexAction() this only works when using routing annotations
  • 51. The "action" suffix is optional for annotations class DefaultController extends Controller { /** * @Route("/", name="homepage") */ public function index(Request $request) { // ... } } no need to call this method indexAction() this only works when using routing annotations best used when the controller class only contains action methods
  • 52. The "method" param is optional for listeners # app/config/services.yml services: app.exception_listener: class: AppBundleEventListenerExceptionListener tags: - { name: kernel.event_listener, event: kernel.exception, method: 'onKernelException' } no need to define the method parameter
  • 53. The "method" param is optional for listeners # app/config/services.yml services: app.exception_listener: class: AppBundleEventListenerExceptionListener tags: - { name: kernel.event_listener, event: kernel.exception, method: 'onKernelException' } no need to define the method parameter on + camelCased event name
  • 54. The is_granted() check in error pages {# app/Resources/TwigBundle/views/Exception/error.html.twig #} {% if app.user and is_granted('ROLE_ADMIN') %} ... {% endif %} Symfony 2.7 and previous need this check to avoid issues in pages not covered by a firewall.
  • 55. The is_granted() check in error pages {# app/Resources/TwigBundle/views/Exception/error.html.twig #} {% if app.user and is_granted('ROLE_ADMIN') %} ... {% endif %} Symfony 2.7 and previous need this check to avoid issues in pages not covered by a firewall. {% if is_granted('ROLE_ADMIN') %} ... {% endif %} Symfony 2.8 no longer requires this check
  • 57. Improve class loading performance I learned this from More information https://github.com/symfony/symfony/pull/16655Tobias Schultze
  • 58. This is what we said during SymfonyCon 2014 Don't load test classes in production { "autoload": { "psr-4": { "MyLibrary": "src/" } }, "autoload-dev": { "psr-4": { "MyLibraryTests": "tests/" } } }
  • 59. New "exclude-from-classmap" option // composer.json { "autoload": { "exclude-from-classmap": ["/Tests/", "/test/", "/tests/"] } } These classes are excluded from the optimized autoloader
  • 60. New "exclude-from-classmap" option // composer.json { "autoload": { "exclude-from-classmap": ["/Tests/", "/test/", "/tests/"] } } $ composer dump-autoload --optimize These classes are excluded from the optimized autoloader
  • 61. New "exclude-from-classmap" option // composer.json { "autoload": { "exclude-from-classmap": ["**/Tests/", "/test/*"] } } ** matches anything * matches anything except /
  • 63. Twig
  • 64. Deprecated filters I learned this from More information http://twig.sensiolabs.org/doc/advanced.html#deprecated-filtersFabien Potencier
  • 65. Applications evolve in time… class AppExtension extends Twig_Extension { public function getFilters() { return array( new Twig_SimpleFilter('old_filter', ...), new Twig_SimpleFilter('new_filter', ...), ); } } How can you deprecate "old_filter" without breaking the application?
  • 66. Filters can be deprecated class AppExtension extends Twig_Extension { public function getFilters() { return array( new Twig_SimpleFilter('old_filter', ..., array( 'deprecated' => true, 'alternative' => 'new_filter', )), new Twig_SimpleFilter('new_filter', ...), ); } } Twig 1.23NEW This is how you deprecate filters and alert developers without breaking things
  • 67. Check if some block exists I learned this from More information https://github.com/twigphp/Twig/pull/1831Martin Hasoň
  • 68. Avoid missing blocks {% if 'title' is block %} <title>{{ block('title') }}<title> {% endif %} Twig 1.2XNEW not merged yet
  • 69. Variadic filters I learned this from More information https://github.com/twigphp/Twig/pull/1699Martin Hasoň
  • 70. The problem with filter arguments {{ product.photo|image(400, 150, 0.9) }}
  • 71. The problem with filter arguments {{ product.photo|image(400, 150, 0.9) }} What if I need to define more arguments?
  • 72. The problem with filter arguments {{ product.photo|image(400, 150, 0.9) }} {{ product.photo|image( width = 400, height = 150, opacity = 0.9 ) }} What if I need to define more arguments? this is a valid solution for Twig, but the underlying PHP code is still very complex
  • 73. Defining a filter with lots of arguments <?php $filter = new Twig_SimpleFilter('image', function ( $path, $width, $height, $opacity ) { $path = ... $width = ... $height = ... $opacity = ... });
  • 74. Defining a variadic filter $filter = new Twig_SimpleFilter('image', function ( $path, $options = array() ) { // ... }, array('is_variadic' => true));
  • 75. Defining a variadic filter $filter = new Twig_SimpleFilter('image', function ( $path, $options = array() ) { // ... }, array('is_variadic' => true)); a single variadic parameter holds any number of passed parameters (unlimited)
  • 76. Getting the values passed $filter = new Twig_SimpleFilter('image', function ( $path, $options = array() ) { $path = ... $width = $options['width']; $height = $options['height']; $opacity = $options['opacity']; }, array('is_variadic' => true));
  • 77. Template source code I learned this from More information https://github.com/twigphp/Twig/pull/1813 https://github.com/twigphp/Twig/pull/1807 Nicolas Grekas
  • 78. The Template class added a new method abstract class Twig_Template implements Twig_TemplateInterface { // ... public function getSource() { // ... } } Twig 1.22NEW
  • 79. How to display the source code of the template {% set template = _self %} {{ template.source|e }}
  • 80. How to display the source code of the template {% set template = _self %} {{ template.source|e }} having the source code allows for example to test if the template is backwards/forwards compatible
  • 81. The (dirty) secret of the getSource() method /* default/index.html.twig */ class __TwigTemplate_c917e55e0a4ad9b0ed28003c15c48de756d875a69e121aca97d5a53e84eaef4f extends Twig_Template { public function __construct(Twig_Environment $env) { } protected function doDisplay(array $context, array $blocks = array()) { // ... } public function getTemplateName() { return "default/index.html.twig"; } // ... public function getDebugInfo() { return array ( 115 => 54, 109 => 53, 93 => 43, 68 => 22, 66 => 21, 57 => 15, 46 => 7, 41 => 4, 35 => 3, 11 => 1,); } } /* {% extends 'base.html.twig' %}*/ /* */ /* {% block body %}*/ /* <div id="wrapper">*/ /* <div id="container">*/ /* <div id="welcome">*/ /* <h1><span>Welcome to</span> Symfony {{ constant('SymfonyComponentHttpKernelKernel::VERSION') }}</h1>*/ /* </div>*/ /* ... */ Compiled Twig templates include its full source code at the bottom
  • 83. Email delivery whitelist I learned this from More information https://github.com/symfony/symfony-docs/pull/4924 https://github.com/symfony/symfony-docs/pull/4925 Terje Bråten E. Weimann
  • 84. In dev environment, redirect all emails # app/config/config_dev.yml swiftmailer: delivery_address: dev@example.com
  • 85. Whitelist some emails in dev environment # app/config/config_dev.yml swiftmailer: delivery_address: dev@example.com delivery_whitelist: - "/notifications@example.com$/" - "/^admin@*$/""
  • 86. Whitelist some emails in dev environment # app/config/config_dev.yml swiftmailer: delivery_address: dev@example.com delivery_whitelist: - "/notifications@example.com$/" - "/^admin@*$/"" email addresses that match the regular expression will be sent (even in dev environment)
  • 88. Environment variables I learned this from More information http://github.com/symfony/symfony/pull/16403Fabien Potencier
  • 89. Setting ENV variables via the web server <VirtualHost *:80> ServerName Symfony DocumentRoot "/path/to/symfony_2_app/web" DirectoryIndex index.php index.html SetEnv SYMFONY__DATABASE_USER user SetEnv SYMFONY__DATABASE_PASSWORD secret <Directory "/path/to/symfony_2_app/web"> AllowOverride All Allow from All </Directory> </VirtualHost> same as setting database_user and database_password in parameters.yml
  • 90. ENV variables are dumped into the container // app/cache/dev/appDevDebugProjectContainer.xml <container> <parameters> <parameter key="database_user">root</parameter> <parameter key="database_password"> __secret__ </parameter> <!-- ... --> </parameters> </container> // app/cache/prod/appProdProjectContainer.php protected function getDefaultParameters() { return array( // ... 'database_user' => 'root', 'database_password' => __secret__, ); } DEV environment PROD environment
  • 91. ENV variables are dumped into the container // app/cache/dev/appDevDebugProjectContainer.xml <container> <parameters> <parameter key="database_user">root</parameter> <parameter key="database_password"> __secret__ </parameter> <!-- ... --> </parameters> </container> // app/cache/prod/appProdProjectContainer.php protected function getDefaultParameters() { return array( // ... 'database_user' => 'root', 'database_password' => __secret__, ); } DEV environment PROD environment You can see the password.
  • 92. ENV variables are dumped into the container // app/cache/dev/appDevDebugProjectContainer.xml <container> <parameters> <parameter key="database_user">root</parameter> <parameter key="database_password"> __secret__ </parameter> <!-- ... --> </parameters> </container> // app/cache/prod/appProdProjectContainer.php protected function getDefaultParameters() { return array( // ... 'database_user' => 'root', 'database_password' => __secret__, ); } DEV environment PROD environment You can see the password. It's static (if ENV variable changes, app breaks)
  • 93. This is what you want… // app/cache/prod/appProdProjectContainer.php protected function getDefaultParameters() { return array( // ... 'database_user' => 'root', 'database_password' => getenv("SYMFONY__DATABASE_PASSWORD"); ); }
  • 94. This is what you want… // app/cache/prod/appProdProjectContainer.php protected function getDefaultParameters() { return array( // ... 'database_user' => 'root', 'database_password' => getenv("SYMFONY__DATABASE_PASSWORD"); ); } … but we failed at implementing this feature.
  • 98. 1. Create a "credentials" file in the prod server # /etc/credentials/example.com.yml parameters: database_user: ... database_password: ... mailer_user: ... mailer_password: ... secret: ... github_client_id: ... github_client_secret: ...
  • 99. 2. Load the credentials file in the application # app/config/config.yml imports: - { resource: parameters.yml } - { resource: services.yml } - { resource: security.yml } - { resource: admin.yml } - { resource: '/etc/credentials/example.com.yml', ignore_errors: true } # ...
  • 100. 3. In "dev" machine • Credentials file doesn't exist but no error is triggered (because of ignore_errors). • App uses the regular parameters.yml file.
  • 101. 4. In "prod" machine • Credentials file overrides parameters.yml • The right parameters are available anywhere (e.g. console commands) • Developers can't see the production configuration options. • Requires discipline to add/remove options.
  • 104. Creating a custom channel is painless # app/config/config.yml monolog: channels: ["marketing"]
  • 105. Creating a custom channel is painless # app/config/config.yml monolog: channels: ["marketing"] $this->get('monolog.logger.marketing')->info('......');
  • 106. Creating a custom channel is painless # app/config/config.yml monolog: channels: ["marketing"] $this->get('monolog.logger.marketing')->info('......'); # app/logs/dev.log [2015-11-30 17:44:19] marketing.INFO: ...... [] []
  • 107. Creating a custom channel is painless # app/config/config.yml monolog: channels: ["marketing"] now you can process these messages apart from the rest of logs (e.g. save them in a different file) $this->get('monolog.logger.marketing')->info('......'); # app/logs/dev.log [2015-11-30 17:44:19] marketing.INFO: ...... [] []
  • 108. The channel name can be a parameter # app/config/config.yml parameters: app_channel: 'marketing' monolog: channels: [%app_channel%] services: app_logger: class: ... tags: [{ name: monolog.logger, channel: %app_channel% }] MonologBundle 2.8NEW
  • 109. Custom formatters Grégoire Pineau I learned this from More information http://symfony.com/doc/current/cookbook/logging/monolog.html#changing-
 the-formatter
  • 110. This is what we said during SymfonyCon 2014 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 }
  • 111. Creating simple custom formatters is painless # app/config/config.yml services: app.log.formatter: class: 'MonologFormatterLineFormatter' public: false arguments: - "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n
 %%context%%nn" - 'H:i:s' - false
  • 112. Creating simple custom formatters is painless # app/config/config.yml services: app.log.formatter: class: 'MonologFormatterLineFormatter' public: false arguments: - "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n
 %%context%%nn" - 'H:i:s' - false the format of each log line
  • 113. Creating simple custom formatters is painless # app/config/config.yml services: app.log.formatter: class: 'MonologFormatterLineFormatter' public: false arguments: - "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n
 %%context%%nn" - 'H:i:s' - false the format of each log line the format of %datetime% placeholder
  • 114. Creating simple custom formatters is painless # app/config/config.yml services: app.log.formatter: class: 'MonologFormatterLineFormatter' public: false arguments: - "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n
 %%context%%nn" - 'H:i:s' - false the format of each log line the format of %datetime% placeholder
  • 115. Creating simple custom formatters is painless # app/config/config.yml services: app.log.formatter: class: 'MonologFormatterLineFormatter' public: false arguments: - "%%level_name%% [%%datetime%%] [%%channel%%] %%message%%n
 %%context%%nn" - 'H:i:s' - false the format of each log line the format of %datetime% placeholder ignore n inside the log messages?
  • 116. Enable the custom log formatter # app/config/config_dev.yml monolog: handlers: main: type: stream path: "%kernel.logs_dir%/%kernel.environment%.log" level: debug formatter: app.log.formatter add the formatter option
  • 117. Custom log format Default log format INFO [18:38:21] [php] The SymfonyComponent DependencyInjectionReference::isStrict method is deprecated since version 2.8 and will be removed in 3.0. {"type":16384,"file":"myproject/vendor/symfony/symfony/ src/Symfony/Component/DependencyInjection/ Reference.php","line":73,"level":28928} INFO [18:38:21] [request] Matched route "homepage". {"route_parameters":{"_controller":"AppBundle Controller DefaultController::indexAction","_route":"homepage"},"r equest_uri":"http://127.0.0.1:8000/"} INFO [18:38:21] [security] Populated the TokenStorage with an anonymous Token. [] DEBUG [18:38:21] [event] Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListener DebugHandlersListener::configure". [] DEBUG [18:38:21] [event] Notified event "kernel.request" to listener "SymfonyComponentHttpKernelEventListener ProfilerListener::onKernelRequest". [2015-11-30 18:38:21] php.INFO: The SymfonyComponent DependencyInjectionReference::isStrict method is deprecated since version 2.8 and will be removed in 3.0. {"type":16384,"file":"myproject/vendor/symfony/symfony/ src/Symfony/Component/DependencyInjection/ Reference.php","line":73,"level":28928} [] [2015-11-30 18:38:21] request.INFO: Matched route "homepage". {"route_parameters": {"_controller":"AppBundleController DefaultController::indexAction","_route":"homepage"},"r equest_uri":"http://127.0.0.1:8000/"} [] [2015-11-30 18:38:21] security.INFO: Populated the TokenStorage with an anonymous Token. [] [] [2015-11-30 18:38:21] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponent HttpKernelEventListener DebugHandlersListener::configure". [] [] [2015-11-30 18:38:21] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponent HttpKernelEventListener ProfilerListener::onKernelRequest". [] [] [2015-11-30 18:38:21] event.DEBUG: Notified event "kernel.request" to listener "SymfonyComponent HttpKernelEventListenerDumpListener::configure". [] []
  • 118. Monolog "plugins" Jordi Boggiano I learned this from More information http://github.com/Seldaek/monolog/wiki/Third-Party-Packages
  • 120. bramus/monolog-colored-line-formatter • Shows log messages in color according to their level. • It uses ANSI Escape Sequences.
  • 121. Colored logs in action if your editor or console supports ANSI escape sequences
  • 122. Colored logs in action if your editor or console doesn't support ANSI escape sequences
  • 124. Christophe Coevoet Tweaking the web debug toolbar I learned this from More information http://symfony.com/doc/current/cookbook/profiler/data_collector.html
  • 125. The new web debug toolbar the new toolbar is less cluttered because it hides panels that don't contain information
  • 126. Reordering toolbar panels # app/config/services.yml services: data_collector.request: class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector tags: - { name: "data_collector", template: "@WebProfiler/Collector/request.html.twig", id: "request", priority: "-512" } data_collector.security: class: 'SymfonyBundleSecurityBundleDataCollectorSecurityDataCollector' arguments: ['@security.token_storage', '@security.role_hierarchy', '@security.logout_url_generator'] tags: - { name: "data_collector", template: "@Security/Collector/security.html.twig", id: "security", priority: "512" }
  • 127. Reordering toolbar panels # app/config/services.yml services: data_collector.request: class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector tags: - { name: "data_collector", template: "@WebProfiler/Collector/request.html.twig", id: "request", priority: "-512" } data_collector.security: class: 'SymfonyBundleSecurityBundleDataCollectorSecurityDataCollector' arguments: ['@security.token_storage', '@security.role_hierarchy', '@security.logout_url_generator'] tags: - { name: "data_collector", template: "@Security/Collector/security.html.twig", id: "security", priority: "512" }
  • 128. Built-in panels priorities Panel Priority request 335 time 330 memory 325 ajax 315 form 310 exception 305 logger 300 events 290 Panel Priority router 285 translation 275 security 270 twig 257 swiftmailer 245 dump 240 config -255
  • 129. Adding your panels between the built-in panels services: app.data_collector: # ... tags: - { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY)
  • 130. Adding your panels between the built-in panels services: app.data_collector: # ... tags: - { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY) tags: - { name: "data_collector", template: "...", id: "...", priority: "273" }
  • 131. Adding your panels between the built-in panels services: app.data_collector: # ... tags: - { name: "data_collector", template: "...", id: "..." } (NO EXPLICIT PRIORITY) tags: - { name: "data_collector", template: "...", id: "...", priority: "273" } tags: - { name: "data_collector", template: "...", id: "...", priority: "320" }
  • 132. Remove panels services: data_collector.translation: class: SymfonyComponentTranslationDataCollectorTranslationDataCollector arguments: ['@translator.data_collector'] tags: - { name: "data_collector" } data_collector.request: class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector tags: - { name: "data_collector" } to remove a panel, define its service without any argument in its data_collector tag
  • 133. Remove panels services: data_collector.translation: class: SymfonyComponentTranslationDataCollectorTranslationDataCollector arguments: ['@translator.data_collector'] tags: - { name: "data_collector" } data_collector.request: class: SymfonyComponentHttpKernelDataCollectorRequestDataCollector tags: - { name: "data_collector" } to remove a panel, define its service without any argument in its data_collector tag remove the Translation panel remove the Request panel
  • 135. Success and failure handlers I learned this from More information https://github.com/symfony/symfony-docs/issues/4258Fabien Potencier
  • 136. Success/failure handlers are available since Symfony 2.0 (2011) …
  • 137. Success/failure handlers are available since Symfony 2.0 (2011) … … but lots of developers don't use them because they are not documented.
  • 138. The security.interactive_login event services: login_listener: class: AppBundleListenerLoginListener arguments: ['@security.token_storage', '@doctrine'] tags: - { name: 'kernel.event_listener', event: 'security.interactive_login' } the most common event to "do things" after the user successfully logs in
  • 139. Defining a success handler namespace AppBundleSecurity; use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface; class LoginHandler implements AuthenticationSuccessHandlerInterface { public function onAuthenticationSuccess(Request $request, TokenInterface $token) { // do something ... return $this->redirect('...'); return new Response('...'); } }
  • 140. Defining a success handler namespace AppBundleSecurity; use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface; class LoginHandler implements AuthenticationSuccessHandlerInterface { public function onAuthenticationSuccess(Request $request, TokenInterface $token) { // do something ... return $this->redirect('...'); return new Response('...'); } } you just need to implement this interface…
  • 141. Defining a success handler namespace AppBundleSecurity; use SymfonyComponentSecurityHttpAuthenticationAuthenticationSuccessHandlerInterface; class LoginHandler implements AuthenticationSuccessHandlerInterface { public function onAuthenticationSuccess(Request $request, TokenInterface $token) { // do something ... return $this->redirect('...'); return new Response('...'); } } you just need to implement this interface… …and return a Response instance
  • 142. Enabling the success handler # app/config/services.yml services: app.login_handler: class: AppBundleSecurityLoginHandler arguments: ... # app/config/security.yml firewalls: main: pattern: ^/ form_login: success_handler: app.login_handler
  • 143. Enabling the success handler # app/config/services.yml services: app.login_handler: class: AppBundleSecurityLoginHandler arguments: ... # app/config/security.yml firewalls: main: pattern: ^/ form_login: success_handler: app.login_handler the LoginHandler class is executed when the user logs in successfully and after the event.interactive_login
  • 144. Tests
  • 145. Meaningful data providers I learned this from More information http://www.thisprogrammingthing.com/2015/making-dataproviders-more-maintainableScott Warren
  • 146. Common data providers public function getUserData() { return array( array(true, false, false), array(true, true, false), array(true, true, true), ); } /** @dataProvider getUserData */ public function testRegistration($register, $enable, $notify) { // ... }
  • 147. When an error happens… $ phpunit -c app F.. Time: 137 ms, Memory: 12.50Mb There was 1 failure: 1) AppBundleTestsControllerDefaultControllerTest::testRegistration with data set #0 (true, false, false) very hard to understand the exact error
  • 148. Meaningful data providers public function getUserData() { return array( 'register user only' => array(true, false, false), 'register and enable' => array(true, true, false), 'register, enable and notify' => array(true, true, true), ); } /** @dataProvider getUserData */ public function testRegistration($register, $enable, $notify) { // ... } array keys are the labels of each data set
  • 149. When an error happens… $ phpunit -c app F.. Time: 137 ms, Memory: 12.50Mb There was 1 failure: 1) AppBundleTestsControllerDefaultControllerTest::testRegistration with data set "register user only" (true, false, false) meaningful error messages
  • 150. Generating data providers I learned this from More information http://php.net/manual/en/language.generators.overview.phpTugdual Saunier
  • 151. Common data providers public function dataProvider() { public function providerBasicDrivers() { return array( array('doctrine.orm.cache.apc.class', array('type' => 'apc')), array('doctrine.orm.cache.array.class', array('type' => 'array')), array('doctrine.orm.cache.xcache.class', array('type' => 'xcache')), array('doctrine.orm.cache.wincache.class', array('type' => 'wincache')), array('doctrine.orm.cache.zenddata.class', array('type' => 'zenddata')), ); } }
  • 152. Data providers using PHP generators public function dataProvider() { public function providerBasicDrivers() { yield ['doctrine.orm.cache.apc.class', ['type' => 'apc']]; yield ['doctrine.orm.cache.array.class', ['type' => 'array']]; yield ['doctrine.orm.cache.xcache.class', ['type' => 'xcache']]; yield ['doctrine.orm.cache.wincache.class', ['type' => 'wincache']]; yield ['doctrine.orm.cache.zenddata.class', ['type' => 'zenddata']]; } } PHP 5.5NEW
  • 153. Data providers using PHP generators public function dataProvider() { public function providerBasicDrivers() { yield ['doctrine.orm.cache.apc.class', ['type' => 'apc']]; yield ['doctrine.orm.cache.array.class', ['type' => 'array']]; yield ['doctrine.orm.cache.xcache.class', ['type' => 'xcache']]; yield ['doctrine.orm.cache.wincache.class', ['type' => 'wincache']]; yield ['doctrine.orm.cache.zenddata.class', ['type' => 'zenddata']]; } } PHP 5.5NEW it reduces memory consumption
  • 154. Better setUp and tearDown I learned this from More information https://phpunit.de/manual/current/en/appendixes.annotations.html #appendixes.annotations.before Sebastian Bergmann
  • 155. Common use of setUp( ) method in tests class IntegrationTest extends PHPUnit_Framework_TestCase { private $rootDir; private $fs; public function setUp() { $this->rootDir = realpath(__DIR__.'/../../../../'); $this->fs = new Filesystem(); if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { throw new RuntimeException("..."); } } }
  • 156. Common use of setUp( ) method in tests class IntegrationTest extends PHPUnit_Framework_TestCase { private $rootDir; private $fs; public function setUp() { $this->rootDir = realpath(__DIR__.'/../../../../'); $this->fs = new Filesystem(); if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { throw new RuntimeException("..."); } } } public function setUp() public function tearDown() public static function setUpBeforeClass() public static function tearDownAfterClass()
  • 157. Better alternative: @before annotation class IntegrationTest extends PHPUnit_Framework_TestCase { private $rootDir; private $fs; /** @before */ public function initialize() { $this->rootDir = realpath(__DIR__.'/../../../../'); $this->fs = new Filesystem(); } /** @before */ public function checkPharFile() { if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { throw new RuntimeException("..."); } } }
  • 158. Better alternative: @before annotation class IntegrationTest extends PHPUnit_Framework_TestCase { private $rootDir; private $fs; /** @before */ public function initialize() { $this->rootDir = realpath(__DIR__.'/../../../../'); $this->fs = new Filesystem(); } /** @before */ public function checkPharFile() { if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { throw new RuntimeException("..."); } } } you can define any number of @before methods
  • 159. Better alternative: @before annotation class IntegrationTest extends PHPUnit_Framework_TestCase { private $rootDir; private $fs; /** @before */ public function initialize() { $this->rootDir = realpath(__DIR__.'/../../../../'); $this->fs = new Filesystem(); } /** @before */ public function checkPharFile() { if (!$this->fs->exists($this->rootDir.'/symfony.phar')) { throw new RuntimeException("..."); } } } you can define any number of @before methods order matters (first method is executed first)
  • 160. Alternatives to setUp and tearDown class IntegrationTest extends PHPUnit_Framework_TestCase { /** @before */ public function initialize() { } /** @after */ public function clean() { } /** @beforeClass */ public static function initialize() { } /** @afterClass */ public static function initialize() { } }
  • 161. Avoid risky tests I learned this from More information https://phpunit.de/manual/current/en/risky-tests.htmlSebastian Bergmann
  • 162. Common PHPUnit configuration <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php" > ... </phpunit>
  • 163. Common PHPUnit configuration <?xml version="1.0" encoding="UTF-8"?> <phpunit xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://schema.phpunit.de/4.1/phpunit.xsd" backupGlobals="false" colors="true" bootstrap="vendor/autoload.php" beStrictAboutTestsThatDoNotTestAnything="true" checkForUnintentionallyCoveredCode="true" beStrictAboutOutputDuringTests="true" beStrictAboutTestSize="true" beStrictAboutChangesToGlobalState="true" > ... </phpunit> this strict configuration will force you to create better tests
  • 165. Nicolas Grekas Deprecation stack traces I learned this from More information https://github.com/symfony/symfony/pull/16709
  • 166. Use the PHPUnit Bridge to detect deprecations $ composer require --dev symfony/phpunit-bridge $ phpunit -c app PHPUnit by Sebastian Bergmann and contributors. ................. Time: 3.7 seconds, Memory: 75.00Mb OK (17 tests, 21 assertions) Remaining deprecation notices (368) OK (17 tests, 21 assertions) Remaining deprecation notices (368)
  • 167. When a deprecation occurs… This information is useful, but incomplete. Often you need the stack trace of the code that triggered the deprecation.
  • 168. On-demand deprecation stack traces // BEFORE $ phpunit -c app // AFTER $ SYMFONY_DEPRECATIONS_HELPER=/Blog::index/ phpunit -c app A regular expression that is matched against className::methodName
  • 169. On-demand deprecation stack traces $ SYMFONY_DEPRECATIONS_HELPER=/.*/ phpunit -c app The class "SymfonyBundleAsseticBundleConfigAsseticResource" is performing resource
 checking through ResourceInterface::isFresh(), which is deprecated since 2.8 and will be removed in 3.0 Stack trace:
 #0 vendor/symfony/symfony/src/Symfony/Component/Config/Resource/BCResourceInterfaceChecker.php(32):
 trigger_error()
 #1 app/bootstrap.php.cache(3061): SymfonyComponentConfigResourceBCResourceInterfaceChecker->isFresh()
 #2 vendor/symfony/symfony/src/Symfony/Component/Config/ResourceCheckerConfigCacheFactory.php(45):
 SymfonyComponentConfigResourceCheckerConfigCache->isFresh()
 #3 vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php(302): SymfonyComponentConfigResourceCheckerConfigCacheFactory->cache()
 #4 vendor/symfony/symfony/src/Symfony/Component/Routing/Router.php(250): SymfonyComponentRoutingRouter->getMatcher()
 #5 vendor/symfony/symfony/src/Symfony/Component/HttpKernel/EventListener/RouterListener.php(154): SymfonyComponentRoutingRouter->matchRequest()
 #6 [internal function]: SymfonyComponentHttpKernelEventListenerRouterListener->onKernelRequest()
 #7 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php(61): call_user_func()
 #8 [internal function]: SymfonyComponentEventDispatcherDebugWrappedListener->__invoke()
 #9 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(181): call_user_func()
 #10 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/EventDispatcher.php(46): SymfonyComponentEventDispatcherEventDispatcher->doDispatch()
 #11 vendor/symfony/symfony/src/Symfony/Component/EventDispatcher/Debug/TraceableEventDispatcher.php(132): SymfonyComponentEventDispatcherEventDispatcher- >dispatch()
 #12 app/bootstrap.php.cache(3178): SymfonyComponentEventDispatcherDebugTraceableEventDispatcher->dispatch()
 #13 app/bootstrap.php.cache(3151): SymfonyComponentHttpKernelHttpKernel->handleRaw()
 #14 app/bootstrap.php.cache(3302): SymfonyComponentHttpKernelHttpKernel->handle()
 #15 app/bootstrap.php.cache(2498): SymfonyComponentHttpKernelDependencyInjectionContainerAwareHttpKernel->handle()
 #16 vendor/symfony/symfony/src/Symfony/Component/HttpKernel/Client.php(79): SymfonyComponentHttpKernelKernel->handle()
 #17 vendor/symfony/symfony/src/Symfony/Bundle/FrameworkBundle/Client.php(131): SymfonyComponentHttpKernelClient->doRequest()
 #18 vendor/symfony/symfony/src/Symfony/Component/BrowserKit/Client.php(317): SymfonyBundleFrameworkBundleClient->doRequest()
 #19 src/AppBundle/Tests/Controller/Admin/BlogControllerTest.php(42): SymfonyComponentBrowserKitClient->request()
 #20 [internal function]: AppBundleTestsControllerAdminBlogControllerTest->testRegularUsersCannotAccessToTheBackend()
 #21 {main} It stops at the first deprecation and shows its stack trace
  • 171. Smoke testing for services Benjamin Eberlei I learned this from More information http://www.whitewashing.de/2015/06/12/the_containertest.html
  • 172. «Smoke Tests are an early warning for when things go wrong»
  • 173. Automatic "smoke testing" for services public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { $service = $client->getContainer()->get($serviceId); $this->assertNotNull($service); } }
  • 174. Automatic "smoke testing" for services public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { $service = $client->getContainer()->get($serviceId); $this->assertNotNull($service); } } get all services defined by the container
  • 175. Automatic "smoke testing" for services public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { $service = $client->getContainer()->get($serviceId); $this->assertNotNull($service); } } get all services defined by the container try to instantiate all of them to look for errors
  • 176. Automatic "smoke testing" for services public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { try { $service = $client->getContainer()->get($serviceId); $this->assertNotNull($service); } catch(InactiveScopeException $e) { } } } you may need this try … catch to avoid issues with request related services
  • 177. Manual "smoke testing" for important services public static function dataServices() { return [ ['app.datetimepicker', 'AppBundleFormTypeDateTimePickerType'], ['app.redirect_listener', 'AppBundleEventListenerRedirectToPreferredLocaleListener'], ]; } /** @dataProvider dataServices */ public function testImportantServices($id, $class) { $client = static::createClient(); $service = $client->getContainer()->get($id); $this->assertInstanceOf($class, $service); } check that the service is instantiable and that the returned class is the right one
  • 178. Services benchmark Alexandre Salomé I learned this from More information https://speakerdeck.com/alexandresalome/french-symfony2-et-performances
  • 179. Service instantiation should not take too long public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { try { $startedAt = microtime(true); $service = $client->getContainer()->get($serviceId); $elapsed = (microtime(true) - $startedAt) * 1000; $this->assertLessThan(50, $elapsed); } catch(InactiveScopeException $e) { } } }
  • 180. Service instantiation should not take too long public function testContainerServices() { $client = static::createClient(); foreach ($client->getContainer()->getServiceIds() as $serviceId) { try { $startedAt = microtime(true); $service = $client->getContainer()->get($serviceId); $elapsed = (microtime(true) - $startedAt) * 1000; $this->assertLessThan(50, $elapsed); } catch(InactiveScopeException $e) { } } } services should be created in 50ms or less
  • 181. Most expensive services in Symfony Standard Service Time (ms) cache_warmer 42.95 doctrine.orm.default_entity_manager 14.05 web_profiler.controller.router 11.88 swiftmailer.mailer.default 6.65 profiler 5.45 annotation_reader 4.53 doctrine.dbal.default_connection 4.22 form.type_extension.form.validator 4.13 routing.loader 4.02 form.type.choice 3.10
  • 183. Simpler command interactions Nikita Gusakov I learned this from More information https://github.com/symfony/swiftmailer-bundle/blob/master/Command/ NewEmailCommand.php
  • 184. Common lifecycle of a command class MyCustomCommand extends ContainerAwareCommand { protected function configure() { } protected function execute(InputInterface $input, OutputInterface $output) { } }
  • 185. Full lifecycle of a command class MyCustomCommand extends ContainerAwareCommand { protected function configure() { } protected function initialize(InputInterface $input, OutputInterface $output) { } protected function interact(InputInterface $input, OutputInterface $output) { } protected function execute(InputInterface $input, OutputInterface $output) { } } here you can ask the user for any missing option or argument
  • 186. Ask for any missing command argument class MyCommand extends ContainerAwareCommand { // ... protected function interact(InputInterface $input, OutputInterface $output) { $dialog = $this->getHelper('dialog'); foreach ($input->getArguments() as $argument => $value) { if ($value === null) { $input->setArgument($argument, $dialog->ask($output, sprintf('<question>%s</question>: ', ucfirst($argument)) )); } } } } the interact() method is usually very verbose. This is the minimal useful interact() method.
  • 189. Most commands are a mess $description = $this->formatSection( 'container', sprintf('Information for service <info>%s</info>', $options['id'])) ."n".sprintf('<comment>Service Id</comment> %s', isset($options['id']) ? $options['id'] : '-') ."n".sprintf('<comment>Class</comment> %s', get_class($service) ); $description[] = sprintf('<comment>Scope</comment> %s', $definition->getScope(false)); $description[] = sprintf('<comment>Public</comment> %s', $definition->isPublic() ? 'yes' : 'no'); $description[] = sprintf('<comment>Synthetic</comment> %s', $definition->isSynthetic() ? 'yes' : 'no'); $description[] = sprintf('<comment>Lazy</comment> %s', $definition->isLazy() ? 'yes' : 'no'); $this->writeText($description, $options);
  • 190. Most commands are a mess $description = $this->formatSection( 'container', sprintf('Information for service <info>%s</info>', $options['id'])) ."n".sprintf('<comment>Service Id</comment> %s', isset($options['id']) ? $options['id'] : '-') ."n".sprintf('<comment>Class</comment> %s', get_class($service) ); $description[] = sprintf('<comment>Scope</comment> %s', $definition->getScope(false)); $description[] = sprintf('<comment>Public</comment> %s', $definition->isPublic() ? 'yes' : 'no'); $description[] = sprintf('<comment>Synthetic</comment> %s', $definition->isSynthetic() ? 'yes' : 'no'); $description[] = sprintf('<comment>Lazy</comment> %s', $definition->isLazy() ? 'yes' : 'no'); $this->writeText($description, $options); you are mixing content with presentation, like in the good old days of HTML+CSS
  • 191. The new "Style Guide" makes built-in commands visually consistent.
  • 192. You can use it in your own commands too use SymfonyComponentConsoleStyleSymfonyStyle; class MyCustomCommand { // ... protected function execute(InputInterface $input, OutputInterface $output) { $io = new SymfonyStyle($input, $output); $io->... // ... } }
  • 193. Displaying a title (BEFORE) // alternative 1 $formatter = $this->getHelperSet()->get('formatter'); $formattedBlock = $formatter->formatBlock($title, '', true); $output->writeln($formattedBlock); // alternative 2 $title = 'Lorem Ipsum Dolor Sit Amet'; $output->writeln('<info>'.$title.'</info>'); $output->writeln(str_repeat('=', strlen($title))); // alternative 3 $output->writeln(''); $output->writeln('Add User Command Interactive Wizard'); $output->writeln('-----------------------------------');
  • 194. Displaying a title (AFTER) use SymfonyComponentConsoleStyleSymfonyStyle; $io = new SymfonyStyle($input, $output); $io->title('Lorem Ipsum Dolor Sit Amet');
  • 195. Displaying a title (AFTER) use SymfonyComponentConsoleStyleSymfonyStyle; $io = new SymfonyStyle($input, $output); $io->title('Lorem Ipsum Dolor Sit Amet');
  • 196. Displaying a table (BEFORE) $table = new Table($this->getOutput()); $table->setStyle('compact'); $table->setHeaders(['Parameter', 'Value']); $table->addRow(['...', '...']); $table->addRow(['...', '...']); $table->addRow(['...', '...']); $table->render();
  • 197. Displaying a table (AFTER) use SymfonyComponentConsoleStyleSymfonyStyle; $headers = ['Parameter', 'Value']; $rows = [ ... ]; $io = new SymfonyStyle($input, $output); $io->table($headers, $rows);
  • 198. Displaying a table (AFTER) use SymfonyComponentConsoleStyleSymfonyStyle; $headers = ['Parameter', 'Value']; $rows = [ ... ]; $io = new SymfonyStyle($input, $output); $io->table($headers, $rows);
  • 199. All the common features are covered $io = new SymfonyStyle($input, $output); $io->title(); $io->section(); $io->text(); $io->listing(); $io->table(); $io->choice(); $io->success(); $io->error(); $io->warning(); $io->ask(); $io->askHidden(); $io->confirm();
  • 200. Create consistent commands effortlesslyFull Sample Command $ php app/console command Lorem Ipsum Dolor Sit Amet ========================== // Duis aute irure dolor in reprehenderit in voluptate velit esse // cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat Name Method Scheme Host Path ----------------- ------ ------ ---- --------------------- admin_post_new ANY ANY ANY /admin/post/new admin_post_show GET ANY ANY /admin/post/{id} admin_post_edit ANY ANY ANY /admin/post/{id}/edit admin_post_delete DELETE ANY ANY /admin/post/{id} ----------------- ------ ------ ---- --------------------- ! [CAUTION] Lorem ipsum dolor sit amet, consectetur adipisicing elit, ! sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. ! Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Consectetur Adipisicing Elit Sed Do Eiusmod ------------------------------------------- * Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. * Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo. * Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Bundle namespace: > AppBundle <Enter> Bundle name [AcmeAppBundle]: > <Enter> Configuration format (yml, xml, php, annotation) [annotation]: > <Enter> Command title All types of questions Section title List Info message Table Caution admonition ! Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris. Consectetur Adipisicing Elit Sed Do Eiusmod ------------------------------------------- * Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. * Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo. * Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Bundle namespace: > AppBundle <Enter> Bundle name [AcmeAppBundle]: > <Enter> Configuration format (yml, xml, php, annotation) [annotation]: > <Enter> Do you want to enable the bundle? (yes/no) [yes]: > <Enter> Configuration format (select one) [annotation]: > yml > xml > php > annotation ! [NOTE] Duis aute irure dolor in reprehenderit in voluptate velit esse ! cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat. [OK] Lorem ipsum dolor sit amet, consectetur adipisicing elit [ERROR] Duis aute irure dolor in reprehenderit in voluptate velit esse. [WARNING] Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi. All ty Secti List All ty resul Note
  • 203. License of this presentation creativecommons.org/licenses/by-nc-sa/3.0