rebuilding our foundation

80
@jessicamauerhan | Madison PHP | https://joind.in/16022 Rebuilding Our Foundation How We Used Symfony To Rewrite Our Application @jessicamauerhan | Madison PHP | https://joind.in/16022

Upload: jessica-mauerhan

Post on 19-Jan-2017

178 views

Category:

Technology


0 download

TRANSCRIPT

Page 1: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rebuilding Our Foundation

How We Used Symfony To Rewrite Our Application

@jessicamauerhan | Madison PHP | https://joind.in/16022

Page 2: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

● Project History ● Problems and Goals● Why Symfony?● Rapid Development

of Quality Code● Doctrine DBAL / ORM

Topics● Automated Testing● Sonata Admin Bundle● Creating an API with

Symfony● Dependency Injection● Current Project

Status

2

Page 3: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

● Learning Management System● Content Production● E-Commerce● Business to Business (B2B)

Application Summary

3

Page 4: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Problematic History● Broken Admin Panel● No Documentation of Basic Processes● Frontend Site Worked, Progress Stalled● Complex Logic not Documented

4

Page 5: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Business Goals● Add Missing Admin Panel Functionality● Add New Features Without Breaking Existing Features● Avoid Downtime

5

Page 6: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Technical Goals● Maintainable Code● Quality Code● Documentation● Rapid Development● Easy Deployment● Zero Regressions Per Release

6

Page 7: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Why Symfony?● Community

○ Third Party Code Integration○ Blazing Trails ○ Popularity○ Support

7

Page 8: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Why Symfony?● Technology

○ Dependency Injection & Decoupling○ Unit Testing○ Functional Testing○ Behavior Testing

8

Page 9: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rapid Development of Quality Code● Version Control: Git● Development Workflow

9

Page 10: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Image sourced from Atlassian: https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflowLicensed under the Creative Commons Attribution 2.5 Australia License.

10

Page 11: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rapid Development of Quality Code● Version Control: Git● Development Workflow: Gitflow● Code Style Guide: PSR-2

11

Page 12: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rapid Development of Quality Code● Version Control: Git● Development Workflow: Gitflow● Code Style Guide: PSR-2● Code Quality Rules

12

Page 13: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

PHP Mess Detector$ php composer require "phpmd/phpmd" --dev

$ php composer install --no-dev

13

Page 14: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

PHP Mess Detector Rules<?xml version="1.0"?><ruleset name="Code Quality" xmlns="http://pmd.sf.net/ruleset/1.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://pmd.sf.net/ruleset/1.0.0 http://pmd.sf.net/ruleset_xml_schema.xsd" xsi:noNamespaceSchemaLocation=" http://pmd.sf.net/ruleset_xml_schema.xsd"> <description>Custom Code Quality Rules</description> <!--Rulesets: http://phpmd.org/rules/index.html--> <rule ref="rulesets/cleancode.xml"/> <rule ref="rulesets/naming.xml/ShortVariable"> <properties> <property name="minimum" value="4"/> </properties> </rule></ruleset>

14

Page 15: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rapid Development of Quality Code● Version Control● Development Workflow: Gitflow● Code Style Guide: PSR-2● Code Quality Rules: PHP Mess Detector● Code Quality Enforcement

15

Page 16: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Git Pre-Commit Code Quality Hook#!/usr/bin/env php<?php

require __DIR__ . '/../../vendor/autoload.php';use Symfony\Component\Console\Application;

class CodeQualityTool extends Application{ private $projectRoot; const PHP_FILES_IN_SRC = '/^src\/(.*)(\.php)$/'; const PHP_FILES_IN_APPLICATION = '/^application\/(.*)(\.php)$/';

public function __construct() { $this->projectRoot = realpath(__DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR); parent::__construct('Code Quality Tool', '1.0.0'); }}

16

Page 17: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Git Pre-Commit Code Quality Hookprivate function extractCommitedFiles(){ $files = []; $output = [];

exec("git diff --cached --name-status --diff-filter=ACM", $output); foreach ($output as $line) { $filename = trim(substr($line, 1)); $isAppFile = preg_match(self::PHP_FILES_IN_APPLICATION, $filename); $isSrcFile = preg_match(self::PHP_FILES_IN_SRC, $filename); if ($isAppFile || $isSrcFile) { $files[] = $filename; } } return $files;}

17

Page 18: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Git Pre-Commit Code Quality Hookprivate function checkPhpMd($files){ $succeed = true; foreach ($files as $file) { $processArgs = ['bin/phpmd', $file, 'text', 'phpmd-rules.xml']; $processBuilder = new ProcessBuilder($processArgs); $processBuilder->setWorkingDirectory($this->projectRoot); $process = $processBuilder->getProcess(); $process->run(); if (!$process->isSuccessful()) { $this->output->writeln($file); $this->output->writeln($process->getErrorOutput()); $this->output->writeln($process->getOutput()); $succeed = false; } } return $succeed;}

18

Page 19: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Git Pre-Commit Code Quality Hookpublic function doRun(InputInterface $input, OutputInterface $output) { $this->input = $input; $this->output = $output;

$output->writeln('<info>Fetching files</info>'); $files = $this->extractCommitedFiles();

$info = '<info>Checking for messy code with PHPMD</info>'; $output->writeln($info); if (!$this->checkPhpMd($files)) { throw new Exception(sprintf('There are PHPMD violations!')); }}

19

Page 20: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rapid Development of Quality Code● Version Control: Git● Development Workflow: Gitflow● Code Style Guide: PSR-2● Code Quality Rules: PHP Mess Detector● Code Quality Enforcement: Git Hooks

20

Page 21: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Doctrine

21

Page 22: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Doctrine DBAL / ORM● Database Abstraction Layer (DBAL)

○ Database Vendor Agnostic○ Query Builder

● Object Relational Mapper (ORM)○ Maps Objects to Tables, Properties to Fields○ Object Relationships○ DQL - it's like SQL for your Objects

22

Page 23: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Generating Entity Classes (Annotations)$ php app/console doctrine:mapping:import --force AcmeBlogBundle xml

$ php app/console doctrine:mapping:convert annotation ./src

$ php app/console doctrine:generate:entities AcmeBlogBundle

23

Page 24: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Annotated Entity Class<?phpnamespace Acme\BlogBundle\Entity;use Doctrine\ORM\Mapping as ORM;/*** Acme\BlogBundle\Entity\BlogComment** @ORM\Table(name="blog_comment")* @ORM\Entity*/class BlogComment{ /** * @var integer $id * @ORM\Column(name="id", type="bigint") * @ORM\Id * @ORM\GeneratedValue(strategy="IDENTITY") */ private $id;

/** * @var string $author * @ORM\Column(name="author", type="string", length=100, nullable=false) */ private $author;}

24

Page 25: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Other Doctrine Tools● Doctrine Migrations● Data Fixtures Library

25

Page 26: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Introducing Automated Tests

26

Page 27: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

$ php composer require "behat/behat" --dev

Installing Behat

27

Page 28: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Writing Features for Existing Code● Write Feature● Run Test

○ Test Passes - Double Check○ Test Fails

■ Described Feature Wrong■ Mistake in Test Code■ Feature is Broken

28

Page 29: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

New Symfony Development

29

Page 30: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Installing Symfony Framework

30

Page 31: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Installing Symfony Framework

31

Page 32: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Merging Symfony Framework with Existing Code

● Install Separately● Move Directories● Combine composer.json

○ scripts○ extra○ autoload

32

Page 33: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Generate App Bundle

33

Page 34: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Generate App BundleC:\wamp\www\test>php app\console generate:bundle Welcome to the Symfony2 bundle generator Your application code must be written in bundles. This command helps you generate them easily. Each bundle is hosted under a namespace (like Acme/Bundle/BlogBundle). The namespace should begin with a "vendor" name like your company name, your project name, or your client name, followed by one or more optional category sub-namespaces, and it should end with the bundle name itself (which must have Bundle as a suffix). See http://symfony.com/doc/current/cookbook/bundles/best_practices.html#index-1 for more details on bundle naming conventions. Use / instead of \ for the namespace delimiter to avoid any problem. Bundle namespace: Demo/AppBundle

34

Page 35: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Generate App BundleIn your code, a bundle is often referenced by its name. It can be the concatenation of all namespace parts but it's really up to you to come up with a unique name (a good practice is to start with the vendor name). Based on the namespace, we suggest DemoAppBundle. Bundle name [DemoAppBundle]: AppBundle

35

Page 36: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

The bundle can be generated anywhere. The suggested default directory uses the standard conventions.

Target directory [C:\wamp\www\test/src]:

Determine the format to use for the generated configuration.

Configuration format (yml, xml, php, or annotation): annotation

To help you get started faster, the command can generate some code snippets for you.

Do you want to generate the whole directory structure [no]? yes

Generate App Bundle

36

Page 37: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Generate App Bundle Summary before generation You are going to generate a "Demo\AppBundle\AppBundle" bundle in "C:\wamp\www\test/src/" using the "annotation" format. Do you confirm generation [yes]? Bundle generation Generating the bundle code: OK Checking that the bundle is autoloaded: OK Confirm automatic update of your Kernel [yes]? Enabling the bundle inside the Kernel: OK Confirm automatic update of the Routing [yes]? Importing the bundle routing resource: OK You can now start using the generated code!

37

Page 38: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Sonata Admin

38

Page 39: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022 39

Page 40: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022 40

Page 41: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

$ php composer require "sonata-project/admin-bundle"

$ php composer require "sonata-project/doctrine-orm-admin-bundle"

Install Sonata Admin

41

Page 42: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

//AppKernel.phppublic function registerBundles(){ return [ //... new Symfony\Bundle\SecurityBundle\SecurityBundle(), new Sonata\CoreBundle\SonataCoreBundle(), new Sonata\BlockBundle\SonataBlockBundle(), new Knp\Bundle\MenuBundle\KnpMenuBundle(), new Sonata\DoctrineORMAdminBundle\SonataDoctrineORMAdminBundle(), new Sonata\AdminBundle\SonataAdminBundle() ];}

Enable Sonata Admin Bundle

42

Page 43: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Configure Sonata Admin Bundle#app\config.ymlsonata_block: default_contexts: [cms] blocks: sonata.admin.block.admin_list: contexts: [admin]

43

Page 44: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

#app\routing.ymladmin: resource: '@SonataAdminBundle/Resources/config/routing/sonata_admin.xml' prefix: /admin

_sonata_admin: resource: . type: sonata_admin prefix: /admin

Sonata Admin Bundle Routing

44

Page 45: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

$ php app/console assets:install web

$ php app/console cache:clear

Configure Sonata Admin Bundle

45

Page 46: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

<?php

namespace Demo\AppBundle\Admin;

use Sonata\AdminBundle\Admin\Admin;use Sonata\AdminBundle\Form\FormMapper;use Sonata\AdminBundle\Datagrid\ListMapper;use Sonata\AdminBundle\Datagrid\DatagridMapper;

class CourseAdmin extends Admin{ protected function configureFormFields(FormMapper $formMapper){}

protected function configureListFields(ListMapper $listMapper){}

protected function configureDatagridFilters(DatagridMapper $datagridMapper){}}

Admin Class

46

Page 47: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Class - Create/Edit Form// Fields to be shown on create/edit formsprotected function configureFormFields(FormMapper $formMapper){ $formMapper ->add('title', 'text', ['label' => 'Course Title']) ->add('author', 'entity', ['class' => 'Demo\AppBundle\Entity\User']) ->add('description', null, ['required' => false]) ->add('categories') ->add('cost');}

47

Page 48: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Class - List// Fields to be shown on listsprotected function configureListFields(ListMapper $listMapper){ $listMapper ->addIdentifier('title') ->add('author') ->add('cost') ->add('categories');}

48

Page 49: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Class - Datagrid Filters// Fields to be shown on filter formsprotected function configureDatagridFilters(DatagridMapper $datagridMapper){ $datagridMapper ->add('title') ->add('author') ->add('categories');}

49

Page 50: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Service# Demo/AppBundle/Resources/config/admin.ymlservices: sonata.admin.course: class: Demo\AppBundle\Admin\CourseAdmin tags: - { name: sonata.admin, manager_type: orm, group: "Course Management", label: "Courses" } arguments: - ~ - Demo\AppBundle\Entity\Course - ~ calls: - [ setTranslationDomain, [AppBundle]]

50

Page 51: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Services - Add to Config# app/config/config.ymlimports: - { resource: parameters.yml } - { resource: security.yml } - { resource: services.yml } - { resource: @AppBundle/Resources/config/admin.yml }

51

Page 52: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Done!

52

Page 53: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Optimization

53

Page 54: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Symfony Debug Toolbar

54

Page 55: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Symfony Profiler

55

Page 56: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Class - Custom Query (List)public function createQuery($context = 'list'){ $queryBuilder = $this->getModelManager() ->getEntityManager('models\Course') ->createQueryBuilder();

$queryBuilder->select('course', 'categories') ->from('models\Course', 'course') ->leftJoin('course.categories', 'categories');

$proxyQuery = new ProxyQuery($queryBuilder); return $proxyQuery;}

56

Page 57: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Admin Class - Custom Query (Edit) Page)public function getObject($id){ $dql = "SELECT course, categories FROM models\Course course LEFT JOIN course.categories categories WHERE course.id = :id";

$query = $this->getModelManager() ->getEntityManager('models\Course') ->createQuery($dql); $query->setParameter('id', $id);

$course = $query->getOneOrNullResult();

return $course;}

57

Page 58: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

API

58

Page 59: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Install FOS Rest Bundle$ php composer require "friendsofsymfony/rest-bundle"

$ php composer require "jms/serializer-bundle"

$ php composer require "nelmio/api-doc-bundle"

59

Page 60: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Enable FOS Rest Bundle and Serializer Bundle

//AppKernel.phppublic function registerBundles(){ return [ // ... new FOS\RestBundle\RestBundle(), new JMS\SerializerBundle\JMSSerializerBundle(), new Nelmio\ApiDocBundle\NelmioApiDocBundle() ];}

60

Page 61: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Configuration# app/config/routing.ymlNelmioApiDocBundle: resource: "@NelmioApiDocBundle/Resources/config/routing.yml" prefix: /api/doc

# app/config/config.ymlnelmio_api_doc: ~framework: templating: engines: ['twig']

61

Page 62: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

API Controller<?php

namespace Demo\AppBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;use FOS\RestBundle\Controller\Annotations\Get;

class UsersController extends FOSRestController{ /** * @Get("/users/") */ public function getUsersAction() { $data = $this->getDoctrine()->getRepository('models\User')->findAll(); $view = $this->view($data, 200) ->setTemplate("AppBundle:Basic:json.twig") ->setTemplateVar('users'); return $this->handleView($view); }}

62

Page 63: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

JSON Twig Tpl{% spaceless %}{% if json is defined %} {{ json|json_encode()|raw }}{% else %} []{% endif %}{% endspaceless %}

63

Page 64: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Add Doc Blocks with Annotation to API Controller<?php

namespace Demo\AppBundle\Controller;

use ...

class UsersController extends FOSRestController{ /** * @Get("/users/") * @ApiDoc( * resource=true, * description="List of Users", * output="models\User" * ) */ public function getUsersAction() { $data = $this->getDoctrine()->getRepository('models\User')->findAll(); $view = $this->view($data, 200) ->setTemplate("AppBundle:Basic:json.twig") ->setTemplateVar('users'); return $this->handleView($view); }}

64

Page 65: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Dependency Injection

Service Location

Configure Dependencies

Outside of Class

Class Requests Dependencies

From Container

65

Page 66: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Service Location<?php

namespace Demo\AppBundle\Command;

use Symfony\Bundle\FrameworkBundle\Command\ContainerAwareCommand;use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Demo\AppBundle\Factory\MessageFactory;

class UserExportCommand extends ContainerAwareCommand{ protected function execute(InputInterface $input, OutputInterface $output) { $rootDir = $this->getContainer()->getParameter('kernel.root_dir'); $users = $this->getContainer()->getDoctrine()

->getRepository('models\User')->findAll();

$messageFactory = new MessageFactory(); foreach ($users as $user) { $emailMessage = $this->getMessageFactory()->generate($user); /** More Processing Logic Here */ } }}

66

Page 67: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

services: appbundle.message.factory: class: Demo\AppBundle\Factory\MessageFactory

Defining Services

67

Page 68: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

services: appbundle.message.factory: class: Demo\AppBundle\Factory\MessageFactory

appbundle.repositories.user: class: Demo\AppBundle\Entity\Repositories\UserRepository factory_service: doctrine.orm.entity_manager factory_method: getRepository arguments: - '\models\User'

Defining Services

68

Page 69: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

services: appbundle.message.factory: class: Demo\AppBundle\Factory\MessageFactory

appbundle.repositories.user: class: Demo\AppBundle\Entity\Repositories\UserRepository factory_service: doctrine.orm.entity_manager factory_method: getRepository arguments: - '\models\User'

appbundle.command.user_export: class: Demo\AppBundle\Command\UserExportCommand calls: - [ setMessageFactory, ["@appbundle.message.factory"]] - [ setUserRepository, ["@appbundle.repositories.user"]] - [ setRootDir, ["%kernel.root_dir%"]]

Defining Services

69

Page 70: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Dependency Injection<?php

namespace Demo\AppBundle\Command;

use Symfony\Component\Console\Input\InputInterface;use Symfony\Component\Console\Output\OutputInterface;use Demo\AppBundle\Factory\MessageFactory;use Symfony\Component\Console\Command\Command;

class UserExportCommand extends Command{ protected $messageFactory; protected $rootDir; protected $userRepository;

}

70

Page 71: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Dependency Injection<?php//..class UserExportCommand extends Command{ //..

public function setMessageFactory($messageFactory) { $this->messageFactory = $messageFactory; }

public function setRootDir($rootDir) { $this->rootDir = $rootDir; }

public function setUserRepository($userRepository) { $this->userRepository = $userRepository; }

71

Page 72: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Dependency Injection<?php//..class UserExportCommand extends Command{ //..

public function getMessageFactory() { return $this->messageFactory; }

public function getRootDir() { return $this->rootDir; }

public function getUserRepository() { return $this->userRepository; }

72

Page 73: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Dependency Injection

73

<?php

//..

class UserExportCommand extends Command{

//..

protected function execute(InputInterface $input, OutputInterface $output) { $users = $this->getUserRepository()->findAll(); /** Processing Logic */ foreach ($users as $user) { $emailMessage = $this->getMessageFactory()->generate($user); /** More Processing Logic Here */ } }}

Page 74: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Controller Using Service Location

74

<?php

namespace Demo\AppBundle\Controller;

use FOS\RestBundle\Controller\FOSRestController;use FOS\RestBundle\Controller\Annotations\Get;

class UsersController extends FOSRestController{ /** * @Get("/users/") */ public function getUsersAction() { $data = $this->getDoctrine()->getRepository('models\User')->findAll(); $view = $this->view($data, 200) ->setTemplate("AppBundle:Basic:json.twig") ->setTemplateVar('users'); return $this->handleView($view); }}

Page 75: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

services: appbundle.controllers.users_controller: class: Demo\AppBundle\Controller\UsersController calls: - [ setUserRepository, ["@appbundle.repositories.user"]]

Defining A Controller As A Service

75

Page 76: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Controller using Dependency Injection

76

/**

* @Route(service="appbundle.controllers.users_controller")

*/

class UsersController extends FOSRestController{ protected $userRepository;

public function setUserRepository($userRepository) { $this->userRepository = $userRepository; }

/** * @Get("/users/") */ public function getUsersAction() { $data = $this->userRepository->findAll(); $view = $this->view($data, 200) ->setTemplate("AppBundle:Basic:json.twig") ->setTemplateVar('users'); return $this->handleView($view); }}

Page 77: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Current Project StatusIt's stable!

● New Symfony based Admin was launched after about 9 months (thanks developers!)

● Had a few bugs, took about 3 more months to be stable

● Over past year, close to 0 regressions (Thanks Behat!)

● Very limited downtime (Thanks Amazon, Elastic Beanstalk, Aurora!)

77

Page 78: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Technical GoalsHappy Developers!

● Maintainable Code: Check

● Quality Code: Check

● Documentation: Check

● Rapid Development: Oh Yeah

● Easy Deployment: So Easy!

● Zero Regressions: Close Enough!

78

Page 79: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

Rebuilding Our Foundation

How We Used Symfony To Rewrite Our Application

@jessicamauerhan | Madison PHP | https://joind.in/16022

Page 80: Rebuilding our Foundation

@jessicamauerhan | Madison PHP | https://joind.in/16022

ResourcesGitflow: https://github.com/nvie/gitflow

PSR-2: http://www.php-fig.org/psr/psr-2/

PHPMD: http://phpmd.org/

Pre-commit hook: https://gist.github.com/jmauerhan/d18e7c232acb3986134d

Doctrine: http://www.doctrine-project.org/

Doctrine Migrations Bundle: http://symfony.com/doc/current/bundles/DoctrineMigrationsBundle/index.html

Doctrine Data Fixtures Bundle: http://symfony.com/doc/current/bundles/DoctrineFixturesBundle/index.html

Behat: http://docs.behat.org/en/v2.5/

XDebug Wizard: http://xdebug.org/wizard.php

Sonata Project: https://sonata-project.org/bundles/admin/2-3/doc/index.html

FOS Rest Bundle: http://symfony.com/doc/master/bundles/FOSRestBundle/index.html

Nelmio API Doc Bundle: https://github.com/nelmio/NelmioApiDocBundle/blob/master/Resources/doc/index.md

80