Tuesday, 12 August 2008

PHPUnit - issues with static method calls

Recently ran into a couple of issues concerning stubbing of static method calls when using PHPUnit.

Taking the following code snippet:

  1. <?php
  2. class Example
  3. {
  4. public function methodCall()
  5. {
  6. Logger::logMessage('message', 4, new Exception());
  7. return true;
  8. }
  9. }
  10. ?>


A static method call, which needs to be stubbed out, is made from within the methodCall() function. Mike Lively suggests refactoring under these circumstances, so that calls to static methods that are external to the class are encapsulated within a new class method.

Refactoring, then yields something like this:

  1. <?php
  2. class Example
  3. {
  4. public function methodCall()
  5. {
  6. $this->logMessage('message', 4, new Exception());
  7. return true;
  8. }
  9. public function logMessage($message, $priority, Exception $exception)
  10. {
  11. Logger::logMessage($message, $priority, $exception);
  12. }
  13. }
  14. ?>


A unit test which stubs out the logMessage() function can then be created:

  1. <?php
  2. require_once 'PHPUnit/Framework/TestCase.php';
  3. require_once 'Example.php';
  4. require_once 'Logger.php';
  5.  
  6. class ExampleTest extends PHPUnit_Framework_TestCase
  7. {
  8. public function testMethodCall()
  9. {
  10. $example = $this->getMock('Example', array('logMessage'));
  11. $example->expects($this->any())->method('logMessage');
  12. $this->assertEquals($example->methodCall(), true);
  13. }
  14. }
  15. ?>


At this point, everything works as expected.

However, problems arise if an equivalent process is followed for static method calls. For instance, if the Example class methods are static:

  1. <?php
  2. class Example
  3. {
  4. public static function methodCall()
  5. {
  6. self::logMessage('message', 4, new Exception());
  7. return true;
  8. }
  9. public static function logMessage($message, $priority, Exception $exception)
  10. {
  11. Logger::logMessage($message, $priority, $exception);
  12. }
  13. }
  14. ?>


The unit test now ignores the stubbed method and calls the original class method. Furthermore, direct calls to the stubbed method throw the following error "Fatal error: Using $this when not in object context in......."

These issues have been documented, but were not easy to find and another ticket on the PHPUnit site indicates that stubbing static methods works as expected. As can be seen when reading through the posts on ticket 139, it appears that support for stubbing static methods was withdrawn sometime between Jun 2007 and Feb 2008, with no indication that this will be made available.


Wednesday, 6 August 2008

PHPUnit - installation on OS X under XAMPP

Just a couple of notes on installing PHPUnit under XAMPP for OS X.

Check that pear is in the PATH

sh-3.2# echo $PATH
/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/bin:/usr/X11/bin:/Applications/xampp/xamppfiles/bin



If pear is absent from the PATH, it can be made available by editing /etc/bashrc using vi to add the following lines:

PATH=$PATH:/Applications/xampp/xamppfiles/bin
export PATH



If you're behind a proxy then you'll need to configure pear using something like the following:

pear config-set http_proxy wwwcache.whereveryouare.ac.uk:3128



PHPUnit can then be installed by running the following commands:

pear channel-discover pear.phpunit.de
pear install phpunit/PHPUnit



If necessary, the memory_limit setting in /Applications/xampp/xamppfiles/etc/php.ini can be increased to avoid "Fatal error: Allowed memory size of 8388608 bytes exhausted (tried..." message during installation of PHPUnit

The following message might then be seen when trying to run PHPUnit from the command line:

$ phpunit
Warning: require_once(PHPUnit/Util/Filter.php): failed to open stream: Not a directory in /Applications/xampp/xamppfiles/bin/phpunit on line 40.......



This can be fixed by opening up /Applications/xampp/xamppfiles/etc/php.ini and amending the include_path line added by pear so it looks as follows:

;***** Added by go-pear
include_path=".:/Applications/xampp/xamppfiles/lib/php:/Applications/xampp/xamppfiles/lib/php/pear"
;*****



Wednesday, 30 July 2008

PHP Logging - logging exceptions

Re-working PHP Exceptions - getLine() vs. getTrace['line'] to use the Logger class (see PHP Logging - Zend_Log) and the Zend Framework, yields something like the following..........

<?php
 
set_include_path('../library'. PATH_SEPARATOR . get_include_path());
require_once "Zend/Loader.php";
Zend_Loader::registerAutoload();
 
require_once "Logger.php";
 
class exceptionContents
{
public function display()
{
try
{
$exception = new Exception('message passed to exception', 5);
throw $exception;
}
catch(Exception $exception)
{
Logger::logMessage('message passed to log object', 1, $exception);
}
 
}
}
 
$exceptionContents = new ExceptionContents();
$exceptionContents->display();
 
?>


.....which produces this sort of information in the log file........

<logEntry>
<timestamp>2008-07-30T21:10:21+01:00</timestamp>
<message>message passed to log object</message>
<priority>1</priority>
<priorityName>ALERT</priorityName>
<exceptionCode>5</exceptionCode>
<file>/path/to/file/containing/line/where/thrown/exception/was/created.php</file>
<line>16</line>
<exceptionMessage>message passed to exception</exceptionMessage>
<exceptionTrace>a:1:{i:0; a:6:{s:4:"file"; s:74:"/path/to/file/containing/method/call/that/throws/exception.php"; s:4:"line"; i:27; s:8:"function"; s:7:"display"; s:5:"class"; s:17:"exceptionContents"; s:4:"type"; s:2:"->"; s:4:"args"; a:0:{}}}</exceptionTrace>
<exceptionTraceAsString>
#0 /path/to/file/containing/method/call/that/throws/exception.php(27): exceptionContents->display()
#1 {main}
</exceptionTraceAsString>
</logEntry>




Monday, 28 July 2008

PHP Logging - Zend_Log

Zend_Log provides a very convenient mechanism for keeping track of particular events when they occur in application code.

I tend to use a singleton pattern-based Logger class and then typically call a static logMessage() method directly.

Alternatively, calling the static instance() method returns the Logger, providing additional flexibility (e.g., customise setEventItem() calls on Zend_Log object).

Zend_Registry is being used to hold some values set during initialization of the application.

<?php
 
class Logger
{
private static $instance;
private $logger;

public static function instance()
{
if(!self::$instance)
{
self::$instance = new self();

$formatter = new Zend_Log_Formatter_Xml();

$filter = new Zend_Log_Filter_Priority(Zend_Registry::get('logFilterLevel'));

$writer = new Zend_Log_Writer_Stream(Zend_Registry::get('logFileLocation'));
$writer->setFormatter($formatter);
$writer->addFilter($filter);

self::$instance->logger = new Zend_Log($writer);
}

return self::$instance;
}

public static function logMessage($message=null, $priorityLevel=null, Exception $exception)
{
$logger = self::instance()->logger;

$logger->setEventItem('exceptionCode', $exception->getCode());
$logger->setEventItem('file', $exception->getFile());
$logger->setEventItem('line', $exception->getLine());
$logger->setEventItem('exceptionMessage', $exception->getMessage());
$logger->setEventItem('exceptionTrace', serialize($exception->getTrace()));
$logger->setEventItem('exceptionTraceAsString', $exception->getTraceAsString());

$logger->log($message, $priorityLevel);
}

}
 
?>



Friday, 25 July 2008

PHP Exceptions - getLine() vs. getTrace()['line']

The getLine() method called on an exception object returns information that differs from the ['line'] entry in the array returned by getTrace().

Using a trivial example (and the bad practice of dumping output to the screen), the following code..........

<?php
 
class exceptionContents
{
public function display()
{
try
{
$exception = new Exception('test exception message', 8);
throw $exception;
}
catch(Exception $exception)
{
echo '$exception->getCode() = '.$exception->getCode().'<br />';
echo '$exception->getFile() = '.$exception->getFile().'<br />';
echo '$exception->getLine() = '.$exception->getLine().'<br />';
echo '$exception->getMessage() = '.$exception->getMessage().'<br /><br />';
echo 'var_dump($exception->getTrace()) = ';
echo var_dump($exception->getTrace()).'<br />'.'<br />';
echo '$exception->getTraceAsString() = '.'<br />'.$exception->getTraceAsString().'<br />';
}
 
}
}
 
$exceptionContents = new ExceptionContents();
$exceptionContents->display();
 
?>


.......returns this sort of output.....


$exception->getCode() = 8
$exception->getFile() = /path/to/phpFile.php
$exception->getLine() = 9
$exception->getMessage() = test exception message

var_dump($exception->getTrace()) =
array
0 =>
array
'file' => string '/path/to/phpFile.php' (length=57)
'line' => int 27
'function' => string 'display' (length=7)
'class' => string 'exceptionContents' (length=17)
'type' => string '->' (length=2)
'args' =>
array
empty


$exception->getTraceAsString() =
#0 /path/to/phpFile.php(27): exceptionContents->display()
#1 {main}



$exception->getLine()
  • returns the line number on which the Exception was created (line 9 in this example).


$exception->getTrace()
$exceptionTrace = $exception->getTrace();
$exceptionTrace[0]['line'];

  • returns the line number for the method/function call that resulted in an exception being thrown (line 27 in this example).


By the same token, $exception->getFile() and $exception->getTrace['file'] also differ and if, for instance, a static method call is made and this call results in an exception being thrown, then getFile() will list the file in which the exception was created and thrown, whereas getTrace()['file'] will list the file in which the static method call was made.