Skip to Content
Course content
Click on the "Edit" button in the top corner of the screen to edit your slide content.

When you first encounter Attributes in PHP 8, it's incredibly easy to fall into a trap. Because they look exactly like annotations in Java or decorators in Python, most developers assume that adding #[SomeAttribute] above a method automatically "triggers" some behavior in the background. They think the PHP engine sees that tag and says, "Oh, I should run the logic inside SomeAttribute now."

Attributes aren't magic function calls

Let's prove that. Imagine you're building a simple API and you want to mark certain methods as "Admin Only." You might write something like this:

#[Attribute]
class AdminOnly {}

class UserController {
    #[AdminOnly]
    public function deleteUser(int $id) {
        echo "User deleted!";
    }
}

$controller = new UserController();
$controller->deleteUser(123);

If you run this, the output is simply "User deleted!". The #[AdminOnly] attribute didn't stop the execution, it didn't check a session, and it didn't throw an exception. It did absolutely nothing. I've seen junior devs spend hours wondering why their "security attribute" wasn't working, only to realize they were expecting the language to provide the execution logic for them.

Reflection is the engine that makes them useful

Here is the reality: Attributes are just structured metadata. They are like sticky notes attached to your code. A sticky note that says "Fragile" doesn't actually make a box fragile; it just tells whoever is handling the box that they should be careful. To make an Attribute "do" something, you have to write the code that reads the note.

In PHP, we do this using the Reflection API. You ask PHP to look at a class or method, check if a specific attribute is present, and then execute your own logic based on that finding. Here is how we actually make that AdminOnly example work:

#[Attribute]
class AdminOnly {}

class UserController {
    #[AdminOnly]
    public function deleteUser(int $id) {
        echo "User deleted!";
    }
}

$controller = new UserController();
$reflection = new ReflectionMethod($controller, 'deleteUser');
$attributes = $reflection->getAttributes(AdminOnly::class);

if (!empty($attributes)) {
    echo "Wait! This method requires admin privileges. Checking permissions... \n";
    // In a real app, you'd check your Auth service here.
}

$controller->deleteUser(123);

Now the logic is decoupled. The UserController doesn't need to know how permission checking works; it just declares that it needs it. The "Dispatcher" or "Router" handles the actual enforcement.

Creating dynamic attributes with arguments

Attributes become truly powerful when you pass data into them. You aren't limited to empty classes; you can define a constructor in your attribute class to capture specific configuration.

Take a routing system, for example. Instead of a giant array mapping URLs to controllers, you can put the route right on the method:

#[Attribute]
class Route {
    public function __construct(public string $path, public string $method = 'GET') {}
}

class ProductController {
    #[Route('/products/view', method: 'GET')]
    public function show() { 
        /* ... */ 
    }

    #[Route('/products/save', method: 'POST')]
    public function save() { 
        /* ... */ 
    }
}


When you instantiate the attribute via $attribute->newInstance(), PHP returns an actual object of the Route class, giving you full access to that $path and $method. It's a much cleaner way to organize metadata than using docblock comments (which are just strings that you'd have to parse with regex) or massive configuration files.




📋 Practical Task

Building a Reflection-Based Property Validator

Your task is to create a basic validation system using Attributes. Instead of manually checking every property in a class, you will build a validator that reads attributes to determine which properties are mandatory.

Requirements:

  • Create an attribute class called #[Required].
  • Create a UserRegistration class with several properties (e.g., username, email, bio). Mark username and email with the #[Required] attribute, but leave bio unmarked.
  • Write a Validator class with a method validate(object $obj).
  • Inside the validate method, use ReflectionClass and ReflectionProperty::getAttributes() to find all properties marked #[Required].
  • If a property marked #[Required] is null or an empty string, the validator should throw an Exception naming the missing field.

Test your code: Try instantiating UserRegistration without an email and ensure your validator catches it!

Rating
0 0

There are no comments for now.

to be the first to leave a comment.