Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Magic Constants and Reflection

Your code usually knows what it is: you wrote it. But sometimes a program has to ask that question of itself while it runs. A logger wants to say which method emitted a warning. A testing tool wants the list of methods in a class it has never seen. PHP answers those questions with two tools of very different sizes: a handful of magic constants for the cheap questions, and Reflection, a whole API, for the detailed ones.

Magic constants

Start with the logger.

<?php

class Logger
{
    public function warn(string $message): void
    {
        echo __CLASS__ . '::' . __FUNCTION__ . " at line " . __LINE__ . ": {$message}\n";
    }
}

(new Logger())->warn('Disk space low');
$ php logger.php
Logger::warn at line 7: Disk space low

The warn() method never spells out its own name, yet the output shows Logger::warn and a line number. __CLASS__, __FUNCTION__ and __LINE__ are filled in by PHP before the code runs, with the name of the class, the name of the function and the line where they appear. Two more do the same job: __METHOD__ gives both names at once, as Class::method, and __FILE__ gives the full path of the current file.

They are called magic because their value depends on where you write them. Try it: push the echo line down with a blank line above it and run again. The 7 becomes an 8.

There is nothing clever underneath. The parser replaces each constant with a plain value, so they cost nothing and cannot be wrong. That is what a log line needs: where a message came from, without a hardcoded name that drifts when someone renames the method.

A magic constant is a name tag the parser sews into your code. It always reads the current location.

Reflection

Magic constants tell code about itself. Reflection lets code examine other code, class by class and method by method, as data it can query at runtime.

<?php

class UserRepository
{
    public function find(int $id): ?string
    {
        return "User #{$id}";
    }

    public function save(string $name): void
    {
        // ...
    }

    private function connect(): void
    {
        // ...
    }
}

$reflection = new ReflectionClass(UserRepository::class);

foreach ($reflection->getMethods(ReflectionMethod::IS_PUBLIC) as $method) {
    echo $method->getName() . "\n";
}
$ php reflect.php
find
save

ReflectionClass wraps a class and exposes its shape: getMethods(), getProperties(), getConstructor(), and more. Each returns further reflection objects, ReflectionMethod or ReflectionProperty, that you can query in turn for a method’s parameters, their types, or whether a property is readonly. Passing ReflectionMethod::IS_PUBLIC filters out connect(), the private helper, and leaves just the public interface. Attributes, later in this chapter, are read back the same way.

A closed box labeled UserRepository seen through an X-ray screen that reveals its three methods, find, save and connect, with connect drawn behind a small padlock

Think of it as an X-ray. The object stays closed, and you still get a full picture of what is inside.

Where you will actually meet this

Be honest about how often you will write new ReflectionClass(...) yourself: rarely. Reflection exists so that other tools can work on classes they have never seen. A dependency injection container reads a constructor’s parameters to figure out what to pass in. PHPUnit uses it to find your test methods. Laravel and Symfony lean on it constantly under the hood.

You will use Reflection through those tools far more often than directly. But the next time a framework does something that looks like magic with a class you just wrote, you know what happened. It took an X-ray.