Breaking inheritance in Object Orientated PHP

An ill conceived technique for accessing private attributes and methods from parent classes in PHP.

Dougall Winship

Developer
·3 min read (925 words)

** Warning : do not do this! **

Using a combination of PHP's magic methods and reflection it's possible to nearly completely sidestep PHP's OO inheritance visibility. Obviously this isn't a good thing under very nearly all circumstances ... just because you can do something doesn't mean you should!

 

The technique is very simple ... you intercept calls made to private methods/attributes using PHP's magic methods __get __set __call and __callStatic, find the private method or attribute in question and force it to be available via reflection's setAccessible(...) method.

 

Note that you'll probably want to turn on php display errors to run most of the following scripts, something like this:

ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);

First, to illustrate the technique, let's consider a very simple case:

class CTest {
	private $privateAttribute;
}
$ctest =  new CTest();
echo $ctest->privateAttribute;

If you attempt to run this then for obvious reasons you will get an error like this Uncaught Error: Cannot access private property CTest::$privateAttribute in .....

However PHP's reflection system allows us to ignore this restriction like so:

class CTest {
	private $privateAttribute = 'private value';
}
$ctest =  new CTest();
$reflectionObj = new ReflectionClass(CTest::class);
$reflectionAttribute = $reflectionObj->getProperty('privateAttribute');
$reflectionAttribute->setAccessible(true);
echo $reflectionAttribute->getValue($ctest);

If you run this code you will see 'private value' output.

Next consider this code:

class CParent {
	private $privateAttribute = 'private value';
}
class CTest extends CParent {
	public function echoPrivateAttribute() {
		echo $this->privateAttribute;
	}
}
$ctest = new CTest();
$ctest->echoPrivateAttribute();

If you try to run this then again you will get an error (well technically a notice in this case) Notice: Undefined property: CTest::$privateAttribute since the parent's privateAttribute is unavailable to the inheriting class, *but* PHP's magic methods allow us to catch this attempt and do something with it, in this case we can force the attribute to be visible via reflection in the manner previously described, this gives us:

class CParent 
{
	private $privateAttribute = 'private value';
}

class CTest extends CParent 
{
    public function echoPrivateAttribute() {
    	echo $this->privateAttribute;
    }
    public function __get($propertyName) {
        $reflectionObj = new ReflectionClass(parent::class);
        $reflectionAttribute = $reflectionObj->getProperty($propertyName);
        if ($reflectionAttribute->isPrivate()) {
        	$reflectionAttribute->setAccessible(true);
        }
        return $reflectionAttribute->getValue($this);
    }
}

$ctest = new CTest();
$ctest->echoPrivateAttribute();

OK great, so now we have this ill advised technique working let's make it a trait so we can abuse our PHP code more easily:

trait AccessParentsProperties 
{
		public function __get($propertyName) {
			$reflectionObj = new ReflectionClass(parent::class);
			$reflectionAttribute = $reflectionObj->getProperty($propertyName);
			if ($reflectionAttribute->isPrivate()) {
				$reflectionAttribute->setAccessible(true);
			}
			return $reflectionAttribute->getValue($this);
		}
}

class CParent 
{
	private $privateAttribute = 'private value';
}

class CTest extends CParent 
{
	use AccessParentsProperties;
	public function echoPrivateAttribute() {
		echo $this->privateAttribute;
	}
}

$ctest = new CTest();
$ctest->echoPrivateAttribute();

Loathe as I am to call this "neater" it arguably has an ugly sort of elegance to it.

There's a few things to note at this stage:

So combining all this together, here is the final monstrous creation:

trait AccessPrivate {

	private static $ancestorReflectionClasses = null;

	private static function getAncestorReflectionClasses() 
	{
		if (is_null(self::$ancestorReflectionClasses)) {
			$parentClasses = class_parents(self::class);
			self::$ancestorReflectionClasses=[];
			foreach ($parentClasses as $parentClass) {
				self::$ancestorReflectionClasses[] = new \ReflectionClass($parentClass);
			}
		}
		return self::$ancestorReflectionClasses;
	}

	private static function findParentReflectProperty($property) 
	{
		$ancestorReflectionClasses = self::getAncestorReflectionClasses();
		foreach ($ancestorReflectionClasses as $ancestorReflectionClass) {
			if ($ancestorReflectionClass->hasProperty($property)) {
				return $ancestorReflectionClass->getProperty($property);
			}
		}
		throw new \ReflectionException("Property '".$property."' does not exist as an attribute of ".self::class." or it's parents");
	}

	private static function findParentReflectMethod($property) 
	{
		$ancestorReflectionClasses = self::getAncestorReflectionClasses();
		foreach ($ancestorReflectionClasses as $ancestorReflectionClass) {
			if ($ancestorReflectionClass->hasMethod($property)) {
				return $ancestorReflectionClass->getMethod($property);
			}
		}
		throw new \ReflectionException("Property '".property."' does not exist as an method of ".self::class." or it's parents");
	}

	public function __call($method, $args)
	{
		$reflectMethod = self::findParentReflectMethod($method);
		if ($reflectMethod->isPrivate()) {
			$reflectMethod->setAccessible(true);
		}
		return $reflectMethod->invokeArgs($this, $args);
	}

	public function __get($name)
	{
		$reflectProperty = self::findParentReflectProperty($name);
		if ($reflectProperty->isPrivate()) {
			$reflectProperty->setAccessible(true);
		}
		return $reflectProperty->getValue($this);
	}

	public function __set($name, $value)
	{
		$reflectProperty = self::findParentReflectProperty($name);
		if ($reflectProperty->isPrivate()) {
			$reflectProperty->setAccessible(true);
		}
		return $reflectProperty->setValue($this,$value);
	}

	public static function __callStatic($name, $args)
	{
		$reflectStaticMethod = self::findParentReflectMethod($name);
		if ($reflectStaticMethod->isPrivate()) {
			$reflectStaticMethod->setAccessible(true);
		}
		return $reflectStaticMethod->invokeArgs(null, $args);
	}
}

You can take this for a ride using something like the following:

class CGrandparent
{
	private $unavailableGrandparentAttribute = 'grandparentPrivateAttribute';

	private static function gp_staticPrivateMethod() {
		echo "gp_staticPrivateMethod<br>";
	}

	private function gp_privateMethod() {
		echo "gp_privateMethod<br>";
	}
}


class CParent extends CGrandparent
{
	private $unavailableParentAttribute = 'parentPrivateAttribute';

	private static function p_staticPrivateMethod() {
		echo "p_staticPrivateMethod<br>"
	}

	private function unavailableParentAttribute() {
		echo "p_privateMethod<br>";
	}
}

class CTest extends CParent
{
	use AccessPrivate;
}

$ctest = new CTest();
// parent's hidden stuff
echo "parent's hidden stuff:<br>";
echo $ctest->unavailableParentAttribute."<br>";
$ctest->unavailableParentAttribute='changed parent attribute';
echo $ctest->unavailableParentAttribute."<br>";
CTest::p_staticPrivateMethod();
$ctest->p_privateMethod();

// grandparent's hidden stuff
echo "<br>grandparent's hidden stuff:<br>";
echo $ctest->unavailableGrandparentAttribute."<br>;
$ctest->unavailableGrandparentAttribute='changed grandparent attibute';
echo $ctest->unavailableGrandparentAttribute."<br>";
CTest::gp_staticPrivateMethod();
$ctest->gp_privateMethod();

Please, please, please bear in mind that in most circumstances this is clearly a very bad idea, it should be considered more of a curiosity than anything actually useful.

Pretty much the only justification for doing something like this that I can think of is when you want to extend a 3rd party library class with the following characteristics:

Normally this isn't a problem, but Magneto...

Finally, please note that aside from the obvious flaws with this oddity there are several gotchas including:

The code in this blog is available as a zip here.


I'm Dougall Winship

Developer at Newicon

Join the newsletter

Subscribe to get our best content. No spam, ever. Unsubscribe at any time.

Get in touch

Send us a message for more information about how we can help you:

We use cookies, review our privacy policy here.