SilverStripe Development Standards: Start Coding Like a Pro

27 Sep

SilverStripe Development Standards: Start Coding Like a Pro

Standards? Who Cares?

Last winter, my wife and I found ourselves nearly freezing in our home, and I hired an on-call HVAC professional to fix a broken circulator pump in our heating system. The unforgiving winters we get in this part of the United States makes it hard to put off such a repair, and we were thankful to have someone come out on such short notice. He was there about five minutes before regretfully telling us that he could not perform the repair without first going back to his shop and get a new set of tools.

Naturally, my instinct was to blame my repair guy for being disorganized, but as it turned out, he had done everything right. We would continue to shiver in our cold house for another two hours not because of an absent-minded technician, but rather, because a narrow-minded builder in 1968 thought he was above the one thing that would have made this an easy problem to solve — standards.

Any time a group of people sharing the same skill are working together on shared or public material, standards will undoubtedly emerge from the system. If they don’t, that system is likely to fail amidst the mitigating forces of disorganization, redundancy, and ultimately, apathy and neglect. When working with open-source material, standards are paramount.

Still.. Who Cares? I’m the only one who sees my code.

Sort of.

SilverStripe is an open-source application. It’s rare that a project built on an open-source architecture will represent the work of only one developer. More often than not, a single developer’s code is working side-by-side with a number of other plugins, modules, and snippets, and of course, the core framework itself. Further, it’s short-sighted to assume that your project will never reach the eyes of another developer. You may not always be around, and who knows, the project may grow enough to warrant more cooks in the kitchen, so to speak. After all, isn’t that why they hired you? To make this website become better and grow?

In short, Don’t build your code using the same mindset that carpenter used to build my house.

Alright, I’m listening. What standards should I be following?

First of all, it’s important to understand that standards are not as permanent as the moon and stars. Standards are a living, breathing, dynamic set of constructs and ideas that area ready to adapt to changes in the landscape. Think of all the new building codes that must have come out when they discovered asbestos was bad for you! The web moves quickly, and standards need to be adaptive.

In light of that, I’m going to touch upon the standards I consider most important in both the SilverStripe framework and object-oriented PHP in general. Feel free to add any additional standards in the comments below.

Taxonomy

We’re going to be using a lot of the same terminology throughout this article, so let’s get some definitions out of the way.

Static variable: A property of a class itself, not instances thereof. Static variables are those that can be set using the class as a namespace, for example:

YourClass::$your_property = "your value";

Object variable: These are properties that are set at the object level, which will store a unique value for each instance of the class, for example:

$yourObject->yourProperty = "your value";

Static method: These are functions that are called using the class name as a namespace, for example:

UploadifyField::show_debug();

These methods are instance agnostic and can be affect all instances of the class globally, or to do simple input/output work, such as sanitizing a string. Those types of static methods are often called factory methods.

SiteTree::generateURLSegment($string);

Object method: A function run against a single instance of a class.

$myObject->disableEverything();

Standard #1: Naming Conventions

CamelCase? lowerCamelCase? like_underscores_instead?

Variables and functions take on a number of different formats for representing multiple words without using spaces. Standardizing these naming conventions provides something very valuable — a developer who is not familiar with the class definition can now infer what the purpose and scope is of these methods and properties.

lowercase_underscore

Used for static methods and static properties.

Good:

        static $records_per_page = 10;

        static function enable_debugging() {
		self::$debugging = true;
        }

Bad:

        static $recordsPerPage = 10;

        static function enableDebugging() {
		self::$debugging = true;
	}

Please note that this is a recently established standard. A lot of core SilverStripe methods have not yet caught up, including Object::addStaticVars(), Director::fileExists(), RSSFeed::linkToFeed(), and many more.

lowerCamelCase

Used for object methods and accessors.

Good:

	public function getUploadFolder() {
		return $this->uploadFolder;
	}

	public function addExtraClass($class) {
		$this->extraClasses[] = $class;
	}

Bad:

	public function get_upload_folder() {
		return $this->uploadFolder;
	}

	public function add_extra_class($class) {
		$this->extraClasses[] = $class;
	}

CamelCase

Used for functions and properties that are intended to be used by a template.

Good:

public $CustomHeading;

public function MyCustomFunction() {
	return "This text goes on the template";
}

Bad:

public $customHeading;

public function doCustomFunction() {
	return "This text goes on the template";
}

Also, note that the properties defined in your datamodel become available to the template, as well, so always be sure to follow this convention in your datamodel arrays.
Good:

static $db = array (
	'Featured' => 'Boolean'
);

Bad:

static $db = array (
	'is_featured' => 'Boolean'
);

alllowercase

Used for controller actions. Alphanumeric characters only.

Good:

public function showproduct($request) {
}

Bad:

public function showProduct($request) {
}

Standard #2: Specifying Visibility

PHP5 brought developers into a world of respectable support for object-oriented features, including the ability to set the visibility of properties and methods in a class. Visibility comes in three different flavors in PHP:

private: This property or method can only be accessed from within this class.
protected: This property or method can only be accessed from within this class or one of its descendants.
public: This property or method can be accessed anywhere.

If no visibility is specified, the property or method is considered public.

Let’s look at how this works. We’ll first define some properties and methods with varying visibility.

	private static $private_var;

	protected $protectedVar;

	private static function private_method($val) {}

	protected function protectedMethod() {}

Now let’s look at how visibility affects their usage:

	// Doesn't work. Trying to access a private var from the public space
	YourClass::$private_var = "value";

	// Works. Accessing it from within the class.
	class YourClass {
		public static function set_private_var($val) {
			self::$private_var = $val;
		}
	}

	// Doesn't work. Trying to access a protected property from the public space.
	$myObj->protectedVar = "value";

	// Works. Accessing it from within the class.
	class MyObject extends AnotherObject {
		public function setProtectedVar($val) {
			$this->protectedVar = $val;
		}
	}

	// Doesn't work. Trying to invoke a protected method from the public space
	$myObj->protectedMethod();

	// Works. Method is called from within the class, or one of its descendants
	public function runThatMethod() {
		$this->protectedMethod();
	}

Setting visibility is very important in object-oriented programming, because it helps to define the way you as the developer intend your code be used. It can also help “idiot proof” your code, lest someone alter a crucial property that breaks the application. Just imagine if some of SilverStripe’s private variables were public. We could get away with things like:

	Object::$statics = false;

Yikes!

Standard #3: Comments (Your friend and your enemy.)

This is more of a best practice than a standard. I’m a firm believer that over-commenting can be just as dangerous as under-commenting one’s code. Code changes frequently, especially in the early phases of a project, and often times, updating the comments supporting that code is of secondary priority. When this happens, we get a very dangerous situation in which the documentation is not accurately representing what the code is supposed to do. Case in point, my favorite submission to a recent StackOverflow thread:

/**
 * Always returns true.
 */
public boolean isAvailable() {
    return false;
}

Of course, this is an extreme, comical example of bad commenting, but it’s an effective microcosm of commenting becoming your enemy. Good comments are kept as concise as possible, and describe only the things that may not be obvious in the code. If you have to, use example code in <code> blocks to describe some use cases. Keep your comments focused on the nature of the function — it’s inputs and outputs, and general purpose. Comments should be agnostic of the application in which they’re used.

Good commenting:

/**
 * Cleans up the URL parameter to make sure it is
 * safe for database queries
 */

Bad commenting:

/**
 * Removes spaces, non-alphanumeric characters,
 * and the number 7 (the client hates 7's… lol) from the
 * url parameter. This method is called by ApplicationController.php
 * (line 324) on Tuesday nights during a lunar eclipse.
 */

The concept of a “clean” input is not only likely to change in the future, but it’s also fairly irrelevant information. All the user needs to know is that the function is used for sanitation. If he really wants to know more, he can look at the source code and infer exactly what’s going on. When you get too specific about the usage and inner-workings of your function, it’s very likely to become a dated, worthless, and confounding piece of documentation. Don’t let your comments become a liability. Keep them simple.

Standard #4: DocBlock: Know it? Use it. Don’t know it? Learn it.

PHP DocBlock is the standard for self-documenting PHP classes. It is designed to integrated with phpDocumentor to create documentation automatically. The SilverStripe API is built with phpDocumentor, and there you can see the payoff for strict adherence to DocBlock syntax.

There’s a lot to know about DocBlock, but fortunately, there are only a few types of syntax you’ll be using frequently.

Let’s look at an example of a function with DocBlock comment.

/**
  * Convert a shorthand byte value from a PHP
  * configuration directive to an integer value
  *
  * @param string $value
  * @return int
  */
 public static function convert_bytes($value) {
            // body of function
 }

Notice that every DocBlock comment is contained in this structure:

/**
  *
  */

On the first line of the comment, we have a short description of what the function does. Next, separated by a blank line, we list the input and output of the function. Each argument of the function gets a @param key, and follows this model:

@param [variable type] $variable_name

Likewise, the @return key follows a similar model:

@return [type] (description of return value, optional)

Lastly, let’s look at an example variable with a DocBlock comment.

	/**
 	 * @var array Extra request parameters to send with the upload request
 	 */
	public $extraParams = array();

All that is necessary is @var [type] (description).

DocBlock comments are always placed directly above the property or method they define.

Standard #5: Cast Your Variables

One of the common themes in a DocBlock comment that you may not be used to seeing is the concept of casting variables, as seen in the [type] section of the @param and @return lines. Variable casting is not a well-understood practice in PHP because PHP is what is known as a weak type language, that is, variables can be assigned arbitrary values, and PHP will figure out on its own what behaviours to assign to that data. It lets the developer get away with lazy coding, allowing for mind-bending scenarios like this:

$foo = "10"; // $foo is a string
$bar = $foo + 5; // I just added an integer to a string
echo $bar; // returns 15, as an integer

This is the type of stuff that makes a seasoned programmer crazy. Combining a string and an integer with the + operator creates a conflict that must be resolved by an assumption — should PHP handle this as the combining of strings, or integers? After all, wouldn’t another possible result to the above addition problem would be “105” as a string? If PHP decided to cast “5” as a string, it would combine “10” and “5” to create “105”, the same way “foo” + “bar” equals “foobar”.

A strong type language would require the programmer to do something more like this (pseudo code)

foo = new String('10');
bar = new String('5');
return foo.append(bar) // returns a string "105"

Always cast your input and output parameters from the DocBlock comment. If necessary, cast a value as “mixed” if it can return a variety of data types. An example of a mixed return is the DataObject::get() function, which can return a DataObjectSet object, or the boolean “false” if no records are found.

In PHP 5, variables can also be casted on the fly, like so:

echo (int) "hello"; // returns integer 0.
echo (boolean) "hello"; // returns 1, or true.
echo (array) 1; // returns an empty array

It’s a good idea to use inline casting to avoid fatal errors when integers are expected as database IDs, or in foreach loops, when arrays are required. For example:

DataObject::get_by_id("MyObject", (int) $untrustedData); // worst case, it passes a 0. No database error!

foreach((array) $mightBeABoolean as $k => $v) {
// the loop runs!
}

Standard #6: Class Organization

There’s nothing worse than fumbling through a 600-line PHP class looking for a 3-line method when there is no rhyme or reason to where the methods are positioned in the class. The SilverStripe documentation gives us a standard for ordering methods and properties in a class. Here they are, listed in order from the top of the class to the bottom.

1) Static variables:
So at the top of your PHP class, list these static properties.

protected static $records_per_page = 10;

public static $application_name = "SilverStripe";

2) Object variables:
Below the static variables (if any), add these object variables.

protected $uploadFolder = "Uploads";

public $delimiter = ",";

Note that in the examples above, the variables are listed in order of their access level, starting with least accessible (“protected”, in this case) to the most accessible (“public”). This is not a codified SilverStripe standard, but rather, just my own preference. We’ll talk more about access levels later in this article.

3) Static methods:
Below our object vars, we’ll place these static methods.

public static function relative_asset_dir($dirname) {
	return preg_replace('|^'.ASSETS_DIR.'/|', '', $dirname);
}

4) DataModel statics: These are common static vars that are used to define the datamodel in SilverStripe with which we’re all very familiar.

static $db = array (
	'Title' => 'Varchar',
	'Description' => 'Text'
);

static $has_one = array (
	'Photo' => 'Image'
);

5) Common methods: Next we put the common methods we use in almost all of our SilverStripe classes, including getCMSFields() and the other usual suspects.

public function getCMSFields() {
	$fields = parent::getCMSFields();
	$fields->addFieldToTab("Root.Content.Photo", new ImageUploadField('Photo', _t('MySite.PHOTO','Photo')));
	return $fields;
}

public function init() {
	parent::init();
	RSSFeed::linkToFeed($this->Link('rss'));
}

6) Accessor methods: All of your getXXX() and setXXX() methods that return or alter the value of a member property.

public function getFoo() {
   return $this->foo;
}

public function setBar($value) {
   $this->bar = $value;
}

7) Controller actions: If you have set up custom actions in your controller, add them here. Remember, controller actions are named in all lowercase, alphanumeric only.

public function showproduct(SS_HTTPRequest $request) {
    $product_id = (int) $request->param('ID');
    return array (
        'Product' => DataObject::get_by_id("Product", $product_id)
    );
}

8 ) Template accessors: Functions that are intended for use on templates. Remember the UpperCamelCase naming convention.

public function FeaturedProducts() {
   return DataObject::get("Product", "Featured = 1");
}

9) Object methods: A catch-all for non-static methods that are not used by templates, are not controller actions or accessors, and are not among the “common” methods.

	protected function loadFileTypes() {
		if(!empty($this->fileTypes)) {
			$this->setVar('fileExt','*.'.implode(';*.',$this->fileTypes));
		}
	}

Standard #7: Nit-picky Stuff

There are a few more conventions that should be followed generally, but are not necessarily crucial.

No line breaks before an opening brace

// good
if($foo == "bar") {

}

// bad
if($foo == "bar)
{

}

Use a single tab per level of nesting.

Tabs only, no spaces.

Use two line breaks between each property or method in a class.

/**
  * @var array Extra request parameters to send with the upload request
  */
 public $extraParams = array();

/**
  * @var array Additional configuration settings for this instance
  */
 public $configuration = array ();

Never close a PHP tag

It’s just not necessary when .php files contain only PHP code.

Bonus: UncleCheese’s Time-Saving Coda Clips

Nothing helps enforce standards better than automation. I’ve created a library of SilverStripe and basic PHP shortcuts for integration with my preferred code editor, Coda. For those of you using TextMate or Aptana, or just about any other editor that supports clips, I encourage you to do the same. These save a ton of time!

Usage

Trigger Description
DocBlock Comments (General)
d[TAB] An empty DocBlock comment
nl[TAB] Adds a new blank line to a DocBlock comment
nlp[TAB] Adds a new @param line to a DocBlock comment
nlr[TAB] Adds a new @return line to a DocBlock comment
docclass[TAB] DocBlock class header
DocBlock Comments (Functions)
fpr[TAB] Function, one parameter, and a return value
fr[TAB] Function, return value, no parameters
fp[TAB] Function, one parameter, no return
fppr[TAB] Function, two parameters, return value
fpppr[TAB] Function, three parameters, return value
fppppr[TAB] Function, four parameters, return value
DocBlock Comments (Variables)
vs[TAB] Var, string
va[TAB] Var, array
vb[TAB] Var, boolean
vi[TAB] Var, integer
SilverStripe Shortcuts
ssclass[TAB] Creates a new empty class structure with markers for the class organization standard discussed in this article. Very useful. One of my favorites.
sscontrol[TAB] Creates a SilverStripe block
ssif[TAB] Creates a SilverStripe block
ssifelse[TAB] Creates a SilverStripe block
ssinc[TAB] Creates a SilverStripe tag

18 Responses to “SilverStripe Development Standards: Start Coding Like a Pro”

  1. Ruurd de Boer 27. Sep, 2010 at 3:48 pm #

    Another great article. I’ll probably go old school with this and print myself a hard copy to keep on my desk.

  2. R Rudy 27. Sep, 2010 at 4:10 pm #

    Thanks UncleCheese, this is a great article that I’ll be sure to revisit before starting my Next SS project and as I upgrade existing ones.

    Well Done.

  3. barry 28. Sep, 2010 at 5:03 am #

    “No line breaks before an opening brace” – This is something I struggle to understand why people do. it is much easier to see code blocks when the braces align. Also since I am talking readability – line length comes into this. So often I see lines that start with while, foreach or if have a long condition and then an action that goes off to the right of the editor. Line lengths should be short, the part after if/foreach/while should be on a new line and always within braces (or at least it is in my code :)

    Also I find that naming variables with a prefix of type greatly improves readability of code (http://www.pharfruminsain.com/Teach/PrefixVariables.aspx)

    • unclecheese 28. Sep, 2010 at 2:24 pm #

      Barry, I wholeheartedly agree. I think the new line/same line brace is a matter of preference, but one that I cannot understand for the life of me is this one:

      if {

      } else {

      }

      That drives me crazy! I end up missing so many else statements!

  4. Roland Lehmann 28. Sep, 2010 at 9:19 am #

    Thanks for Your work in standards. We thought a lot about that issue too, still many of Your ideas can be added to our conventions.

  5. Aram 28. Sep, 2010 at 2:01 pm #

    Great Article Aaron, some stuff I wasn’t aware of in there.

    One item I do disagree with however is the opening brace, I recently started new-lining my opening braces and I have to say I think it makes my code far more readable….just a preference I guess, but one I am going to stick with for now :)

    Thanks again for a great article.

    Aram

  6. Dan Hensby 28. Sep, 2010 at 2:47 pm #

    I thought thisWasCamelCase and ThisWasTitleCase

  7. unclecheese 28. Sep, 2010 at 2:53 pm #

    Hey, guys,

    Just to be clear, these aren’t just my standards that I made up. I should have been more clear that they come from the SilverStripe specifications for coding convention, found here: http://doc.silverstripe.org/coding-conventions

    It’s easy to argue one way or another on a lot of these, like the curly brace issue, but when you’re working with a framework, you have to establish some consistency, and I think it’s just a matter of saying, “well, we can see the value in both, but we’re just going with this one, because.. that’s what we choose.”

    I had to retrain myself to use braces on the same line to be more consistent with the rest of the SilverStripe codebase. In my opinion, it’s worth it to create that sense of homogeneity in the project.

  8. Dieter 29. Sep, 2010 at 4:52 am #

    +1 for opening braces on a new line, as Aram points out this is a huge improvement for readability.

    • Jamie 01. Oct, 2010 at 6:23 am #

      Braces on newlines is always going to be a matter of taste, but personally I find that putting braces on new lines uses up far too much vertical space. I prefer denser code so that I can see more of it at a time.

      Dieter’s code drives me nuts! ;)

  9. Custer 06. Oct, 2010 at 8:48 am #

    Hats off UC! Another useful article!

  10. barry 06. Oct, 2010 at 9:47 am #

    just to stir this up a little :) in javascript the ‘{‘ should always be on the same line… anyone know why?

    • kelly 20. Feb, 2011 at 1:47 pm #

      @barry: there are many instances where this is true in Javascript, but not always (for example, you can open the brace on the following line if you have begun a function declaration, but not with assignment of an object literal).

  11. unraveledideas 01. Aug, 2011 at 12:15 am #

    Wow I suck a bunch of these. Thanks to you I’ll be getting better. Thanks for the info and for the Coda snippets.

  12. Hmm it appears like your blog ate my first comment (it was super long)
    so I guess I’ll just sum it up what I wrote and say, I’m thoroughly enjoying your blog. I too am an aspiring blog blogger but I’m still new to the whole thing. Do you have any points for beginner blog writers? I’d certainly appreciate it.

  13. Dallas Texas Furnace Repair Company 20. Jan, 2013 at 1:36 am #

    Hey I know this is off topic but I was wondering if you knew of any widgets I could add to my blog that
    automatically tweet my newest twitter updates.
    I’ve been looking for a plug-in like this for quite some time and was hoping maybe you would have some experience with something like this. Please let me know if you run into anything. I truly enjoy reading your blog and I look forward to your new updates.