September 4, 2023
by Matt Raines

Looking back to 2009

« 2007 | 2008 | 2009 | 2010 | 2011 »

Tim surveys the construction work at 98 Sandgate High Street

Tim spent much of the year knee deep in construction material rebuilding our office in a historic building on Sandgate High Street, expertly aided by Darren Briddock. It had recently been a newsagent and once upon a time a stationers and a tobacconists but had been closed for over a year and it was great to bring it back to life. As I recall, every floorboard lifted revealed another problem that needed fixing.

We celebrated by watching Gary Numan sing in the rain and with an opening party where there may have been plenty of leftover canapes but we bravely worked our way through all the Kentish sparkling wine and Gurkha beer.

Our bespoke logistics platform for Seymour Transport replaced an existing Access database and allowed them to plan, load, deliver and invoice for goods including steel for the London Olympics and manage vehicles and drivers. It was a delight working with a small business where we were able to spend time shadowing their staff across the business to see how the system would actually be used in practice, test ideas, and build exactly what they needed to allow their staff to concentrate on what they’re good at instead of being bogged down in admin. This was our first major release in Symfony since the IPC contract work although work on our next generation Lib Dem CMS was already well under way too.

I was a little less than enamoured with the Access database itself though, railing at the time about such meaningful table names as tblCustExtraJoin, tblCustExtraJoinQuote, tblCustExtraJoinRec, tblBandsOver2Tonnes, and tblJobVehSub.

Having been unhappy with our existing provider for a little while and with more equipment to install, we moved to hosting our servers at the excellent Custodian Data Centre in Kent, who have been our colocation partners ever since. Tim was a little disappointed I didn’t choose the place that used to be a nuclear command and control bunker though.

Alliance Party website in 2009Consultancy included a stint at Shoreditch startup This Ad Will Change Your Life. They had a revolutionary vision to blur the boundaries between consumer and advertiser that I think probably presaged the influencer era but sadly I don’t think ever got the funding to deliver the product they had envisioned.

Other custom builds including a branch website for Habitat for Humanity, email marketing platform for Midas Training, payment processing for The Full Effect, and a rebrand of the Alliance Party of Northern Ireland’s website to look a bit more Star Trek.

Prater Raines website in 2009This was my first year organising the UK’s largest PHP conference at Kensington Olympia. Next year we’d move to the Business Design Centre in Islington. I was tasked with managing speakers, sponsors and the exhibition and all told I’m proud to have raised over over £40,000 from sponsors including Microsoft, Facebook, PayPal, Sun Microsystems, and Adobe across the two years. At our first TestFest event I was able to find a bug in PHP core and get it fixed, which I’m still stupidly proud of.

Tools of the trade

Location, location, location

The year in tech

Sample code

<?php

class ReportLoadByWeightRange extends Report
{

  private $customers;

  private function getCustomers()
  {
    if (!isset($this->customers)) {
      $this->customers = Doctrine::getTable("Customer")->findByIds($this->params["customers"]);
    }
    return $this->customers;
  }

  public function getTitle()
  {
    sfContext::getInstance()->getConfiguration()->loadHelpers("Date");
    $customer_names = array();
    foreach ($this->getCustomers() as $customer) {
      $customer_names[] = (string)$customer;
    }
    return "Deliveries by weight range for " . implode(" / ", $customer_names) . " between " .
              format_date($this->params["start_date"], "P") . " and " .
              format_date($this->params["end_date"], "P");
  }

  public function getColumns()
  {
    $columns = array("heading" => new ReportColumnString(""));
    $last_weight = 0;
    foreach ($this->params["ranges"] as $weight) {
      $columns["$last_weight"]
        = new ReportColumnFloat("$last_weight-$weight<acronym title=\"tonnes\">t</acronym>");
      $last_weight = $weight;
    }
    $columns["$last_weight"] = new ReportColumnFloat("$last_weight<acronym title=\"tonnes\">t</acronym>+");
    $columns["total"] = new ReportColumnFloat("Total");
    return $columns;
  }

  private function getHeadings()
  {
    return array(
      array("heading" => "Consignments"),
      array("heading" => "Weight (<acronym title=\"tonnes\">t</acronym>)"),
      array("heading" => "Charge (£)"),
      array("heading" => "Charge by weight (£/<acronym title=\"tonne\">t</acronym>)"),
      array("heading" => "Average consignment weight (<acronym title=\"tonnes\">t</acronym>)"));
  }

  private function addData($results, &$data, $weight, &$totals)
  {
    foreach (array("consignments", "weight", "charge") as $index => $field) {
      $data[$index][$weight] = $results[$field];
      $data[$index]["total"] += $results[$field];
      $totals[$index][$weight] += $results[$field];
      $totals[$index]["total"] += $results[$field];
    }
    $this->calculate($data, $weight);
  }

  private function calculate(&$data, $weight)
  {
    $precision = sfConfig::get("app_report_load_by_weight_range_weight_precision", 3);
    $data[3][$weight] = number_format($data[2][$weight] / $data[1][$weight], 2, ".", "");
    $data[4][$weight] = number_format($data[1][$weight] / $data[0][$weight], $precision, ".", "");
    $data[1][$weight] = number_format($data[1][$weight], $precision, ".", "");
  }

  public function getData()
  {
    $report = array(
      "groups"  => array("All selected customers" => $this->getHeadings()),
      "totals"  => array()
    );
    foreach (array_merge(array(0, "total"), $this->params["ranges"]) as $weight) {
      $report["groups"]["All selected customers"][0][$weight] = 0;
      $report["groups"]["All selected customers"][1][$weight] = 0;
      $report["groups"]["All selected customers"][2][$weight] = 0;
    }
    $query = Doctrine::getTable("Job")->createQuery("j")->
      select("COUNT(*) consignments")->
      addSelect("SUM(weight_in_reference_unit) weight")->
      addSelect("SUM(total_charge_in_pounds) charge")->
      innerJoin("j.Delivery d")->
      innerJoin("d.Run r")->
      where("customer_id = ?")->
      andWhere("weight_in_reference_unit >= ?")->
      andWhere("weight_in_reference_unit < ?")->
      andWhere("allocated = ?", true)->
      andWhere("deliver_at >= ?", $this->params["start_date"])->
      andWhere("deliver_at <= ?", $this->params["end_date"]);
    foreach ($this->getCustomers() as $customer) {
      $data = $this->getHeadings();
      $data[0]["total"] = $data[1]["total"] = $data[2]["total"] = 0;
      $last_weight = 0;
      foreach ($this->params["ranges"] as $weight) {
        $results = $query->fetchOne(
          array($customer->getId(), $last_weight, $weight), Doctrine::HYDRATE_ARRAY);
        $this->addData($results, $data, $last_weight, $report["groups"]["All selected customers"]);
        $last_weight = $weight;
      }
      $results = $query->fetchOne(
        array($customer->getId(), $last_weight, PHP_INT_MAX), Doctrine::HYDRATE_ARRAY);
      $this->addData($results, $data, $last_weight, $report["groups"]["All selected customers"]);
      $this->calculate($data, "total");

      $report["groups"][(string)$customer] = $data;
    }

    foreach (array_merge(array(0, "total"), $this->params["ranges"]) as $weight) {
      $this->calculate($report["groups"]["All selected customers"], $weight);
    }
    return $report;
  }

}