symfony war stories
DESCRIPTION
UK Symfony Meetup, November 2012 Original presentation: https://docs.google.com/presentation/pub?id=1PLcqTby6yqSbfWlMIDHknH852DU6DO6OAgQJOtSEdsg&start=false&loop=false&delayms=3000TRANSCRIPT
SymfonyWar Stories
SensioLabsUK
Jakub Zalas
UK Symfony Meetup
November, 2012
http://www.rgbstock.com/bigphoto/mhY2DCY
Jakub Zalas a.k.a. Kuba
○ Twitter: @jakub_zalas○ Blog: http://zalas.eu○ Github: @jakzal
○ Remembers symfony 1.0 (2007)
○ Open Source contributor○ BDD advocate○ Works at Sensio Labs UK○ ZCE & Sensio Labs
Certified Symfony Developer
Symfony War Stories
● Bundling the code
● Thinking outside of the bundle
● Being lazy
● Learning your stuff
● Keeping your playground clean
● Building the quality in
Bundling the code
http://www.rgbstock.com/bigphoto/mfXkYUq
VS
Whenever in doubt...
● Start simple and let the design emerge● Extract bundles when you need to reuse it
or you see it would improve clarity● Avoid two-way dependencies between
bundles
Thinking outside of
the bundle
http://www.rgbstock.com/bigphoto/mfBYR2S
VS
To keep your design clean...
● Treat bundles as an integration layer between PHP libraries and the Symfony framework
● In your libraries, avoid dependencies on
bundles
Being lazy
http://www.sxc.hu/photo/858871
There's a bundle for that!
Learning your stuff
http://www.sxc.hu/photo/1095604
Have you ever used?
● Event listeners and subscribers
● Form data transformers
● Twig extensions
● Profiler extensions
● DIC compiler passes
● more...
It's easy!
let's see few examples...
Event listeners and subscribers (Doctrine)
services: sensio.listener.search_indexer: class: Sensio\Listener\SearchIndexer tags: - name: doctrine.event_listener event: postPersist
namespace Sensio\Listener;
use Doctrine\ORM\Event\LifecycleEventArgs;use Sensio\Entity\Product;
class SearchIndexer{ public function postPersist(LifecycleEventArgs $args) { $entity = $args->getEntity(); $entityManager = $args->getEntityManager();
if ($entity instanceof Product) { /* Index me */ } }}
Lifecycle callbacks (Doctrine)
/** * @ORM\Entity() * @ORM\HasLifecycleCallbacks() */class User{ /** * @ORM\PrePersist */ public function updateCreatedAt() { /* ... */ }}
Event listeners and subscribers (Symfony)
services: sensio.action_listener: class: Sensio\MyBundle\EventListener\MyListener tags: - name: kernel.event_listener event: kernel.controller method: onKernelController
namespace Sensio\MyBundle\EventListener;
class MyListener{ public function onKernelController( FilterControllerEvent $event) { $controller = $event->getController(); $request = $event->getRequest();
// ... }}
Event listeners and subscribers (Forms)
$callback = function(FormEvent $event) { if (null === $event->getData()) { $formData = $event->getForm()->getData(); $event->setData($formData); }};
$builder->add('email', 'text');$builder->get('email') ->addEventListener( FormEvents::PRE_BIND, $callback );
Data transformers (Forms)
class IssueToNumberTransformer implements DataTransformerInterface { public function transform($issue) { // return issue number }
public function reverseTransform($number) { // return Issue instance }}
$field = $builder->create('issue', 'text') ->addModelTransformer(new IssueToNumberTransformer());
$builder->add($field);
Twig extensions
<!-- Instead of: -->
<div class="price"> ${{ 9800.333|number_format(2, '.', ',') }}</div>
<!-- just do: -->
<div class="price"> {{ 9800.333|price }}</div>
class SensioExtension extends \Twig_Extension{ public function getFilters() { return array( 'price' => new \Twig_Filter_Method( $this, 'priceFilter') ); }
public function getName() { return 'sensio_extension'; }
public function priceFilter($number, /* ... */ ) {}}
public function priceFilter( $number, $decimals = 0, $decPoint = '.', $sep = ','){ $price = number_format( $number, $decimals, $decPoint, $sep );
return '$'.$price;}
services: sensio.twig.acme_extension: class: Sensio\Twig\SensioExtension tags: - { name: twig.extension }
Web profiler
./app/console container:debug
./app/console container:debug twig.loader
./app/console router:debug
./app/console router:debug homepage
./app/console router:match /logout
Debugging commands
Debugging commands
Keeping your playground clean
http://www.rgbstock.com/bigphoto/mZTKnw6
Use the service container!
public function publishAction($date){ $show = $this->findShow($date); $fileName = sprintf('show-%s.json', $date->format('Y-m-d'));
while (file_exists($fileName)) { $fileName = $fileName.preg_replace('/.json$/', '-1.json'); }
$options = array( /* options here */ ); $s3 = new AmazonS3($options);
$objectOptions = array( 'body' => json_encode($show), 'acl' => $options['objectAcl']);
if (!$s3->create_object('my-bucket', $fileName, $objectOptions) { throw new \Exception('Could not save file'); }
// ...}
public function publishAction($date){ $show = $this->findShow($date);
$fileNameResolver = new FileNameResolver(); $filesystem = $this->get('gafrette_filesystem');
$publisher = new Publisher( $fileNameResolver, $filesystem );
$publisher->publish($show, $date);}
Use the service container!
public function publishAction($date){ $show = $this->findShow($date); $publisher = $this->get('sensio.publisher'); $publisher->publish($show, $date);}
Be SOLID!
/** * @ORM\Entity() */class Product{ // ...
public function createOrderReport($month) { // $this->em->getRepository() ... }
public function setEntityManager($em) { $this->em = $em; }}
class OrderReportGenerator{ private $em = null;
public function __construct(EntityManager $em) { $this->em = $em; }
public function generate(Product $product, $month) { // ... }}
Building quality in
http://www.rgbstock.com/photo/nrcyovQ/perfect
Unit tests
class ReportTest extends \PHPUnit_Framework_TestCase{ public function testThatItReturnsFilePath() { $report = new Report();
$file = $report->create('January');
$this->assertSame('/january.csv', $file); }}
... or specs
namespace spec;
class Report extends \PHPSpec2\ObjectBehavior{ function it_returns_file_path() { $this->create('January') ->shouldReturn('/january.csv'); }}
Managing expectations
Feature: Viewing recent news on the homepage As a Marketing Manager I want Visitors to see recent news on the homepage In order to get them interested in the content
Scenario: Viewing recent news Given there are 10 news items And there were 5 news items written since yesterday When I visit the homepage Then I should see 5 recent news items in the news section
Scenario: There is not enough news Given there are 10 news items But there were 3 news items written since yesterday When I visit the homepage Then I should see 3 recent news items in the news section
Thank you!