An Introduction To Object Reflection In PHP

An Introduction To Object Reflection In PHP

30th January 2022 - 24 minutes read time

Reflection is used to gather more information about code during the runtime of the program. This allows code to inspect itself and to make small modifications, which is useful in a variety of situations.

This is possible in PHP thanks to the reflection classes, which are built into core. PHP is capable of using reflection to inspect all types of classes, object, functions, variables and even the recent addition of Fibers. Using the reflection classes we can find out the internal structure of a class that an object is part of, including properties, methods constants as well as what sort of class we are dealing with. Reflection works even if the property of method is private or protected.

This post will concentrate on objects (and their associated classes) as the code used to inspect objects can generally be applied to other forms in PHP.

Take the following class, which is an arbitrary PHP class containing a properties and methods that are public and private. This will give us a good test base that we can use to perform some different actions to find out whats in the class. All of the examples in this article will use this class and the instantiated object.

<?php

namespace MyNamesace;

/**
 * Class MyClass.
 */
class MyClass
{
  /**
   * Private property.
   *
   * @var string
   */
  private $privateProperty = 'private';

  /**
   * Public property.
   *
   * @var string
   */
  public $publicProperty = 'public';

  /**
   * A class constant.
   */
  const A_CONSTANT = 'constant';

  /**
   * MyClass constructor.
   */
  public function __construct()
  {
  }

  /**
   * Public method.
   *
   * @return mixed
   *   The value of privateProperty.
   */
  public function aPublicMethod()
  {
    return $this->privateProperty;
  }

  /**
   * Private method.
   *
   * @return integer
   *   Returns the number 1.
   */
  private function aPrivateMethod()
  {
    return 1;
  }
}

This class would be instantiated into an object in the usual way.

$object = new MyClass();

We are now ready to inspect the object.

Using var_dump() And print_r()

To start with, I thought I would demonstrate how much information can be gleaned using basic functions like var_dump() and print_r(), which many PHP developers may have seen in use.

var_dump() will print out value and type information about a variable.

var_dump($object);

This produces the following result.

object(MyNamesace\MyClass)#1 (2) {
  ["privateProperty":"MyNamesace\MyClass":private]=>
  string(7) "private"
  ["publicProperty"]=>
  string(6) "public"
}

print_r() will print out just the value information about a variable.

print_r($object);

This produces the following result.

MyNamesace\MyClass Object
(
    [privateProperty:MyNamesace\MyClass:private] => private
    [publicProperty] => public
)

This has told us about the properties of the object, but we haven't seen information about the constant or methods that the object contains. There is plenty of information hidden behind the object that we haven't seen. Let's look at how to find this information with reflection.

Creating Reflection Objects

The ReflectionClass class is used to discern information about a class or an object. This is a special internal PHP class that can give us access to the hidden workings of the class and can be created from a given class or object.

To create a ReflectionClass object from a class, pass the class name into the constructor.

$reflection = new \ReflectionClass(\MyNamesace\MyClass::class);

To create a ReflectionClass object from an object, pass a created object into the constructor.

$reflection = new \ReflectionClass($object);

The result is a ReflectionClass object, which we can now use to find out more about the class.

Interrogating The ReflectionClass

The ReflectionClass contains a lot of methods that return all kinds of information about the object or class in question. I will detail a few of the most used ones here.

getName()

Perhaps the simplest is the getName() method, which returns the class name.

echo $reflection->getName();

This returns the fully qualified name of the class (with namespace).

MyNamesace\MyClass

This is essentially the same as using the ::class special constant on the class.

getNamespaceName()

This will return the namespace of the class.

print_r($reflection->getNamespaceName());

For the example class above this produces the following.

MyNamesace

getShortName()

The short name is the local class name.

print_r($reflection->getShortName());

This will return the following when applied to our example class.

MyClass

getFileName()

Another useful method is getFileName(), which will print out the absolute path to the file where the class is located.

echo $reflection->getFileName();

This is great for debugging purposes as it will tell you where the class being used was defined.

getDocComment()

This will return the comment that precedes the class declaration.

print_r($reflection->getDocComment());

The return value here is a string. For our example class this returns the following.

/**
 * Class MyClass.
 */

getProperties()

Finding the properties of the class is done using the getProperties() method.

print_r($reflection->getProperties());

This returns an array of RelfectionProperty objects, which is another type of reflection object used to gain information about properties.

Array
(
    [0] => ReflectionProperty Object
        (
            [name] => privateProperty
            [class] => MyNamesace\MyClass
        )

    [1] => ReflectionProperty Object
        (
            [name] => publicProperty
            [class] => MyNamesace\MyClass
        )

)

To get a single property use the getProperty() method, passing in the name of the property that you want to find.

print_r($reflection->getProperty('privateProperty'));

This returns a single ReflectionProperty object.

ReflectionProperty Object
(
    [name] => privateProperty
    [class] => MyNamesace\MyClass
)

The ReflectionProperty object is actually used to find out more information about the property. I'll cover that later though as there are one or two aspects that are important to go over in detail.

getConstants()

It is also possible to find a list of the constants available in the class using the getConstants() method.

print_r($reflection->getConstants());

This returns an associative array of the constants with the name being the keys of the array. For our example class this would be as follows.

Array
(
    [A_CONSTANT] => constant
)

You can also get a single constant by using the getConstant() method and passing in the name of the constant.

print_r($reflection->getConstant('A_CONSTANT'));

This returns the value of the constant, whatever that might be and is equivalent to just accessing the constant. Using reflection here is useful if the constant has been declared as private or protected and you need to see what the value is from outside the class.

getMethods()

The getMethods() method will return a list of methods that the class has.

print_r($reflection->getMethods());

This returns an array of ReflectionMethod objects, each of which can be used to find out more information about the method.

Array
(
    [0] => ReflectionMethod Object
        (
            [name] => aPublicMethod
            [class] => MyNamesace\MyClass
        )

    [1] => ReflectionMethod Object
        (
            [name] => aPrivateMethod
            [class] => MyNamesace\MyClass
        )

    [2] => ReflectionMethod Object
        (
            [name] => __construct
            [class] => MyNamesace\MyClass
        )

)

You can also use the getMethod() method to return a ReflectionMethod about a single method, passing in the name of the method you want to view.

print_r($reflection->getMethod('aPrivateMethod');

This prints out the following.

ReflectionMethod Object
(
    [name] => aPrivateMethod
    [class] => MyNamesace\MyClass
)

Note that the constructor is also included in this array. You can also use the getMethod() method to find out more about a specific method, just pass in the name of the method you want to view, much like getProperty().

getConstructor()

If you need to take a look at just the constructor then you can use getConstructor().

print_r($reflection->getConstructor());

This returns a single ReflectionMethod object, representing the constructor.

ReflectionMethod Object
(
    [name] => __construct
    [class] => MyNamesace\MyClass
)

This will return null if no constructor exists in the class.

Boolean Checks

As well as pulling information out of the class it is also possible to perform several checks to see what kind of class is involved.

isCloneable()

The isCloneable() check tells you if the class can be cloned. The clone keyword in PHP allows objects to be copied, but setting the __clone() magic method to be private will prevent the object being cloned at all, causing this check to return false.

var_dump($reflection->isCloneable());

If we run the check on the example class above then it will return true.

isFinal()

This is a check to see if the class can be extended. Final classes are defined using the final keyword in the class definition.

var_dump($reflection->isFinal());

The example class above will return false since it doesn't contain the final keyword and is extendable.

isInstantiable()

This is a check to see if the object can be created from the class, or instantiated. Classes that can't be instantiated are either abstract or contain private constructors. Other PHP constructs like traits are not instantiable either and so will return true for this check.

var_dump($reflection->isInstantiable());

The example class above will return true, since we are able to create an object.

isAbstract()

If the class is abstract then this check will return true.

var_dump($reflection->isAbstract());

The example class above will return false since it is not abstract.

isUserDefined() / isInternal()

These two checks are used to see if the class in question is part of PHP or has been defined by project source code. Our example class above will return true for the isUserDefined() check since we have defined it in source code ourselves.

var_dump($reflection->isUserDefined());

The opposite of this is the isInternal() check, which tells is if the class is part of PHP.

var_dump($reflection->isInternal());

The example class would return false for this check, but classes like SoapClient or even ReflectionClass would return true since they are part of PHP.

isInstance($object)

The isInstance() check works in a similar way to the instanceof operator.

var_dump($reflection->isInstance($object));

This will return true if the class analysed by the ReflectionClass is an instance of the object passed to the check.

isSubclassOf($class)

If you are looking to specifically detect a subclass of a particular type then the isSubclassOf() check can be used.

var_dump($reflection->isSubclassOf(\SoapClient::class));

This will return true if the ReflectionClass is a sub class of the passed class. In the case above the check would return true if the class extended the SoapClient class.

hasConstant()

There are also a few "has" checks, the first of which is the check to see if a constant exists.

var_dump($reflection->hasConstant('A_CONSTANT'));

If we run this on our example class then it will return true since it contains the constant named.

hasMethod()

The hasMethod() check can be used to check if the class has a method of a given name. This will work even if the method is private.

var_dump($reflection->hasMethod('aPrivateMethod'));

This will return true for our example class since this method exists.

hasProperty()

To see if a property exists the hasProperty() method can be used, passing in the name of the property in question. Again, this works even if the property is private.

var_dump($reflection->hasProperty('privateProperty'));

This will return true for the example class.

Using ReflectionProperty

Running the getProperty() method will return a ReflectionProperty object that can be used to interrogate the property. This object contains a few methods that are useful in discovering more about the property in question. The simplest way to get this object is to run the getProperty() method, passing in the name of the property. This will work even if the property is private or protected.

For this section we will be using the ReflectionProperty created from the property "privateProperty" from the class.

​$property = $reflection->getProperty('privateProperty');

It's also possible to generate the object by instantiating the \ReflectionProperty class, using the class name and the name of the property you want to view.

$property = new \ReflectionProperty(\MyNamesace\MyClass::class, 'privateProperty');

The most basic of these methods is getName(), which returns the name of the property.

echo $property->getName(); // privateProperty

You can also use the getDeclaringClass() method to return the ReflectionClass object of the parent class, which means you always have access to it through the property.

There are some standard checks we can use to tell us more about the property in question.

var_dump($property->isPrivate()); // true
var_dump($property->isPublic()); // false
var_dump($property->isProtected()); // false
var_dump($property->isReadOnly()); // false

Note that the isReadOnly() method relies on the new readonly keyword in PHP 8.1.0.

To get the doc comment from the method you can use the getDocComment() method.

echo $property->getDocComment();

This will return the comment as a string, so the above code will print out the following.

/**
   * Private property.
   *
   * @var string
   */

Getting And Setting Properties

Perhaps the most useful part of the ReflectionProperty object is to get and set the value from the property. To do this you need to have an instantiated object that you can use to perform this work so let's make sure we have that in place.

$object = new MyClass();

To get the value of the property you would use the getValue() method, passing in the object that you want to pull the value from. Remember that the $property variable is the ReflectionProperty object, created from the property itself.

echo $property->getValue($object); // prints "private"

The default value of the property in question is "private" so that's what is returned here.

You can set the value of the property using the setValue() method. This takes in the object and the value to set the property with.

$property->setValue($object, 'wibble');

This works even if the property is private or protected. To prove this we can grab the value a second time to prove that the value has changed.

echo $property->getValue($object); // prints "wibble"

This returns the value we set the private property with. This is quite powerful and means that you are able to change the value of an object with a private or protected property.

Using ReflectionMethod

Similar to the ReflectionProperty object it is possible to use ReflectionMethod to find out more about the methods of a class. To create the ReflectionMethod you can use the getMethods() or getMethod() method, which returns an instance of ReflectionMethod.

$method = $reflection->getMethod('aPrivateMethod');

You can also create the ReflectionMethod object by instantiating the object directly using the class name and the name of the method you want to view.

$method = new \ReflectionMethod(\MyNamesace\MyClass::class, 'aPrivateMethod');

Just like the property you can use getName() and getDeclaringClass() to get the name of the method and the ReflectionClass object of the original class for the method in question. The getDocComment() method can also be used to get the comment of the method.

The same checks can also be used to find out more about the permissions of the method in question.

var_dump($method->isgPrivate()); // true
var_dump($method->isPublic()); // false
var_dump($method->isProtected()); // false
var_dump($method->isStatic()); // false

What you will likely be wanting to do is invoke methods using the ReflectionMethod object, so let's look at that.

Invoking Methods

It is possible to invoke methods of an object using the ReflectionMethod object. This will work even if the method is private or protected, so this is a good way of forcing private methods to be run.

To invoke a method you just need to call the invoke method on the ReflectionMethod object, passing in the object that we want call the method on. For our example class this would return "1", since that's what the example private method does.

var_dump($method->invoke($object)); // prints "int(1)"

Note that in previous versions of PHP you needed to call a method called setAccessible() to make the private method accessible. This has not been required since 8.1.0 and calling the setAccessible method will have no effect.

If you want to pass arguments to the method then you can just pass them as arguments to the invoke method, which are then passed in order to the method in question. For example, to pass the number 2 to the method you would do the following.

$method->invoke($object, 2);

Another way of passing arguments to the method is to use the invokeArgs() method from ReflectionMethod. This works in the same way as the invoke() method, but the second parameter is an array of arguments that will be passed to the method. The array passed is expanded to the separate parameters of the method, in much the same way as the splat operator (...) would do.

$method->invokeArgs($object, [2]);

Calling methods like this works in the same way as calling the method itself, but also allows us to call any private or protected methods as if we had full access to them outside of the class.

This technique is useful for things like unit testing since it allows you to directly test a method without having to call other methods first or alter the original class. Just create a reflection of the class and then invoke the method, passing in any arguments you need.

Conclusion

I have to admit that I've been using PHP for a few years now, and only ever had the need to use reflection a handful of times. It is a powerful technique to know about and should be part of your PHP toolbox. I haven't seen it commonly used in production code, except perhaps to perform checks on objects using methods like isInstance() or hasMethod().

Using reflection is absolutely perfect if you want to find out more about the object for debugging or informational purposes.  Looking at production code I can see it in use in a couple of places that are for debugging purposes. For example, the SMTP module in Drupal has the following code in the install handler. This is used to feed back information to the user about the location of the PHPMailer package, which is a requirement of the module.

//... 
      $reflector = new \ReflectionClass('\PHPMailer\PHPMailer\PHPMailer');

      $requirements['smtp_phpmailer'] = [
        'title' => (string) t('SMTP: PHPMailer library'),
        'value' => $installed_version,
        'description' => t('PHPMailer is located at %path', ['%path' => $reflector->getFileName()]),
      ];
//...

It's also sometimes essential to use for unit testing as you can run private methods without changing the class. Another example I found was in the unit tests for the metatag module in Drupal. The following runs the test() method of the MetaNameBase class, but because that method is protected it needs to be called using the invoke() method.

  public function testTidy() {
    $method = "tidy";
    $class = new \ReflectionClass(get_class($this->metaNameBase));
    $method = $class->getMethod($method);
    // Set the protected method tidy to be accessible.
    $method->setAccessible(TRUE);

    $filterResult1 = $method->invoke($this->metaNameBase, "  Test   123  ");
    $this->assertEquals('Test 123', $filterResult1);
// ...

If you are using reflection in production code then I think that you really need to question why. Using reflection to alter private properties in objects or to call private methods can lead to unexpected results. By working around private and protected items in the object you are working against the reason for those protected terms in the first place. Maintenance will be harder due to the fact that the these items are changed or called through different mechanisms, so you might not be able to find it.

I can see certain arguments for changing objects from third party dependencies, but there are much better ways than using reflection to do this. As an example, if you have a class from a third party that contains a private method you want to use then you can extend the class and wrap the call to the parent method in a public method that you create yourself. This has much more predictable results in the long run and makes introducing unexpected bugs somewhat harder.

Add new comment

The content of this field is kept private and will not be shown publicly.