Turn PHP Attributes Into Docs With Signal

Published on by

Turn PHP Attributes Into Docs With Signal image

Signal is a PHP library from Steve McDougall that reads attributes you place on your classes and methods and generates documentation from them. The idea is to keep API references in the same place as the code they describe, so the docs change when the code changes instead of going stale in a separate file. It requires PHP 8.5 and Symfony Console, and it is distributed under the MIT license.

The library ships 24 attributes split across three groups: attributes that label what a class is, attributes that record a class's relationships and status, and attributes that document individual methods. You annotate your code, run one command, and get back Markdown for humans and JSON for tooling.

Labelling what a class is

The first group of attributes describes the role a class plays in your application. There are 13 of them, covering common building blocks such as #[Module], #[Service], #[Repository], #[Action], #[Controller], #[Event], #[Listener], #[Middleware], #[Job], #[Command], #[Query], #[Aggregate], and #[ValueObject]. Each one takes an optional description and a list of tags:

use JustSteveKing\Signal\Attributes\Service;
 
#[Service(
description: 'Issues and revokes API tokens for authenticated users',
tags: ['auth', 'tokens'],
)]
final class TokenService
{
// ...
}

When Signal generates output, it groups classes by these types, so every controller lands in one section and every service in another.

Recording relationships and status

A second group documents how a class connects to the rest of the system and where it sits in its lifecycle. #[DependsOn] records a collaborator, #[ListensTo] ties a listener to an event, and #[Deprecated] and #[Internal] flag classes that callers should treat with care:

use JustSteveKing\Signal\Attributes\Listener;
use JustSteveKing\Signal\Attributes\ListensTo;
use JustSteveKing\Signal\Attributes\DependsOn;
 
#[Listener(description: 'Sends a welcome email after registration')]
#[ListensTo(event: UserRegistered::class)]
#[DependsOn(class: MailService::class)]
final class SendWelcomeEmail
{
// ...
}

These relationships are the kind of detail that usually lives in someone's head or in a diagram that no one updates. Putting them next to the class keeps them honest.

Documenting methods

The third group describes what individual methods do. #[Route] records an HTTP method and path, #[Authorize] notes the ability a caller needs, #[Validates] captures validation rules field by field, and #[Cached] records a TTL. Three more attributes document behaviour that a method signature alone does not reveal: #[Emits] for events a method dispatches, #[Throws] for the exceptions it can raise, and #[SideEffect] for observable work such as sending mail or writing to a queue.

use JustSteveKing\Signal\Attributes\Route;
use JustSteveKing\Signal\Attributes\Authorize;
use JustSteveKing\Signal\Attributes\Validates;
use JustSteveKing\Signal\Attributes\Emits;
use JustSteveKing\Signal\Attributes\Throws;
use JustSteveKing\Signal\Attributes\SideEffect;
 
#[Route(method: 'POST', path: '/api/subscriptions', description: 'Start a subscription')]
#[Authorize(ability: 'subscriptions.create')]
#[Validates(field: 'plan', rules: 'required|in:monthly,yearly')]
#[Emits(event: 'SubscriptionStarted')]
#[SideEffect(description: 'Charges the customer through the payment gateway', tags: ['billing'])]
#[Throws(exception: PaymentFailedException::class, description: 'If the gateway rejects the charge')]
public function store(Request $request): JsonResponse
{
// ...
}

The #[SideEffect] and #[Throws] attributes are worth calling out, because they document the things a reader cannot infer from a return type. Knowing a method charges a card or can throw PaymentFailedException is the sort of fact that otherwise only surfaces when something breaks.

Configuration and output

Signal reads a signal.json file at the root of your project. It tells the generator which directory to scan, which formats to write, where to put them, and which paths to skip:

{
"input": "src/",
"output": {
"format": ["markdown", "json"],
"path": "docs/"
},
"exclude": [
"src/Attributes/"
]
}

With that in place, one command produces the documentation:

php vendor/bin/signal generate

The Markdown output is organised by class type with a table of contents, and the JSON output carries the same metadata in a form other tools can read, which makes it a starting point for things like generating an OpenAPI description or feeding an internal service catalogue.

Install it with Composer:

composer require juststeveking/signal

You can read the source and the full list of attributes on GitHub.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Cube

Laravel Newsletter

Join 40k+ other developers and never miss out on new tips, tutorials, and more.

image
Acquaint Softtech

Hire Laravel developers with AI expertise at $20/hr. Get started in 48 hours.

Visit Acquaint Softtech
Kirschbaum logo

Kirschbaum

Providing innovation and stability to ensure your web application succeeds.

Kirschbaum
Laravel Cloud logo

Laravel Cloud

Easily create and manage your servers and deploy your Laravel applications in seconds.

Laravel Cloud
Lucky Media logo

Lucky Media

Get Lucky Now - the ideal choice for Laravel Development, with over a decade of experience!

Lucky Media
Shift logo

Shift

Running an old Laravel version? Instant, automated Laravel upgrades and code modernization to keep your applications fresh.

Shift
No Compromises logo

No Compromises

Joel and Aaron, the two seasoned devs from the No Compromises podcast, are now available to hire for your Laravel project. ⬧ Flat rate of $9500/mo. ⬧ No lengthy sales process. ⬧ No contracts. ⬧ 100% money back guarantee.

No Compromises
Harpoon: Next generation time tracking and invoicing logo

Harpoon: Next generation time tracking and invoicing

The next generation time-tracking and billing software that helps your agency plan and forecast a profitable future.

Harpoon: Next generation time tracking and invoicing
Acquaint Softtech logo

Acquaint Softtech

Acquaint Softtech offers AI-ready Laravel developers who onboard in 48 hours at $3000/Month with no lengthy sales process and a 100 percent money-back guarantee.

Acquaint Softtech
Tinkerwell logo

Tinkerwell

The must-have code runner for Laravel developers. Tinker with AI, autocompletion and instant feedback on local and production environments.

Tinkerwell
SaaSykit: Laravel SaaS Starter Kit logo

SaaSykit: Laravel SaaS Starter Kit

SaaSykit is a Multi-tenant Laravel SaaS Starter Kit that comes with all features required to run a modern SaaS. Payments, Beautiful Checkout, Admin Panel, User dashboard, Auth, Ready Components, Stats, Blog, Docs and more.

SaaSykit: Laravel SaaS Starter Kit
PhpStorm logo

PhpStorm

The go-to PHP IDE with extensive out-of-the-box support for Laravel and its ecosystem.

PhpStorm

The latest

View all →
USAIGE: Track Token Usage and Costs for Laravel AI SDK Requests image

USAIGE: Track Token Usage and Costs for Laravel AI SDK Requests

Read article
Help make Filament faster! image

Help make Filament faster!

Read article
Yammi Audit Log: Track Who Really Made a Change Across Jobs and Queues image

Yammi Audit Log: Track Who Really Made a Change Across Jobs and Queues

Read article
Route Metadata Support in Laravel 13.17 image

Route Metadata Support in Laravel 13.17

Read article
Ship AI with Laravel: Failover, Queues, and Middleware for AI Agents image

Ship AI with Laravel: Failover, Queues, and Middleware for AI Agents

Read article
Monitor and Control Schedules, Queues, and Errors in Laravel with Watchtower image

Monitor and Control Schedules, Queues, and Errors in Laravel with Watchtower

Read article