Traits, Exceptions, and Better Automated Tests

I really, really dig 5.4′s traits. Let’s review:

  • Through the ‘use’ keyword, traits let you include shared functionality in a class without relying on inheritance or encapsulation of another class.
  • With the exception of declaring static variables (you can’t), and referencing parent (will call the class you’re using the trait in), traits behave just like they’re part of a class.

Now, let me say a few things about how I prefer to code:

  • Unless I know 100%, beyond a shadow of a doubt, that an argument list will *never* change, I prefer passing objects or arrays as arguments instead of variables.  What I’ve seen happen in many projects / frameworks is you end up with this impossible method list.
  • As much as possible, I like to write defensive code that favors exceptions I can catch over fatal errors that will crash my programs.
  • I do NOT like repeating myself.
  • I’m lazy.  When I’m running automated tests, I don’t just want to know that an assert failed, I want to know exactly why it failed.

Thus, was borne my solution to checking required params and throwing exceptions I can catch in my tests when they’re omitted.  Consider the following code snippets:

The RequiredParams trait:

namespace System\Friend;

trait RequiredParams
{
	protected function checkArgs($params, $required){
		$missing = array();
		if(is_array($params) || is_object($params)){
			if(!is_object($params)) $params = (object) $params;

			foreach($required as $k){
				if(!isset($params->$k)) $missing[] = $k;
			}

			if(count($missing) > 0) {
				$missingKeys = implode(", ", $missing);
				throw new \Exception("Required params are missing: $missingKeys");
			}
			else {
				return true;
			}
		}

		throw new \Exception("Argument list was empty.");
	}
}

RequiredParams is used in AccountingProxy (duly noted that the hardcoded numeric literals should probably be constants). Here is one of the methods:

	public function createInvoice($params)
	{
		$this->checkArgs($params, array('type', 'id', 'created', 'due', 'items'));

		$invoice = array(
			'Invoice' => array(
				'Type' => ($params->type == 'payable') ? 'ACCPAY' : 'ACCREC',
				'Contact' => array(
					'ContactNumber' => $params->id
				),
				'Status' => 'AUTHORISED',
				'LineItems' => array(
				),
				'Date' => $params->created,
				'DueDate' => $params->due
			)
		);

		foreach($params->items as $item){
			$this->checkArgs($item, array('description', 'qty', 'amt'));

			$item = (object) $item;

			if(isset($item->code)){
				$code = $item->code;
			}
			else {
				$code = ($params->type == 'payable') ? 65700 : 410;
			}

			$invoice['LineItems']['LineItem'] = array(
				'LineItem' => array(
					'Description' => $item->description,
					'Quantity' => $item->qty,
					'UnitAmount' => $item->amt,
					'AccountCode' => $code
				)
			);
		}

		$created = $this->xero->invoices($invoice);

		return $created;
	}

And here is a a test definition for the method:

		$this->defineTest(
			new \Model\Implementation\AccountingProxy,
			array(
				'name' => 'Create a receivable invoice',
				'method' => 'createInvoice',
				'args' => array(
					(object) array(
						'type' => 'receivable',
						'id' => $customerId,
						'created' => '',
						'due' => \System\Implementation\DateTime::now(),
						'items' => array(
							array(
							'description' => 'This is an invoice item',
							'qty' => 1,
							'amt' => 20.00
							)
						)
					)
				),
				'assert' => array(
					'is',
					'numeric'
				),
				'responseToVar' => &$payInvId
			)
		);

So, as you can see, if createInvoice is missing any required items, even params within params, we can use checkArgs to throw an exception and give us real details about the problem.

Enjoy!

This entry was posted in PHP, Programming. Bookmark the permalink. Post a comment or leave a trackback: Trackback URL.

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>