Diasparsoft Logo Let's write software that people understand.

Home | Contact

Training

JUnit support

Outsourcing

The work that Diasparsoft did for us was outstanding. We are now using the software on a daily basis and their work could well have a dramatic impact on the Reds' organization in the near future.Cincinnati Reds Baseball Club.


Publications

Tips & Tricks

Diasparsoft Toolkit

What is Diaspar?

Interesting Bits RSS

Home » Archives » September 2004 » PHP: pass by value/pass by reference

[Previous entry: "File permissions and HTTPD"] [Next entry: "UGOT: A familiar approach to testing from Frank Cohen"]

09/28/2004: "PHP: pass by value/pass by reference"


After years of programming in languages where passing object references around is the norm, I just spent about 30 minutes re-learning the distinction between pass by value and pass by reference. The good news is that I did so within the context of writing tests, so it wasn't so bad.

It boils down to this: PHP passes function parameters by value, so unless you say otherwise, the function will operate on a copy of its parameters, and changes to an object's state are not visible by the invoker. If you want to pass a parameter by reference, you must say so. I came across this while rolling my own test runner in PHP.

I started with a class TestRunner that keeps track of test results. To execute a test, the test runner invokes a test method and passes itself as a parameter to the test. The test then signals failure back to the test runner when a test fails. Here's a little code. First, the Test Runner:

class TestRunner {
	var $passed;
	var $failed;
	var $tests;
	
	function TestRunner() {
		$this->passed = 0;
		$this->failed = 0;
		$this->tests = array();
	}

function addTest($test) { $this->tests[] = $test; }

function signalFailed() { $this->failed++; } function runTests() { foreach ($this->tests as $i => $eachName) { call_user_func($eachName, $this); } } function report() { echo count($this->tests) . " run, " . $this->passed . " passed, " . $this->failed . " failed."; }

A test is just a global function, such as this one:

function testNullStringIsEmpty($testRunner) {
	$testRunner->signalFailed();
}

Here, $testRunner->signalFailed() is the equivalent to xUnit's fail() method.

The problem is that when I run the tests, the report claims 0 tests failed, even though clearly one of the tests failed. Sadly, I had to debug:

  1. It happily reports "1 run, 0 passed, 0 failed," so I know the test runner has the test to execute.
  2. When I add echo "I got here." inside the test function, I confirm that the test is executing.
  3. When I add echo "I got here." inside signalFailed(), I confirm that the test tries to signal its failure.
  4. When I add echo $this->failed inside signalFailed(), I get 1.
  5. When I add echo $this->failed after executing the test inside runTests(), I get 0! RED BAR

I read the PHP manual and it has a section on "References Explained". Hm. That sounds like that will help. I read this:

You can pass variable to function by reference, so that function could modify its arguments.

The syntax is clear: use the ampersand (&) before the function parameter name to declare it "pass by reference". I did that.

function testNullStringIsEmpty(&$testRunner) {
	$testRunner->signalFailed();
}

I ran the tests. No change. Strange! But the manual clearly says:

Note that there's no reference sign on function call - only on function definition. Function definition alone is enough to correctly pass the argument by reference.

Well, let me try changing the function call site anyway. It can't hurt. I did that.

class TestRunner {
	function runTests() {
		foreach ($this->tests as $i => $eachName) {
			call_user_func($eachName, &$this);
		}
	}

Hey, it worked! Just for fun, I changed the function parameter back.

function testNullStringIsEmpty($testRunner) {
	$testRunner->signalFailed();
}

Hey, it still worked! It seems that I have to do the exact opposite of what the manual claimed. Shame.

Fortuantely, the PHP manual is a kind of Wiki, so I'll post my addition to the manual, and perhaps it (along with this entry) will help someone. We'll see!