Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Getting yourself out of trouble with PHP anti-patterns

Getting yourself out of trouble with PHP anti-patterns

Do you have code that you’re afraid to touch because nobody really knows what it does any more? Do you have systems which would be easier to re-write than to maintain? Are developers avoiding working on various tickets because it’ll involve working with a particularly painful part of the code?

Maybe a few developers have tried refactoring over the years but given up (or made things worse!), and certain ‘bad smells’ in the code are stunting progress.

While ‘Design Patterns’ tell us how to design code upfront to solve a common problem, ‘Anti-patterns’ tell us how to rescue a struggling PHP application before it leads to a company’s downfall.

Together we’ll explore some of the common pitfalls, find out how to recognise issues before they become serious, and we’ll see how to move a failing project back into the limelight.

By the end of this session everybody will be eager to tackle those parts of your codebase that the whole team has been steering clear of for months!

nealio82

June 10, 2019
Tweet

More Decks by nealio82

Other Decks in Programming

Transcript

  1. GETTING OUT
    OF TROUBLE
    WITH
    ANTI-PATTERNS

    View Slide

  2. View Slide

  3. View Slide

  4. View Slide

  5. WE ARE HIRING!

    View Slide

  6. GETTING OUT
    OF TROUBLE
    WITH
    ANTI-PATTERNS

    View Slide

  7. View Slide

  8. Design patterns are common ways of solving
    familiar problems in software engineering

    View Slide

  9. https://laracasts.com/series/design-patterns-in-php

    View Slide

  10. View Slide

  11. Anti-patterns are common ways of solving familiar
    problems in software engineering…

    View Slide

  12. …which have an unintended negative result

    View Slide

  13. We Keep Making The Same Mistakes

    View Slide

  14. Spotting anti-patterns helps you identify
    where things have gone wrong, and puts you
    back on the right path

    View Slide

  15. View Slide

  16. View Slide

  17. View Slide

  18. View Slide

  19. SOFTWARE
    DEVELOPMENT
    ANTI-PATTERNS

    View Slide

  20. SOFTWARE ARCHITECTURE
    ANTI-PATTERNS

    View Slide

  21. PROJECT
    MANAGEMENT
    ANTI-PATTERNS

    View Slide

  22. I thought you’d
    already fixed
    that bug?

    View Slide

  23. CUT & PASTE
    PROGRAMMING

    View Slide

  24. The same bug recurs throughout the system
    despite many local fixes
    Symptoms

    View Slide

  25. Lines of code increase more than productivity
    Symptoms

    View Slide

  26. Excessive software maintenance costs
    Symptoms

    View Slide

  27. Organisational emphasis is on short-term
    payoff rather than long-term investment
    Typical Causes

    View Slide

  28. Development speed is considered a top marker
    of productivity
    Typical Causes

    View Slide

  29. Lack of abstraction / poor understanding of
    inheritance, composition, or other
    development strategies
    Typical Causes

    View Slide

  30. People who, unfamiliar with a new technology,
    copy & paste examples from stack overflow and
    just modify to their needs
    Typical Causes

    View Slide

  31. Code Mining

    View Slide

  32. $ composer require --dev sebastian/phpcpd

    View Slide

  33. $ php vendor/bin/phpcpd . --exclude-regexps=vendor

    View Slide

  34. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  35. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  36. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  37. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  38. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  39. Found 35 clones with 1873 duplicated lines in 44 files:
    .../campaign_groups/lib/ManageCampaignGroups.php:258-462 (204 lines)
    .../campaign/lib/ManageCampaigns.php:335-539

    View Slide

  40. Refactor

    View Slide

  41. Pluck Reusable Components

    View Slide

  42. /**
    * filters out campaigns.
    *
    * @param ArrayManipulation $campaigns
    * @param array $filtersSpec
    *
    * @throws Exception
    */
    public static function filterCampaignsList(ArrayManipulation $campai
    $filterRules = array();
    foreach ($filtersSpec as $filter) {
    if ($filter['name'] == '-custom-ysdb') {
    $operator = $filter['comparator'];
    $expectedValue = $filter['value'];
    $campaignFbIds = self::pluckFbCampaignIds($campaigns->getValue
    if (!$campaignFbIds) {
    continue;
    }
    // get yesterday spend. used in `yday spnd/dly bdgt` filter
    $spec = DWEndpointSpec::create()
    ->setIds($campaignFbIds)
    ->addFields(array('spend'))
    ->setDateFrom((new DateTime())->modify('-1 DAY'))
    ->setDateTo(new DateTime())
    campaign
    ManageCampaignGroups.php
    campaign_groups

    View Slide

  43. View Slide

  44. /**
    * @param $filtersArray
    *
    * @return array
    */
    public static function parseFiltersArray($filtersArray) {
    $pre = array();
    $post = array();
    $preFiltersNames = array(
    'statusIds' => 'statusIds',
    'name' => 'name',
    'budget' => 'budget',
    'objective' => 'objective',
    'spend_cap' => 'spend_cap'
    );
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    return true;
    }
    }
    return false;
    };
    campaign
    ManageCampaignGroups.php
    campaign_groups

    View Slide

  45. ManageCampaigns.php
    /**
    * @param $filtersArray
    *
    * @return array
    */
    public static function parseFiltersArray($filtersArray) {
    $pre = array();
    $post = array();
    $preFiltersNames = array(
    'dimensionTag_\d+' => 'dimensionTag',
    'statusIds' => 'statusIds',
    'bidTypes' => 'bidTypes',
    'campaignGroupIds' => 'campaignGroupIds',
    'name' => 'name',
    'budget' => 'budget',
    'optimisationGoal' => 'optimisationGoal',
    'billingEvent' => 'billingEvent',
    '-custom-dayparting' => '-custom-dayparting',
    '-custom-dimension' => '-custom-dimension',
    );
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    return true;
    campaign
    campaign_groups

    View Slide

  46. /**
    * @param $filtersArray
    *
    * @return array
    */
    public static function parseFiltersArray($filtersArray) {
    $pre = array();
    $post = array();
    $preFiltersNames = array(
    'dimensionTag_\d+' => 'dimensionTag',
    'statusIds' => 'statusIds',
    'bidTypes' => 'bidTypes',
    'bidModel' => 'bidModel',
    'campaignIds' => 'campaignIds',
    'campaignGroupIds' => 'campaignGroupIds',
    'uploadId' => 'uploadId',
    'creative_title' => 'creative_title',
    'creative_body' => 'creative_body',
    'name' => 'name',
    'maxBid' => 'maxBid',
    '-custom-dimension' => '-custom-dimension',
    );
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    campaign
    campaign_groups
    campaign_ads
    ManageAds.php

    View Slide

  47. abstract class CampaignManagement
    {
    abstract protected static function getFilters(): array;
    public static function parseFiltersArray(): array
    {
    $pre = array();
    $post = array();
    $preFiltersNames = static::getFilters();
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    return true;
    }
    }
    return false;
    };
    foreach ($filtersArray as $filter) {
    if ($in_array_match($filter['name'], $preFiltersNames)) {
    campaign
    campaign_groups
    campaign_ads
    CampaignManagement.php
    Management

    View Slide

  48. abstract class CampaignManagement
    {
    abstract protected static function getFilters(): array;
    public static function parseFiltersArray(): array
    {
    $pre = array();
    $post = array();
    $preFiltersNames = static::getFilters();
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    return true;
    }
    }
    return false;
    };
    foreach ($filtersArray as $filter) {
    if ($in_array_match($filter['name'], $preFiltersNames)) {
    campaign
    campaign_groups
    campaign_ads
    CampaignManagement.php
    Management

    View Slide

  49. abstract class CampaignManagement
    {
    abstract protected static function getFilters(): array;
    public static function parseFiltersArray(): array
    {
    $pre = array();
    $post = array();
    $preFiltersNames = static::getFilters();
    $in_array_match = function (&$value, $array) {
    foreach ($array as $pattern => $newValue) {
    if (preg_match('/^' . $pattern . '$/', $value)) {
    $value = $newValue;
    return true;
    }
    }
    return false;
    };
    foreach ($filtersArray as $filter) {
    if ($in_array_match($filter['name'], $preFiltersNames)) {
    campaign
    campaign_groups
    campaign_ads
    CampaignManagement.php
    Management

    View Slide

  50. class ManageAdsPage extends CampaignManagement
    {
    protected static function getFilters(): array
    {
    return array(
    'dimensionTag_\d+' => 'dimensionTag',
    'statusIds' => 'statusIds',
    'bidTypes' => 'bidTypes',
    'bidModel' => 'bidModel',
    'campaignIds' => 'campaignIds',
    'campaignGroupIds' => 'campaignGroupIds',
    'uploadId' => 'uploadId',
    'creative_title' => 'creative_title',
    'creative_body' => 'creative_body',
    'name' => 'name',
    'maxBid' => 'maxBid',
    '-custom-dimension' => '-custom-dimension',
    );
    }
    }
    campaign
    campaign_groups
    campaign_ads
    ManageAds.php
    Management

    View Slide

  51. class ManageAdsPage extends CampaignManagement
    {
    protected static function getFilters(): array
    {
    return array(
    'dimensionTag_\d+' => 'dimensionTag',
    'statusIds' => 'statusIds',
    'bidTypes' => 'bidTypes',
    'bidModel' => 'bidModel',
    'campaignIds' => 'campaignIds',
    'campaignGroupIds' => 'campaignGroupIds',
    'uploadId' => 'uploadId',
    'creative_title' => 'creative_title',
    'creative_body' => 'creative_body',
    'name' => 'name',
    'maxBid' => 'maxBid',
    '-custom-dimension' => '-custom-dimension',
    );
    }
    }
    campaign
    campaign_groups
    campaign_ads
    ManageAds.php
    Management

    View Slide

  52. class ManageAdsPage extends CampaignManagement
    {
    protected static function getFilters(): array
    {
    return array(
    'dimensionTag_\d+' => 'dimensionTag',
    'statusIds' => 'statusIds',
    'bidTypes' => 'bidTypes',
    'bidModel' => 'bidModel',
    'campaignIds' => 'campaignIds',
    'campaignGroupIds' => 'campaignGroupIds',
    'uploadId' => 'uploadId',
    'creative_title' => 'creative_title',
    'creative_body' => 'creative_body',
    'name' => 'name',
    'maxBid' => 'maxBid',
    '-custom-dimension' => '-custom-dimension',
    );
    }
    }
    campaign
    campaign_groups
    campaign_ads
    ManageAds.php
    Management

    View Slide

  53. $ php vendor/bin/phpcpd . --exclude-regexps=vendor

    View Slide

  54. Found 31 clones with 1426 duplicated lines in 42 files:

    View Slide

  55. View Slide

  56. Code Review

    View Slide

  57. We’re not really
    sure what that
    code does

    View Slide

  58. But it works so
    we just leave it
    alone
    We’re not really
    sure what that
    code does

    View Slide

  59. LAVA FLOW

    View Slide

  60. Undocumented, complex, important-looking
    functions / classes / segments unrelated to the
    system architecture
    Symptoms

    View Slide

  61. Unjustifiable variables & code fragments
    distributed throughout system
    Symptoms

    View Slide

  62. Lots of ‘TODO / to complete’ comments
    Symptoms

    View Slide

  63. Large blocks of commented-out code with no
    explanation
    Symptoms

    View Slide

  64. R&D code finds its way into production
    Typical Causes

    View Slide

  65. Lone-wolf working on the code
    Typical Causes

    View Slide

  66. Shifting goals of the software project
    Typical Causes

    View Slide

  67. Lack of architectural forethought / design
    Typical Causes

    View Slide

  68. Dig In To The Problem

    View Slide

  69. actions.class.php
    actions class inlinebuilderActions extends sfActions
    {
    public function executeProcess(sfWebRequest $request) {
    $uploadId = $request->getParameter('uploadId');
    $clientId = $this->getUser()->getAttribute('clientSessionId');
    $inlineBuilderUpload = Doctrine::getTable('InlineBuilderUpload')-
    >findOneByIdAndClientId($uploadId, $clientId);
    if ($inlineBuilderUpload instanceof InlineBuilderUpload) {
    # We only allow to process builds from the last step of the builde
    it (saved)
    if (in_array($inlineBuilderUpload->getStatus(),
    array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SA
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_Q
    $inlineBuilderUpload->save();
    }
    }
    /*
    $bulkUpload = $inlineBuilderUpload->loadBulkUpload();
    $conn = sfContext::getInstance()->getDatabaseManager()->getDatabase(
    >getDoctrineConnection();

    View Slide

  70. actions.class.php
    actions
    }
    }
    /*
    $bulkUpload = $inlineBuilderUpload->loadBulkUpload();
    $conn = sfContext::getInstance()->getDatabaseManager()->getDatabase('d
    >getDoctrineConnection();
    try {
    $conn->beginTransaction();
    $bulkUpload->saveIntoDB();
    // Commit the transaction when done
    $conn->commit();
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_PROCESSE
    $inlineBuilderUpload->save();
    } catch (Exception $e) {
    // Rollback if transaction fail
    $conn->rollback();
    $bulkError = new BulkError();
    $bulkError->setClass('BulkDB');
    $bulkError->setFunction('BulkDB.saveIntoDB');
    $bulkError->setType(BulkError::TYPE_ERROR);
    $bulkError->setMessage($e->getMessage());

    View Slide

  71. actions.class.php
    actions
    $bulkError->setFunction('BulkDB.saveIntoDB');
    $bulkError->setType(BulkError::TYPE_ERROR);
    $bulkError->setMessage($e->getMessage());
    $bulkError->setObject('inlineBuilderUpload');
    $bulkError->setAccountId($inlineBuilderUpload->getAccountId());
    $bulkError->save();
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_ERROR);
    $inlineBuilderUpload->save();
    }
    */
    $this->redirect('/inlinebuilder/uploads');
    }
    public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }

    View Slide

  72. Refactor

    View Slide

  73. Detective Work

    View Slide

  74. $ git blame actions.class.php

    View Slide

  75. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  76. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  77. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  78. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  79. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  80. a315cb4a (Jordi 2014-07-01) /*
    9dedbd66 (Jordi 2014-06-30) $conn = sfContext::getInstance()->getDatabaseManager()->
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) try {
    9dedbd66 (Jordi 2014-06-30) $conn->beginTransaction();
    9dedbd66 (Jordi 2014-06-30)
    144398e4 (Jordi 2014-06-30) $bulkUpload->saveIntoDB();
    144398e4 (Jordi 2014-06-30)
    9dedbd66 (Jordi 2014-06-30) $conn->commit();
    9dedbd66 (Jordi 2014-06-30)

    View Slide

  81. $ git show a315cb4a

    View Slide

  82. commit a315cb4a741f7dec51376477c8c66419991aa22d
    Author: Jordi
    Date: Tue Jul 1 13:08:15 2014 +0100
    [Inline Builder] - DB Process -- Cron added.
    + task/BuilderTask.class.php

    View Slide

  83. commit a315cb4a741f7dec51376477c8c66419991aa22d
    Author: Jordi
    Date: Tue Jul 1 13:08:15 2014 +0100
    [Inline Builder] - DB Process -- Cron added.
    + task/BuilderTask.class.php

    View Slide

  84. BuilderTask.class.php
    task
    class BuilderTask extends sfBaseTask
    {
    private function _createInlineBuilders($inlineBuilders)
    {
    TerminalHelper::log("Found " . count($inlineBuilders) . " Inline Buil
    created", "yellow");
    if(count($inlineBuilders) > 0) {
    /** @var InlineBuilderUpload $inlineBuilder */
    foreach ($inlineBuilders as $inlineBuilder) {
    TerminalHelper::log("Loading Bulk Upload for Inline Job " . $inli
    "yellow");
    $bulkUpload = $inlineBuilder->loadBulkUpload();
    $conn = sfContext::getInstance()->getDatabaseManager()->getDataba
    >getDoctrineConnection();
    try {
    $conn->beginTransaction();
    TerminalHelper::log("Saving Job to DB", "yellow");
    $bulkUpload->saveIntoDB();
    actions

    View Slide

  85. actions.class.php
    actions
    it (saved)
    if (in_array($inlineBuilderUpload->getStatus(),
    array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE
    $inlineBuilderUpload->save();
    }
    }
    /*
    * DELETE ALL THE COMMENTED-OUT CODE!
    */
    $this->redirect('/inlinebuilder/uploads');
    }
    public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }
    task

    View Slide

  86. actions.class.php
    actions
    it (saved)
    if (in_array($inlineBuilderUpload->getStatus(),
    array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE
    $inlineBuilderUpload->save();
    }
    }
    /*
    * DELETE ALL THE COMMENTED-OUT CODE!
    */
    $this->redirect('/inlinebuilder/uploads');
    }
    public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }
    task

    View Slide

  87. actions.class.php
    actions
    it (saved)
    if (in_array($inlineBuilderUpload->getStatus(),
    array(InlineBuilderUpload::STATUS_EDITING,InlineBuilderUpload::STATUS_SAVE
    $inlineBuilderUpload->setStatus(InlineBuilderUpload::STATUS_IN_QUE
    $inlineBuilderUpload->save();
    }
    }
    /*
    * DELETE ALL THE COMMENTED-OUT CODE!
    */
    $this->redirect('/inlinebuilder/uploads');
    }
    public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }
    task

    View Slide

  88. $ git log -- actions.class.php

    View Slide

  89. commit d4e72ab65850ef49fd829e367fe6970025f180bb
    Author: Daniel
    Date: Thu Oct 15 15:34:31 2015 +0300
    Excel download button last page of inline builder. PR-4971
    commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a
    Author: Radu
    Date: Thu Oct 15 10:14:09 2015 +0100
    PR-4972 #resolve Duplicate Excel download from the upload's page

    View Slide

  90. commit d4e72ab65850ef49fd829e367fe6970025f180bb
    Author: Daniel
    Date: Thu Oct 15 15:34:31 2015 +0300
    Excel download button last page of inline builder. PR-4971
    commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a
    Author: Radu
    Date: Thu Oct 15 10:14:09 2015 +0100
    PR-4972 #resolve Duplicate Excel download from the upload's page

    View Slide

  91. commit d4e72ab65850ef49fd829e367fe6970025f180bb
    Author: Daniel
    Date: Thu Oct 15 15:34:31 2015 +0300
    Excel download button last page of inline builder. PR-4971
    commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a
    Author: Radu
    Date: Thu Oct 15 10:14:09 2015 +0100
    PR-4972 #resolve Duplicate Excel download from the upload's page

    View Slide

  92. commit d4e72ab65850ef49fd829e367fe6970025f180bb
    Author: Daniel
    Date: Thu Oct 15 15:34:31 2015 +0300
    Excel download button last page of inline builder. PR-4971
    commit 937a9d05b5a9120e9345d8784814ff5dc7a1f51a
    Author: Radu
    Date: Thu Oct 15 10:14:09 2015 +0100
    PR-4972 #resolve Duplicate Excel download from the upload's page

    View Slide

  93. $ git show 937a9d05b

    View Slide

  94. + Excel
    + getUser()->hasPermission('new_excel')) {?>
    +

    View Slide

  95. + Excel
    + getUser()->hasPermission('new_excel')) {?>
    +

    View Slide

  96. + Excel
    + getUser()->hasPermission('new_excel')) {?>
    +

    View Slide

  97. + Excel
    + getUser()->hasPermission('new_excel')) {?>
    +

    View Slide

  98. actions.class.php
    actions
    task
    public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }

    View Slide

  99. actions.class.php
    actions public function executeDownloadOLD(sfWebRequest $request) {
    // ...
    }
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }
    task

    View Slide

  100. actions.class.php
    actions
    task
    public function executeDownload(sfWebRequest $request) {
    // ...
    }
    public function executeOldDownload(sfWebRequest $request) {
    // ...
    }
    }

    View Slide

  101. actions.class.php
    actions
    task
    public function executeVersion2Download(sfWebRequest $request) {
    // ...
    }
    public function executeVersion1Download(sfWebRequest $request) {
    // ...
    }
    }

    View Slide

  102. View Slide

  103. Invest In Educating Your Developers

    View Slide

  104. Redefine The Architecture Whenever You See A Lava Flow Forming

    View Slide

  105. That code is a
    mess!

    View Slide

  106. That code is a
    mess!
    Making a change in
    one place breaks
    something in
    another

    View Slide

  107. SPAGHETTI CODE

    View Slide

  108. Code cannot be re-used
    Symptoms

    View Slide

  109. Objects are frequently named as processes
    Symptoms

    View Slide

  110. Minimal relationships between objects
    Symptoms

    View Slide

  111. Many object methods take no parameters, and
    utilise class or global variables
    Symptoms

    View Slide

  112. Difficult to retain staff with OOP background
    Symptoms

    View Slide

  113. Side-effects everywhere
    Symptoms

    View Slide

  114. Poor application performance
    Symptoms

    View Slide

  115. Programmers are new to OOP
    Typical Causes

    View Slide

  116. No mentoring / inefficient code reviews
    Typical Causes

    View Slide

  117. No thought of architectural design prior to
    implementation
    Typical Causes

    View Slide

  118. Developers working in isolation
    Typical Causes

    View Slide

  119. 10af6d6 adding privacy policy page
    93ddb94 i
    bcb7319 ii
    8747627 Finishing up Website Usage Page
    2414eb8 iwq
    2a5910d ii
    eadc6e8 ii
    0d23c1d ii
    e6a97b1 wq
    5780487 i
    64eea96 ii
    6914478 qwe
    1f06a5d iq
    07d15b8 wr
    1d6df82 echo
    fc014f1 Adding new background
    c7ed903 Here we go
    9040b40 Testing
    4523f29 testing
    66e7e60 iok
    965d026 Making modifications
    a5f58f8 Adding Google Manager codes
    08f91d9 wq
    ff555c1 removing header
    23d07b1 Removing content
    61e480d ok trying this out

    View Slide

  120. 10af6d6 adding privacy policy page
    93ddb94 i
    bcb7319 ii
    8747627 Finishing up Website Usage Page
    2414eb8 iwq
    2a5910d ii
    eadc6e8 ii
    0d23c1d ii
    e6a97b1 wq
    5780487 i
    64eea96 ii
    6914478 qwe
    1f06a5d iq
    07d15b8 wr
    1d6df82 echo
    fc014f1 Adding new background
    c7ed903 Here we go
    9040b40 Testing
    4523f29 testing
    66e7e60 iok
    965d026 Making modifications
    a5f58f8 Adding Google Manager codes
    08f91d9 wq
    ff555c1 removing header
    23d07b1 Removing content
    61e480d ok trying this out

    View Slide

  121. campaigns.php
    functions
    function deleteCampaignGuestsViaCampaignId($campaign_id)
    {
    $dbh = getNewPDOInstance(‘MY_DB_NAME’);
    $sql = 'DELETE FROM campaign_guest WHERE campaign_id = ‘
    . $campaign_id;
    $stmt = $dbh->prepare($sql);
    if ($stmt->execute()) {
    return true;
    }
    }
    function deletePhotosFromCampaignFolderViaCampaignId($campaign_id)
    {
    global $root_prefix;
    global $rootd;
    $path = $root_prefix . "/uploads/campaigns/" . $campaign_id;
    $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id;
    $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path);
    // print_r($photos);
    foreach ($photos as $key => $photo) {
    // this is to skip . and .. in the folder.
    if ("." === substr($photo, 0, 1)) {

    View Slide

  122. function deleteCampaignGuestsViaCampaignId($campaign_id)
    {
    $dbh = getNewPDOInstance(‘MY_DB_NAME’);
    $sql = 'DELETE FROM campaign_guest WHERE campaign_id = ‘
    . $campaign_id;
    $stmt = $dbh->prepare($sql);
    if ($stmt->execute()) {
    return true;
    }
    }
    function deletePhotosFromCampaignFolderViaCampaignId($campaign_id)
    {
    global $root_prefix;
    global $rootd;
    $path = $root_prefix . "/uploads/campaigns/" . $campaign_id;
    $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id;
    $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path);
    // print_r($photos);
    foreach ($photos as $key => $photo) {
    // this is to skip . and .. in the folder.
    if ("." === substr($photo, 0, 1)) {
    campaigns.php
    functions

    View Slide

  123. function deleteCampaignGuestsViaCampaignId($campaign_id)
    {
    $dbh = getNewPDOInstance(‘MY_DB_NAME’);
    $sql = 'DELETE FROM campaign_guest WHERE campaign_id = ‘
    . $campaign_id;
    $stmt = $dbh->prepare($sql);
    if ($stmt->execute()) {
    return true;
    }
    }
    function deletePhotosFromCampaignFolderViaCampaignId($campaign_id)
    {
    global $root_prefix;
    global $rootd;
    $path = $root_prefix . "/uploads/campaigns/" . $campaign_id;
    $real_file_path = $rootd . "/uploads/campaigns/" . $campaign_id;
    $photos = scandir($_SERVER['DOCUMENT_ROOT'] . $path);
    // print_r($photos);
    foreach ($photos as $key => $photo) {
    // this is to skip . and .. in the folder.
    if ("." === substr($photo, 0, 1)) {
    campaigns.php
    functions

    View Slide

  124. function createUpdateLoginClient($client){
    $password_hash = password_hash($client['password'], PASSWORD_DEFAULT);
    $id = $client['id'];
    $dbh = getNewPDOInstance(‘login_clients');
    if($id){
    // ...
    }
    if($client['password'] && $id){
    // ...
    }
    if(!$id){
    $client_id = createClient($client['user_name'], $client['email'], $
    // ...
    }
    if($result){
    return true;
    } else{
    return true;
    }
    }
    function createClient($email){
    $dbh = getNewPDOInstance('MY_DB_NAME');
    // ...
    }
    campaigns.php
    functions
    clients.php

    View Slide

  125. function createUpdateLoginClient($client){
    $password_hash = password_hash($client['password'], PASSWORD_DEFAULT);
    $id = $client['id'];
    $dbh = getNewPDOInstance(‘login_clients');
    if($id){
    // ...
    }
    if($client['password'] && $id){
    // ...
    }
    if(!$id){
    $client_id = createClient($client['user_name'], $client['email'], $
    // ...
    }
    if($result){
    return true;
    } else{
    return true;
    }
    }
    function createClient($email){
    $dbh = getNewPDOInstance('MY_DB_NAME');
    // ...
    }
    campaigns.php
    functions
    clients.php

    View Slide

  126. function createUpdateLoginClient($client){
    $password_hash = password_hash($client['password'], PASSWORD_DEFAULT);
    $id = $client['id'];
    $dbh = getNewPDOInstance(‘login_clients');
    if($id){
    // ...
    }
    if($client['password'] && $id){
    // ...
    }
    if(!$id){
    $client_id = createClient($client['user_name'], $client['email'], $
    // ...
    }
    if($result){
    return true;
    } else{
    return true;
    }
    }
    function createClient($email){
    $dbh = getNewPDOInstance('MY_DB_NAME');
    // ...
    }
    campaigns.php
    functions
    clients.php

    View Slide

  127. function createUpdateLoginClient($client){
    $password_hash = password_hash($client['password'], PASSWORD_DEFAULT);
    $id = $client['id'];
    $dbh = getNewPDOInstance(‘login_clients');
    if($id){
    // ...
    }
    if($client['password'] && $id){
    // ...
    }
    if(!$id){
    $client_id = createClient($client['user_name'], $client['email'], $
    // ...
    }
    if($result){
    return true;
    } else{
    return true;
    }
    }
    function createClient($email){
    $dbh = getNewPDOInstance('MY_DB_NAME');
    // ...
    }
    campaigns.php
    functions
    clients.php

    View Slide

  128. index.php
    functions
    error_reporting(0);
    //ini_set('display_errors', E_ALL);
    $IN_PRODUCTION = true;
    $root_prefix = true||$IN_PRODUCTION?”/campaign”:"";
    $rootd = $_SERVER['DOCUMENT_ROOT'].$root_prefix."/";
    require $rootd.'/lib/init/init.php';
    $do = _get("do");
    $get = _get("get");
    $page = _get("page");
    $app = _get("app");
    $folder = _get("folder");
    if (isset($app)) {
    header('Content-Type: application/json');
    $path = $rootd.'lib/app/'.$app.'.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $path;
    }elseif (isset($do)) {
    $path = $rootd.'lib/do/'.$do.'.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $path;

    View Slide

  129. index.php
    functions
    if($folder){
    $path = $rootd.'lib/get/'.$folder.'/'.$get.'.php';
    }else{
    $path = $rootd.'lib/get/'.$get.'.php';
    }
    if (!file_exists($path))
    exit(header("location: /404"));
    include $path;
    } elseif (isset($page)) {
    $path = $rootd.'lib/pages_data/'.$page.'.php';
    if (!file_exists($path)){
    error_log('lol ' . $path);
    exit(header("location: /404"));
    }
    include $rootd.'lib/init/html.php';
    include $path;
    } elseif (isset($_SESSION['my_id'])) {
    $path = $rootd.'lib/pages_data/home.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $rootd.'lib/init/html.php';
    include $path;
    } else {
    $path = $rootd.'lib/pages_data/sign-up.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $rootd.'lib/init/html.php';
    include $path;
    }

    View Slide

  130. index.php
    functions
    if($folder){
    $path = $rootd.'lib/get/'.$folder.'/'.$get.'.php';
    }else{
    $path = $rootd.'lib/get/'.$get.'.php';
    }
    if (!file_exists($path))
    exit(header("location: /404"));
    include $path;
    } elseif (isset($page)) {
    $path = $rootd.'lib/pages_data/'.$page.'.php';
    if (!file_exists($path)){
    error_log('lol ' . $path);
    exit(header("location: /404"));
    }
    include $rootd.'lib/init/html.php';
    include $path;
    } elseif (isset($_SESSION['my_id'])) {
    $path = $rootd.'lib/pages_data/home.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $rootd.'lib/init/html.php';
    include $path;
    } else {
    $path = $rootd.'lib/pages_data/sign-up.php';
    if (!file_exists($path))
    exit(header("location: /404"));
    include $rootd.'lib/init/html.php';
    include $path;
    }

    View Slide

  131. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  132. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  133. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  134. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  135. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  136. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  137. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  138. // Init MySQL logins
    function getNewPDOInstance($db_name) {
    require $_SERVER['DOCUMENT_ROOT']."/master_config.php";
    $db_hostname = $lab_hostname;
    $db_username = $lab_username;
    $db_password = $lab_password;
    if($is_live & $db_name == “MY_DB_NAME” ||
    $db_name == “OTHER_DB_NAME” ||
    $db_name == “YET_ANOTHER_DB_NAME”
    ){
    $db_hostname = $tracking_machine_hostname;
    $db_username = $tracking_machine_username;
    $db_password = $tracking_machine_password;
    }
    if($is_live & $db_name == "login"){
    $db_hostname = $login_machine_hostname;
    $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  139. $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    if($is_live & $db_name == “login_clients"){
    $db_hostname = $login_clients_machine_hostname;
    $db_username = $login_clients_machine_username;
    $db_password = $login_clients_machine_password;
    }
    $dbh = null;
    try {
    $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4
    $db_password);
    } catch(PDOException $e) {
    error_log("Error in newPDOInstance: ".$e->getMessage());
    print_r($e);
    return false;
    }
    //print_r($e);
    return $dbh;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  140. $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    if($is_live & $db_name == “login_clients"){
    $db_hostname = $login_clients_machine_hostname;
    $db_username = $login_clients_machine_username;
    $db_password = $login_clients_machine_password;
    }
    $dbh = null;
    try {
    $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4
    $db_password);
    } catch(PDOException $e) {
    error_log("Error in newPDOInstance: ".$e->getMessage());
    print_r($e);
    return false;
    }
    //print_r($e);
    return $dbh;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  141. $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    if($is_live & $db_name == “login_clients"){
    $db_hostname = $login_clients_machine_hostname;
    $db_username = $login_clients_machine_username;
    $db_password = $login_clients_machine_password;
    }
    $dbh = null;
    try {
    $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4
    $db_password);
    } catch(PDOException $e) {
    error_log("Error in newPDOInstance: ".$e->getMessage());
    print_r($e);
    return false;
    }
    //print_r($e);
    return $dbh;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  142. $db_username = $login_machine_username;
    $db_password = $login_machine_password;
    }
    if($is_live & $db_name == “login_clients"){
    $db_hostname = $login_clients_machine_hostname;
    $db_username = $login_clients_machine_username;
    $db_password = $login_clients_machine_password;
    }
    $dbh = null;
    try {
    $dbh = new PDO("mysql:host=$db_hostname;dbname=$db_name;charset=utf8mb4
    $db_password);
    } catch(PDOException $e) {
    error_log("Error in newPDOInstance: ".$e->getMessage());
    print_r($e);
    return false;
    }
    //print_r($e);
    return $dbh;
    }
    index.php
    functions
    database
    connection.php

    View Slide

  143. index.php
    functions
    database $is_live = true;
    $lab_hostname = '...';
    $lab_username = '...';
    $lab_password = '...';
    $tracking_machine_hostname = '...';
    $tracking_machine_username = '...';
    $tracking_machine_password = '...';
    $login_machine_hostname = '...';
    $login_machine_username = '...';
    $login_machine_password = '...';
    $login_clients_machine_hostname = '...';
    $login_clients_machine_username = '...';
    $login_clients_machine_password = '...';
    master_config.php

    View Slide

  144. Refactor

    View Slide

  145. Encapsulate Elements With Getters & Setters

    View Slide

  146. index.php
    functions
    database $is_live = true;
    $lab_hostname = '...';
    $lab_username = '...';
    $lab_password = '...';
    $tracking_machine_hostname = '...';
    $tracking_machine_username = '...';
    $tracking_machine_password = '...';
    $login_machine_hostname = '...';
    $login_machine_username = '...';
    $login_machine_password = '...';
    $login_clients_machine_hostname = '...';
    $login_clients_machine_username = '...';
    $login_clients_machine_password = '...';
    master_config.php

    View Slide

  147. index.php
    functions
    database class Config
    {
    private const IS_LIVE = true;
    private const LAB_HOSTNAME = '...';
    private const LAB_USERNAME = '...';
    private const LAB_PASSWORD = '...';
    // ...
    public static function isLive(): bool
    {
    return self::IS_LIVE;
    }
    public static function labDBHostname(): string
    {
    return self::LAB_HOSTNAME;
    }
    }
    Config.php

    View Slide

  148. index.php
    functions
    database class Database
    {
    private $default = null;
    private $lab = null;
    private $login_clients = null;
    public function getConnectionFor(string $databaseName): \PDO
    {
    if (false === array_key_exists(
    $databaseName,
    get_object_vars($this))
    ) {
    throw new \InvalidArgumentException(‘Invalid DB requested');
    }
    if (null === $this->{$databaseName}) {
    $this->{$databaseName} =
    $this->createConnection($databaseName);
    }
    return $this->{$databaseName};
    }
    Config.php
    Database.php

    View Slide

  149. index.php
    functions
    database class Database
    {
    private $default = null;
    private $lab = null;
    private $login_clients = null;
    public function getConnectionFor(string $databaseName): \PDO
    {
    if (false === array_key_exists(
    $databaseName,
    get_object_vars($this))
    ) {
    throw new \InvalidArgumentException(‘Invalid DB requested');
    }
    if (null === $this->{$databaseName}) {
    $this->{$databaseName} =
    $this->createConnection($databaseName);
    }
    return $this->{$databaseName};
    }
    Config.php
    Database.php

    View Slide

  150. index.php
    functions
    database class Database
    {
    private $default = null;
    private $lab = null;
    private $login_clients = null;
    public function getConnectionFor(string $databaseName): \PDO
    {
    if (false === array_key_exists(
    $databaseName,
    get_object_vars($this))
    ) {
    throw new \InvalidArgumentException(‘Invalid DB requested');
    }
    if (null === $this->{$databaseName}) {
    $this->{$databaseName} =
    $this->createConnection($databaseName);
    }
    return $this->{$databaseName};
    }
    Config.php
    Database.php

    View Slide

  151. index.php
    functions
    database class Database
    {
    private $default = null;
    private $lab = null;
    private $login_clients = null;
    public function getConnectionFor(string $databaseName): \PDO
    {
    if (false === array_key_exists(
    $databaseName,
    get_object_vars($this))
    ) {
    throw new \InvalidArgumentException(‘Invalid DB requested');
    }
    if (null === $this->{$databaseName}) {
    $this->{$databaseName} =
    $this->createConnection($databaseName);
    }
    return $this->{$databaseName};
    }
    Config.php
    Database.php

    View Slide

  152. index.php
    functions
    database
    private function createConnection(string $databaseName): \PDO
    {
    if ('lab' === $databaseName) {
    return new \PDO(
    Config::labDBHostname(),
    Config::labDBUsername(),
    Config::labDBPassword()
    );
    }
    if ('login_clients' === $databaseName) {
    return new \PDO(
    // ...
    );
    }
    }
    Config.php
    Database.php

    View Slide

  153. index.php
    functions
    database
    private function createConnection(string $databaseName): \PDO
    {
    if ('lab' === $databaseName) {
    return new \PDO(
    Config::labDBHostname(),
    Config::labDBUsername(),
    Config::labDBPassword()
    );
    }
    if ('login_clients' === $databaseName) {
    return new \PDO(
    // ...
    );
    }
    }
    Config.php
    Database.php

    View Slide

  154. index.php
    repository
    database
    Config.php
    functions
    ClientRepository.php
    class ClientRepository
    {
    private $db;
    public function __construct(Database $db)
    {
    $this->db = $db;
    }
    function createUpdateLoginClient($client)
    {
    $password_hash = password_hash($client['password'], PASSWORD_DEFAUL
    $id = $client['id'];
    $dbh = this->db->getConnectionFor('login_clients');
    if ($id) {
    // ...
    }
    if ($client['password'] && $id) {
    // ...
    }
    if (!$id) {
    $client_id = $this->createClient($client['user_name'], $client[
    // ...
    }
    }

    View Slide

  155. Pluck Reusable Components

    View Slide

  156. {
    function createUpdateLoginClient($client)
    {
    if ($id) {
    // ...
    }
    if ($client['password'] && $id) {
    // ...
    }
    if (!$id) {
    $client_id = $this->createClient(
    $client['user_name'],
    $client[‘email'],
    $client['security_clearance']
    );
    // ...
    }
    }
    function createClient($email)
    {
    $this->db->getConnectionFor('login_clients');
    // ...
    }
    }
    index.php
    repository
    database
    Config.php
    functions
    ClientRepository.php

    View Slide

  157. {
    function createUpdateLoginClient($client)
    {
    if ($id) {
    // ...
    }
    if ($client['password'] && $id) {
    // ...
    }
    if (!$id) {
    $client_id = $this->createClient(
    $client['user_name'],
    $client[‘email'],
    $client['security_clearance']
    );
    // ...
    }
    }
    function createClient($email)
    {
    $this->db->getConnectionFor('login_clients');
    // ...
    }
    }
    index.php
    repository
    database
    Config.php
    functions
    ClientRepository.php

    View Slide

  158. Even consistent bad code is better than
    inconsistent spaghetti
    Remember

    View Slide

  159. class Client
    {
    private $id;
    private $email;
    private $password;
    private $securityClearance;
    public function __construct(
    int $id,
    string $email,
    string $password,
    string $securityClearance = 'CLIENT'
    ) {
    $this->id = $id;
    $this->email = $email;
    $this->password = $password;
    $this->securityClearance = $securityClearance;
    }
    public function getEmail(): string
    {
    return $this->email;
    }
    public function getPassword(): string
    index.php
    repository
    database
    Config.php
    functions
    Client.php
    model

    View Slide

  160. index.php
    repository
    database
    Config.php
    functions
    Client.php
    model
    class Client
    {
    private $id;
    private $email;
    private $password;
    private $securityClearance;
    public function __construct(
    int $id,
    string $email,
    string $password,
    string $securityClearance = 'CLIENT'
    ) {
    $this->id = $id;
    $this->email = $email;
    $this->password = $password;
    $this->securityClearance = $securityClearance;
    }
    public function getEmail(): string
    {
    return $this->email;
    }
    public function getPassword(): string

    View Slide

  161. index.php
    repository
    database
    Config.php
    functions
    Client.php
    model
    class Client
    {
    private $id;
    private $email;
    private $password;
    private $securityClearance;
    public function __construct(
    int $id,
    string $email,
    string $password,
    string $securityClearance = 'CLIENT'
    ) {
    $this->id = $id;
    $this->email = $email;
    $this->password = $password;
    $this->securityClearance = $securityClearance;
    }
    public function getEmail(): string
    {
    return $this->email;
    }
    public function getPassword(): string

    View Slide

  162. index.php
    repository
    database
    Config.php
    functions
    Client.php
    model
    class Client
    {
    private $id;
    private $email;
    private $password;
    private $securityClearance;
    public function __construct(
    int $id,
    string $email,
    string $password,
    string $securityClearance = 'CLIENT'
    ) {
    $this->id = $id;
    $this->email = $email;
    $this->password = $password;
    $this->securityClearance = $securityClearance;
    }
    public function getEmail(): string
    {
    return $this->email;
    }
    public function getPassword(): string

    View Slide

  163. {
    function createUpdateLoginClient(Client $client)
    {
    $password_hash = password_hash($client->getPassword(), PASSWORD_DEF
    $this->db->getConnectionFor('login_clients');
    if ($client->getId()) {
    // ...
    }
    if ($client->getPassword() && $client->getId()) {
    // ...
    }
    if (!$client->getId()) {
    $client_id = $this->createClient($client);
    // ...
    }
    }
    function createClient(Client $client)
    {
    $this->db->getConnectionFor('login_clients');
    // ...
    }
    }
    index.php
    repository
    database
    Config.php
    functions
    ClientRepository.php
    model

    View Slide

  164. {
    function createUpdateLoginClient(Client $client)
    {
    $password_hash = password_hash($client->getPassword(), PASSWORD_DEF
    $this->db->getConnectionFor('login_clients');
    if ($client->getId()) {
    // ...
    }
    if ($client->getPassword() && $client->getId()) {
    // ...
    }
    if (!$client->getId()) {
    $client_id = $this->createClient($client);
    // ...
    }
    }
    function createClient(Client $client)
    {
    $this->db->getConnectionFor('login_clients');
    // ...
    }
    }
    index.php
    repository
    database
    Config.php
    functions
    ClientRepository.php
    model

    View Slide

  165. Identify & Eleminate Lava Flows As You Go

    View Slide

  166. View Slide

  167. Take Time To Analyse The Domain Before Starting Work

    View Slide

  168. Develop A Model Before Implementation, Rather Than Concurrently

    View Slide

  169. Don’t touch
    that class

    View Slide

  170. It’s the heart of the
    application
    Don’t touch
    that class

    View Slide

  171. THE BLOB

    View Slide

  172. Single class with large number of properties &
    methods (>60 is typical)
    Symptoms

    View Slide

  173. Single class encapsulates a disparate collection
    of properties & methods
    Symptoms

    View Slide

  174. Class is too inefficient or excessively complex
    for reuse and testing, or uses excessive
    resources even for simple operations
    Symptoms

    View Slide

  175. Functionality is added piecemeal without ever
    refactoring components out
    Typical Causes

    View Slide

  176. Lack of object oriented architecture
    Typical Causes

    View Slide

  177. Lack of any architecture
    Typical Causes

    View Slide

  178. Lack of architectural enforcement
    Typical Causes

    View Slide

  179. class ReportFactory {
    /**
    * class to query the db for reports grids while using PDO prepared queri
    * temporary tables to increase performance. can return both a main selec
    * and several 'tmp_tables' queries which are required for the main query
    *
    * tmp tables must be executed first in the calling script as they provid
    * cleanup operations and facilitate the execution of the main query.
    *
    * example usage:
    *
    * $factory = new ReportFactory;
    * $factory->setSuperCampaign($super);
    * $factory->setReportType('persona');
    * $factory->addSortColumn($col, $order);
    *
    * foreach($factory->getTmpTablesSql() AS $tmp) {
    * $stmt = $conn->prepare($tmp);
    * $stmt->execute($pdo_tmp_params);
    * }
    *
    * $stmt = $conn->prepare($factory->getSql());
    * $stmt->execute($pdo_params);
    * $array = $stmt->fetchAll();
    *
    * note: $pdo_params is an array of parameters such as 'clientId',
    reporting
    ReportFactory.php

    View Slide

  180. class ReportFactory {
    /**
    * class to query the db for reports grids while using PDO prepared queri
    * temporary tables to increase performance. can return both a main selec
    * and several 'tmp_tables' queries which are required for the main query
    *
    * tmp tables must be executed first in the calling script as they provid
    * cleanup operations and facilitate the execution of the main query.
    *
    * example usage:
    *
    * $factory = new ReportFactory;
    * $factory->setSuperCampaign($super);
    * $factory->setReportType('persona');
    * $factory->addSortColumn($col, $order);
    *
    * foreach($factory->getTmpTablesSql() AS $tmp) {
    * $stmt = $conn->prepare($tmp);
    * $stmt->execute($pdo_tmp_params);
    * }
    *
    * $stmt = $conn->prepare($factory->getSql());
    * $stmt->execute($pdo_params);
    * $array = $stmt->fetchAll();
    *
    * note: $pdo_params is an array of parameters such as 'clientId',
    reporting
    ReportFactory.php

    View Slide

  181. class ReportFactory {
    /**
    * class to query the db for reports grids while using PDO prepared queri
    * temporary tables to increase performance. can return both a main selec
    * and several 'tmp_tables' queries which are required for the main query
    *
    * tmp tables must be executed first in the calling script as they provid
    * cleanup operations and facilitate the execution of the main query.
    *
    * example usage:
    *
    * $factory = new ReportFactory;
    * $factory->setSuperCampaign($super);
    * $factory->setReportType('persona');
    * $factory->addSortColumn($col, $order);
    *
    * foreach($factory->getTmpTablesSql() AS $tmp) {
    * $stmt = $conn->prepare($tmp);
    * $stmt->execute($pdo_tmp_params);
    * }
    *
    * $stmt = $conn->prepare($factory->getSql());
    * $stmt->execute($pdo_params);
    * $array = $stmt->fetchAll();
    *
    * note: $pdo_params is an array of parameters such as 'clientId',
    reporting
    ReportFactory.php

    View Slide

  182. CampaignID DateTime Event
    1234 2019-01-01 13:37:00 click
    1234 2019-01-01 12:34:56 view
    9876 2019-01-02 18:00:01 view
    1234 2019-01-02 23:33:32 click
    … … …
    … … …
    … … …

    View Slide

  183. CampaignID DateTime Event
    1234 2019-01-01 13:37:00 click
    1234 2019-01-01 12:34:56 view
    9876 2019-01-02 18:00:01 view
    1234 2019-01-02 23:33:32 click
    … … …
    … … …
    … … …
    CampaignID Date Clicks Likes Follows …
    1234 2019-01-01 39480120 1398473 32424 …
    3482 2019-01-01 23482134 235019934 94512 …
    9876 2019-01-02 9324243 123489132 405202 …
    9231 2019-01-02 1249823 509847 3248 …

    View Slide

  184. CampaignID DateTime Event
    1234 2019-01-01 13:37:00 click
    1234 2019-01-01 12:34:56 view
    9876 2019-01-02 18:00:01 view
    1234 2019-01-02 23:33:32 click
    … … …
    … … …
    … … …
    CampaignID Date Event Total
    1234 2019-01-01 click 1398473
    1234 2019-01-01 view 235019934
    9876 2019-01-02 view 123489132
    1234 2019-01-02 click 509847
    CampaignID Date Event Total
    1234 2019-01-01 click 1398473
    1234 2019-01-01 view 235019934
    9876 2019-01-02 view 123489132
    1234 2019-01-02 click 509847
    CampaignID Date Event Total
    1234 2019-01-01 click 1398473
    1234 2019-01-01 view 235019934
    9876 2019-01-02 view 123489132
    1234 2019-01-02 click 509847
    CampaignID Date Clicks Likes Follows …
    1234 2019-01-01 39480120 1398473 32424 …
    3482 2019-01-01 23482134 235019934 94512 …
    9876 2019-01-02 9324243 123489132 405202 …
    9231 2019-01-02 1249823 509847 3248 …

    View Slide

  185. Identify Relationships According To Contracts

    View Slide

  186. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  187. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  188. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  189. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  190. Refactor

    View Slide

  191. Identify Natural Homes For The Contracts

    View Slide

  192. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  193. class QueryBuilder {
    public function addSortColumn($col, $order) {}
    public function setReportType($report_type) {}
    public function addTmpOr($status) {}
    public function setWhere($where) {}
    // ...
    }
    reporting
    QueryBuilder.php
    Tmp

    View Slide

  194. class ReportFactory {
    private $queryBuilder;
    private $sql;
    public function __construct()
    {
    $this->queryBuilder = new QueryBuilder();
    }
    public function getQueryBuilder()
    {
    return $this->queryBuilder;
    }
    public function getSql()
    {
    return $sql;
    }
    // ...
    }
    reporting
    ReportFactory.php
    Tmp

    View Slide

  195. function someAction() {
    $factory = new ReportFactory();
    // ...
    $factory->addWhere('status = '. $st);
    $factory->addTmpOr('(c.status <= '.$st.')');
    $factory->addWhere('(time_start <= "'
    . date("Y-m-d H:i:s", time())
    . '" OR time_start is null)’
    );
    }
    reporting
    Controller.php
    Tmp

    View Slide

  196. function someAction() {
    $factory = new ReportFactory();
    $builder = $factory->getQueryBuilder();
    // ...
    $builder->addWhere('status = '. $st);
    $builder->addTmpOr('(c.status <= '.$st.')');
    $builder->addWhere('(time_start <= "'
    . date("Y-m-d H:i:s", time())
    . '" OR time_start is null)’
    );
    }
    reporting
    Controller.php
    Tmp

    View Slide

  197. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}
    function addSortColumn($col, $order) {}
    function setReportType($report_type) {}
    function addTmpOr($status) {}
    function setWhere($where) {}
    function addWhere($where) {}
    function getWhere() {}
    function setColumns($col) {}
    function addColumn($col) {}
    function setParameters($params) {}
    function addParam($param) {}

    View Slide

  198. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}

    View Slide

  199. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}

    View Slide

  200. class TemporaryTable {
    public function createTmpTables(string $reportType) {}
    private function createSuperCampaignsTable() {}
    private function createCampaignGroupsTable() {}
    private function createSuperCampaignsStatusesTable() {}
    private function createCampaignGroupsStatusesTable() {}
    private function createSuperCampaignsStatsTable() {}
    private function createCampaignGroupsStatsTable() {}
    private function createCampaignsStatsTable() {}
    // ...
    }
    reporting
    TmpTable.php
    Tmp
    QueryBuilder.php

    View Slide

  201. class TemporaryTable {
    public function createTmpTables(string $reportType)
    {
    switch ($reportType) {
    case 'clicks':
    $this->createCampaignGroupsTable();
    $this->createCampaignGroupsStatsTable();
    $this->createClicksMetricsTable();
    break;
    case 'views':
    // ...
    break;
    }
    }
    }
    reporting
    TmpTable.php
    Tmp
    QueryBuilder.php

    View Slide

  202. function _createTmpTables() {}
    function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}
    function _createSuperCampaignsTable() {}
    function _createCampaignGroupsTable() {}
    function _createSuperCampaignsStatusesTable() {}
    function _createCampaignGroupsStatusesTable() {}
    function _createSuperCampaignsStatsTable() {}
    function _createCampaignGroupsStatsTable() {}
    function _createCampaignsStatsTable() {}

    View Slide

  203. function _getScheduledReportsSelectSql() {}
    function _getCampaignSelectSql() {}
    function _getPersonaSelectSql() {}
    function _getImageSelectSql() {}
    function _getTitleSelectSql() {}
    function _getOverviewSelectSql() {}
    function _getExcelDownloadSelectSql() {}
    function _getCampaignsOverviewSelectSql() {}

    View Slide

  204. class MetricsQuery
    {
    private $tmpTable;
    public function __construct(TmpTable $tmpTable)
    {
    $this->tmpTable = $tmpTable;
    }
    public function getScheduledReportsSelectSql() {}
    public function getCampaignSelectSql() {}
    public function getPersonaSelectSql() {}
    public function getImageSelectSql() {}
    public function getTitleSelectSql() {}
    public function getOverviewSelectSql() {}
    // ...
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php

    View Slide

  205. Remove Couplings

    View Slide

  206. Controller

    View Slide

  207. ReportFactory
    getQueryBuilder
    getSql

    Controller

    View Slide

  208. ReportFactory
    getQueryBuilder
    getSql

    Controller

    View Slide

  209. ReportFactory
    getQueryBuilder
    getSql

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    View Slide

  210. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    View Slide

  211. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    View Slide

  212. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    View Slide

  213. abstract class MetricsReport extends ReportFactory
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract public function setQueryConditions();
    public function getReport()
    {
    $this->setQueryConditions();
    $sql = $this->getSql();
    $query = $this->pdo->prepare($sql);
    return $query->execute();
    }
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php
    MetricsReport.php

    View Slide

  214. abstract class MetricsReport extends ReportFactory
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract public function setQueryConditions();
    public function getReport()
    {
    $this->setQueryConditions();
    $sql = $this->getSql();
    $query = $this->pdo->prepare($sql);
    return $query->execute();
    }
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php
    MetricsReport.php

    View Slide

  215. abstract class MetricsReport extends ReportFactory
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract public function setQueryConditions();
    public function getReport()
    {
    $this->setQueryConditions();
    $sql = $this->getSql();
    $query = $this->pdo->prepare($sql);
    return $query->execute();
    }
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php
    MetricsReport.php

    View Slide

  216. function someAction() {
    $factory = new ReportFactory();
    // ...
    $factory->addWhere('status = '. $st);
    $factory->addTmpOr('(c.status <= '.$st.')');
    $factory->addWhere('(time_start <= "'
    . date("Y-m-d H:i:s", time())
    . '" OR time_start is null)’
    );
    }
    reporting
    Controller.php
    Tmp

    View Slide

  217. function someAction() {
    $report = new SomeMetricsReport();
    return $report->getResult();
    }
    reporting
    Controller.php
    Tmp

    View Slide

  218. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    View Slide

  219. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    MetricsReport
    ReportFactory
    setQueryConditions

    View Slide

  220. abstract class MetricsReport extends ReportFactory
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract public function setQueryConditions();
    public function getReport()
    {
    $this->setQueryConditions();
    $sql = $this->getSql();
    $query = $this->pdo->prepare($sql);
    return $query->execute();
    }
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php
    MetricsReport.php

    View Slide

  221. abstract class MetricsReport extends ReportFactory
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract public function getQuery();
    public function getReport()
    {
    $sql = $this->getQuery();
    $query = $this->pdo->prepare($sql);
    return $query->execute();
    }
    }
    reporting
    MetricsQuery.php
    Tmp
    QueryBuilder.php
    TmpTable.php
    MetricsReport.php

    View Slide

  222. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    getQuery
    MetricsReport
    ReportFactory

    View Slide

  223. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    getQuery
    MetricsReport
    ReportFactory

    View Slide

  224. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql
    ReportFactory
    getQueryBuilder
    getSql


    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    MetricsReport
    getQuery

    View Slide

  225. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    MetricsReport
    getQuery

    View Slide

  226. View Slide

  227. interface TmpTableSql
    {
    public function getSql(): string;
    }
    reporting
    TmpTableSql.php
    Query
    Tmp

    View Slide

  228. class SomeTempTable implements TmpTableSql
    {
    private $sql;
    public function __construct(TmpTableSql $sql)
    {
    $this->sql = $sql;
    }
    public function getSql(): string
    {
    return $this->sql->getSql() . ' CREATE TABLE ... ;';
    }
    }
    reporting
    TmpTableSql.php
    Query
    Tmp
    SomeTmpTable.php

    View Slide

  229. The solution doesn’t have to be perfect…
    Remember

    View Slide

  230. … Just less shit than it was before
    Remember

    View Slide

  231. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    TmpTable
    createTmpTables
    createCampaignsTable
    createStatsTable

    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    MetricsReport
    getQuery

    View Slide

  232. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    << interface >>
    TmpTable
    getSql
    Controller
    QueryBuilder
    addColumn
    addSortColumn
    addWhere

    MetricsReport
    getQuery

    View Slide

  233. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    << interface >>
    TmpTable
    getSql
    Controller
    MetricsReport
    getQuery

    View Slide

  234. class MetricsReport
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract protected function setupTables(): TmpTable;
    abstract protected function getSql(): string;
    public function getReport()
    {
    $tmpTables = $this->connection->prepare($this->setupTables());
    $tmpTables->execute();
    $query = $this->connection->prepare($this->getSql());
    $query->execute();
    return $query->fetchAll();
    }
    reporting
    TmpTableSql.php
    Query
    Tmp
    MetricsReport.php
    SomeTmpTable.php

    View Slide

  235. class MetricsReport
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract protected function setupTables(): TmpTable;
    abstract protected function getSql(): string;
    public function getReport()
    {
    $tmpTables = $this->connection->prepare($this->setupTables());
    $tmpTables->execute();
    $query = $this->connection->prepare($this->getSql());
    $query->execute();
    return $query->fetchAll();
    }
    reporting
    TmpTableSql.php
    Query
    Tmp
    MetricsReport.php
    SomeTmpTable.php

    View Slide

  236. class MetricsReport
    {
    private $connection;
    public function __construct(\PDO $connection)
    {
    $this->connection = $connection;
    }
    abstract protected function setupTables(): TmpTable;
    abstract protected function getSql(): string;
    public function getReport()
    {
    $tmpTables = $this->connection->prepare($this->setupTables());
    $tmpTables->execute();
    $query = $this->connection->prepare($this->getSql());
    $query->execute();
    return $query->fetchAll();
    }
    reporting
    TmpTableSql.php
    Query
    Tmp
    MetricsReport.php
    SomeTmpTable.php

    View Slide

  237. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    << interface >>
    TmpTable
    getSql
    Controller
    MetricsReport
    setupTables
    getSql
    getReport

    View Slide

  238. MetricsQuery
    getCampaignSelectSql
    getImageSelectSql
    getTitleSelectSql

    << interface >>
    TmpTable
    getSql
    Controller
    MetricsReport
    setupTables
    getSql
    getReport

    View Slide

  239. << interface >>
    TmpTable
    getSql
    Controller
    MetricsReport
    setupTables
    getSql
    getReport

    View Slide

  240. reporting
    TmpTableSql.php
    Query
    Tmp
    MetricsReport.php
    SomeTmpTable.php

    View Slide

  241. TmpTableSql.php
    Query
    MetricsReport.php
    SomeTmpTable.php

    View Slide

  242. View Slide

  243. Code Review

    View Slide

  244. The project is almost
    finished, we’ve been
    a great team

    View Slide

  245. The project is almost
    finished, we’ve been
    a great team
    Everybody got on so
    well & worked so
    hard together

    View Slide

  246. The project is almost
    finished, we’ve been
    a great team
    Everybody got on so
    well & worked so
    hard together
    It’s going to be a
    disaster

    View Slide

  247. FEAR
    OF
    SUCCESS

    View Slide

  248. People involved in the project begin to obsess
    over things which could go wrong
    Symptoms

    View Slide

  249. Insecurities about professional competence
    begin to surface
    Symptoms

    View Slide

  250. Conflicts between team members arise just
    before project completion
    Symptoms

    View Slide

  251. Irrational decisions are made & inappropriate
    resolutions to problems are taken
    Symptoms

    View Slide

  252. Negative publicity on the project is generated
    by people involved with the work
    Symptoms

    View Slide

  253. People have ‘termination issues’
    Typical Causes

    View Slide

  254. Team members build relationships which they
    subconsciously don’t want to break up at the
    end of the project
    Typical Causes

    View Slide

  255. People have assumed roles / status within the
    team which they don’t want to lose when the
    project is completed
    Typical Causes

    View Slide

  256. People do crazy things
    Typical Causes

    View Slide

  257. Declare Success

    View Slide

  258. Celebrate Achievements & Praise Team Members

    View Slide

  259. We spent 6 months
    waiting for
    requirements from
    management

    View Slide

  260. We spent 6 months
    waiting for
    requirements from
    management
    But they’ll cancel
    the project unless
    it’s completed soon

    View Slide

  261. We spent 6 months
    waiting for
    requirements from
    management
    They’ve given us 4
    weeks
    But they’ll cancel
    the project unless
    it’s completed soon

    View Slide

  262. FIRE DRILL

    View Slide

  263. Management prevents development teams
    from making progress by telling them to wait
    Symptoms

    View Slide

  264. Management prevents development teams
    from making progress by giving unclear or
    conflicting requirements
    Symptoms

    View Slide

  265. After a few months management calls an
    emergency launch meeting, setting unrealistic
    deadlines & threatening to cancel the project
    Symptoms

    View Slide

  266. Managers have too many projects competing
    for their attention at any given time
    Typical Causes

    View Slide

  267. Shelter

    View Slide

  268. Iterative Progress

    View Slide

  269. Internal Progress

    View Slide

  270. External Progress

    View Slide

  271. Notes

    View Slide

  272. Pick The Low Hanging Fruit

    View Slide

  273. Learning & Experience

    View Slide

  274. Training

    View Slide

  275. Leadership

    View Slide

  276. Teamwork

    View Slide

  277. View Slide

  278. View Slide

  279. WE ARE HIRING!

    View Slide