Making private and protected getters and setters in PHP

Okay, let’s get this out of the way: there are a bunch of people who think you should never use PHP's magic methods.

I think they’re mostly hard-line ideologues who raise a few reasonable objections about convenience and then retreat quickly to, “But then it doesn't count as object-oriented programming anymore”.

But I’ve considered those objections and decided I still want to use __get and __set. I want to be able to lazy-load class variables, and automatically memoize them when they change, without having to repeat the same code everywhere. I also really dislike the explicit getter/setter pattern (getSomething() and setSomething()) that's more common, because it’s noisy and requires extra effort to extend the class in the future.

There’s just one catch: I’m writing code that will have to coexist in a shared environment, and __get and __set are public. This means that if I expose anything private through __get and __set, like my user’s secret key, then other code can load my class and interrogate it for the secret key too. Well, that’s not good. (Realistically: other malicious code executing in the same environment has access to the same database I do, so restricting the scope on magic methods only makes it a tiny bit less convenient for the other code to extract private data. But, this is still a fun exercise.)

It would be nice if it were possible to have __get and __set be private methods, but PHP says no:

$ php -f ~/Downloads/private_get_test.php 
PHP Warning:  The magic method __get() must have public visibility and cannot be static in /home/rob/Downloads/private_get_test.php on line 5
hello
hello
hello
	

...that would be PHP complaining that I tried to make __get a private function, and then going ahead and granting public access to the variable it’s gatekeeping for anyway. Dammit.

This calls for a hack!

PHP code can look at its own call stack at any time through debug_backtrace(). debug_backtrace() returns an array; the first element of the array is the current function, the next element of the array is the function that called the current function, and so on. debug_backtrace() also provides metadata about each calling function, so we can figure out whether our magic function was called by our own object or not, like so:

class my_class {

	private function __get ($var) {
		$stack_trace = debug_backtrace();
		array_shift($stack_trace);
		if ( empty($stack_trace) || ! array_key_exists('object', $stack_trace[0]) || $stack_trace[0]['object'] !== $this ) {
			throw new Exception("'$var' is not a publicly-accessible property");
		}
		if ( $var === 'hello' ) {
			return 'hello';
		}
	}

	public function test () {
		return $this->hello;
	}

}

function test_2 ($thing) {
	echo $thing->hello . "\n";
}

$thing = new my_class();
echo $thing->test() . "\n";
echo $thing->hello . "\n";
test_2($thing);
		

echo $thing->test() . "\n"; will work just fine, because the application calls a public function in the object and the public function then asks for a private variable through __get(). But, the next two lines will throw exceptions.

One of my favorite things about PHP is that it gives programmers just enough rope to hang themselves.