Burning tree image

Working code isn’t enough #2

This is the second in (what is apparently becoming) a series of posts about good and bad coding styles from my personal perspective.

In each post I show two possible fully working solutions to a code problem to highlight the opposite extremes of what I feel are good and bad programming styles.

Problem: The Path Problem on TestDome


Bad Solution

  public function cd($newPath)
    if ($newPath[0]!=='/') {
      $this->currentPath = implode('/',array_reduce(explode('/',$newPath),function($new, $cd){
        return ($cd=='..')?array_slice($new,0,-1):array_merge($new,[$cd]);
    } else {
      $this->currentPath = $newPath;

Good Solution

  const SEPERATOR = '/';

  public function cd($newPath)
    // turn our path strings into arrays to simplify processing ----------
    $currentPathBits = explode(static::SEPERATOR, $this->currentPath);
    $cdPathBits = explode(static::SEPERATOR, trim($newPath));

    // check for special cases -------------------------------------------
    if (count($cdPathBits) == 0) {
      // no path change specified so nothing to do
    if ($cdPathBits[0] == '') {
      // absolute path specified - so reset $currentPathBits
      $currentPathBits = [];
    // core path bit processing ------------------------------------------
    foreach ($cdPathBits as $cdPathBit) {
      switch ($cdPathBit) {
        case '..':
          // move up a directory - remove the last bit of the current address bits
        case '.':
          // current directory - nothing to change in this situation
          // new directory bit - so append it to the path
          array_push($currentPathBits, $cdPathBit);
    // now turn the $currentPathBits back into the path string -----------
    $this->currentPath = implode(static::SEPERATOR, $currentPathBits);

Is the good vs badness self explanatory? Maybe not. The Bad solution is very clever isn’t it? Aren’t I showing off how clever I am. How well I can wield the arcane magic that is the shitty PHP core functions. I’ve folded it all together so there’s hardly any wasted characters. And “it works – so what are you complaining about?”

I am complaining! Anyone who writes web or scripting code in that bad solution style and expects it to get into production, is either: showing off; trying to make it impossible to fire them; or pushing up the Autistic Spectrum. And with the exception of the Autist, I refer the others to the reply given in the case of Arkell v. Pressdram

Try writing code that other people can grok. Code that still makes sense to you in 12 months time when you come back to it. Maybe code that even non-developers have a chance of roughly understanding. Code that you can change quickly and safely if a problem is found or a great opportunity requires a nimble pivot.

If anyone ends up reading any of these and is interested in discussing why I think they’re good and bad, I’m up for that 🙂


Working code isn’t enough #1

This is the first in (what might be) a series of posts about good and bad coding styles from my personal perspective.

In each post I’m going to show two possible fully working solutions to a code problem to highlight the opposite extremes of what I feel are good and bad programming styles.

Problem: Thesaurus question on TestDome


Bad Solution

    public function getSynonyms($word)
        return json_encode(['word'=>$word,'synonyms'=>@$this->thesaurus[$word]?:[]]);

Good Solution

    public function getSynonyms($word)
        $synonyms = [];
        if (array_key_exists($word,$this->thesaurus)) {
            $synonyms = $this->thesaurus[$word];
        $result = [
        return json_encode($result);

Hopefully the good vs badness is self explanatory, but if anyone ends up reading any of these and is interested in discussing why I think they’re good and bad, I’m up for that too 🙂

BuddyPress activity streams vs forums

I’m generally happy with the way that WordPress / BuddyPress combo is working for the new community website I’ve put together (Ivybridge Matters)

But one thing I think our users generally find confusing is understanding the difference between posting comments directly into the activity stream for a group, versus going into the forum and creating posts and replying in there.

Generally comments in the activity stream disappear quickly for all but the quietest groups, and yet it’s the first thing users are presented with, and the most likely way they are liable to try to post updates and questions – which are soon lost ‘off the bottom’.

To encourage (force?) users to use the Forum, I’ve tweaked the BuddyPress code so that only a groups’ Admins and Moderators can post comments directly into a groups activity stream. WARNING: This is not a generic solution. The code change, changes it for all groups and I’ve only tested it (and only expect it) to work for BuddyPress 1.5.

The tweaked file is available for download here and is a variation of the file

Given that I’m using BuddyPress 1.5 (with Forums for Groups installed), and the theme ‘BuddyPress Colours‘ (version 1), to get this to work I simply had to:

  1. Create the directory wp-content/themes/buddypress-colours/activity
  2. Extract post-form.php from the zip file and copy it into the directory created in Step 1
All seems to be working well for me. Feel free to use this as you will from my point of view. Let me know how you get on, and I’d appreciate any comments or suggestion.
Ideally it would be driven from BuddyPress / Settings / ‘Restrict activity stream comments to Admin & Moderators only’ yes/no. But I haven’t the time now to look into that for now.

Propel: Joining to non-propel mapped tables – conquered

For some time I’ve been avoiding figuring this out. Today I ‘faced my fear’ 🙂

I have a core project which uses Propel for DB access. This core project has an ‘extension’ architecture (plugins), and many of these extensions also benefit from using Propel.

I did not want to make the core project dependent on the extensions (obviously) – but have ended up with the extensions not being able to easily link with the Propel in the core. For example, a Print Job Management extension, the extension code has a PrintJobUser table (php name) with UserId column (php name) which is effectively a reference to the User table in the core project. Now because the extension has it’s own schema Propel doesn’t understand it’s a relationship.

In the past I’ve written simple code in the Propel derived classes to make it easy to jump back to the core Propel Objects. But in this case I simply needed the full collection of PrintJobUser objects rehydrated with ‘virtual columns’ for 3 fields from the core projects User table (oh – and a couple of PrintJob propel joins too).

So effectively what I’ve done here is joining Propel mapped tables with a non-Propel mapped table with the extra fields appearing as virtual columns.

The key functions are actually on Criteria rather than ModelCriteria – addJoinObject() and addAsColumn() – but using these directly messes up ModelCriteria slightly necessitating a call to addSelectColumn() to ensure the main class we’re trying to hydrate still has it’s fields there.

public function getAllUsers() {
        return PrintJobUserQuery::create()
            ->addJoinObject(new Join("print_job_user.user_id", "user.userId",
            ->addAsColumn('UserName', 'user.name')
            ->addAsColumn('UserLogin', 'user.login')
            ->addAsColumn('UserSecurityLevel', 'user.userSecurityLevel')

There are a couple of gotchas which are worth noting:

  • The call to addSelectColumn() must come first and must use the MySQL table name.
    Calling addAsColumn() seems to set up the Criteria in such a way that when the select statement is built it no longer ‘automatically’ adds the columns for the class we’re querying, and hence we have to explicitly do it ourself. It has to come first or the fields don’t map properly during the rehydration process (at least that’s how it seemed to work for me?!?)
  • The call to addJoinObject() uses the MySQL table names and field names
  • The call to addJoinObject() must first have the class we’re building a query for, whether it’s left or right join, otherwise the FROM clause seems to come out wrong
  • The calls to addAsColumn() must have the first parameter (the alias) is in the PHP form and will map directly to the getXXXXX calls to return these virtual columns on the hydrated class
  • The calls to addAsColumn() must have the second parameter (the sql select element) in the MySQL table.column format as this just drops verbatim into the select statement