Knickers: Change your underwear!
Error Handling

Error handling is a very important part of any application and is unfortunately very easy to skip in languagues such as PHP. We have attempted to make Error handling a integral part of Knickers, and here are some of the general precepts.

Examples

class A
{
	/**
	*Go get some data
	*
	*@return mixed data string or CDMJ_ERROR
	*@throws Error_CDMJ
	*/
	function getInfo()
	{
		if(($result = $this->dba->execute($query)) === CDMJ_ERROR)
			return $this->setCDMJError(new Error_CDMJ($this->dba->getCDMJError()));
		
		$record = $result->fetchRow();
		
		return $record['SOME_FIELD'];
	}
	
	/**
	* Typical validation routine
	*
	*@param array data fields
	*@return boolean or CDMJ_ERROR
	*@throws Error_InvalidData
	*@throws Error_CDMJ
	*/
	function validate($data)
	{
		if($data[0] != 'what i expect')
			$this->addInvalidDataError(new Error_InvalidData());
		
		// assuming that checkSpecificField is also adding an error to our iterator
		if(($check = $this->checkSpecificField($data[1])) === CDMJ_ERROR)
		{
			// BAD: Do *not* just pass along the error directly
			//return $this->setCDMJError($this->getCDMJError());
			
			// GOOD: Create an Error of your own, to make the message more traceable
			return $this->setCDMJError(new Error_CDMJ($this->getCDMJError())); 
		}
		
		if($data[2] != 'what i expect')
			$this->addInvalidDataError(new Error_InvalidData());
		
		return ($this->getInvalidDataErrorCount() == 0);
	}
}	
	

if(($info = $a->validate($data)) === CDMJ_ERROR) // function couldn't do its job
{
	$errorObject = $a->getCDMJError();
	if(is_a('Error_DBA',$errorObject))
	{
		// do something with error		
	}
}
elseif($info === FALSE) // validation failed (not valid)
{
	$errors = $a->getInvalidDataErrors();
	while($errors->hasNext())
	{
		$error = $errors->next();
		
		// do something with the data
	}

}
else
{
	// Yeah! All is well
}
///// Typical single page

$locale = new Locale();
$location = new Location();

/*
* Different functions can throw different errors for the same 
* "condition" depending on what the purpose of that function is.
* 
* Both of the following functions take a $locID paramter.
* However, if it is sytactically incorrect, they will throw different Errors
* The first will throw an InvalidData, the second an IllegalArgument.
*/
// returns FALSE if not valid (Error_InvalidData), TRUE if valid, or CDMJ_ERROR
$location->isValidLocID($locID);

// vs

// returns CDMJ_ERROR or void
$location->setID($locID);  



if(($check = $location->checkAndSetIfValid($locID)) === CDMJ_ERROR)
{
	// Something bad happened; we could not do our job.
	$err = $location->getCDMJError();
	
	$og->setTag('ERROR_MESSAGES', $err->getMessages());
}
elseif(!$check)
{
	// loc id invalid...
	$err = $location->getInvalidDataErrors();
	while($err->hasNext())
	{
		$err = $err->next();
		$msgs = $err->getMessages();
	
		while($msgs->hasNext())
		{
			$msg = $msgs->next();
			$errorMsgs[] = $msg;
		}
	}
	
	$it = new Iterator_Array($msg);
	
	$og->setTag('ERROR_MESSAGES',$it);
}
else
{
	//data is valid
}
//// Example of how errors are "passed up" through your code

class A
{
	/**
	*@throws Error_CDMJ
	*/
	function run()
	{
		if(($check = $this->B->loadPage($pageName)) === CDMJ_ERROR)
			return $this->setCDMJError(new Error_CDMJ($this->B->getCDMJError()));
		else
			return $check;		
	}
}


class B
{
	/**
	*@throws Error_Resource <<< we throw this instead of the more specific errors we receive
	*/
	function loadPage($pageName)
	{
		if($this->C->loadData('users') == CDMJ_ERROR)
		{
			$error = $this->C->getCDMJError();
			if(is_a($error,'Error_Resource_NotFound'))
			{
				// maybe try a different resource...
				// all is good
				
				return $goodData;
			}
			else
				return $this->setCDMJError(
					new Error_Resource(
						new MessageDetails(array('PATH' => 'users')),
						$error);
		}
		
		// everything was fine
		return $goodData;
	}
}



class C
{
	/**
	* Should this return Error_CDMJ or something more specific??
	*@throws Error_Resource_NotFound
	*@throws Error_Resource_NotAvailable
	*/
	function loadData($type)
	{
		// initially when this class is built, it gets it data from flat files
		$path = '/path/to/'.$type;
		
		if(!file_exists($path))
			return $this->setCDMJError(
				new Error_Resource_NotFound(
					new MessageDetails(array('PATH' => $path)));
		
		if(file_is_locked($path))
			return $this->setCDMJError(
				new Error_Resource_NotAvailable
					(new MessageDetails(array('PATH' => $path)));
		
		// .... but at some point in time later, 
		// it is converted to use a database....
		// can still throw the same errors, 
		// because they are generic enough.
	}
}
///// for validation, we don't recommend that error messages themselves be used 
///// to determine how to programatically respond to an error.  If the 
///// difference in error types is important, you should be able to call a 
///// function to determine what specifically went wrong.

class Email
{
	function isValid($email)
	{
		if(doesnt_contain_@)
			$this->addInvalidDataError(new Error_InvalidData(
				new Message(MSG_EMAIL_BAD_SYNTAX,array('EMAIL' => $email))));
		
		if(domain_is_bad)
			$this->addInvalidDataError(new Error_InvalidData(
				new Message(MSG_EMAIL_BAD_DOMAIN,array('EMAIL' => $email))));
		
		return ($this->getInvalidDataErrorCount() == 0);
	}
}