What Is Signal?
Signal is an open-source PHP library by Steve McDougall that turns native PHP 8.x attributes into living documentation. Instead of maintaining a separate wiki or API reference that drifts out of date, you annotate your classes and methods directly, run one CLI command, and get back Markdown for humans and JSON for tooling. It requires PHP 8.5 and Symfony Console, and is distributed under the MIT license.
Three Groups of Attributes
Signal ships 24 attributes organised into three logical groups.
1. Labelling What a Class Is
Thirteen attributes describe the architectural role of a class: #[Module], #[Service], #[Repository], #[Action], #[Controller], #[Event], #[Listener], #[Middleware], #[Job], #[Command], #[Query], #[Aggregate], and #[ValueObject]. Each accepts an optional description and a tags array.
use JustSteveKing\Signal\Attributes\Service;
#[Service(
description: 'Issues and revokes API tokens for authenticated users',
tags: ['auth', 'tokens'],
)]
final class TokenService
{
// ...
}
The generator groups output by these types, so every controller lands in one section and every service in another.
2. Recording Relationships and Status
A second group documents how a class connects to the rest of the system. #[DependsOn] records a collaborator, #[ListensTo] ties a listener to an event, and #[Deprecated] / #[Internal] flag classes that callers should treat with care.
#[Listener(description: 'Sends a welcome email after registration')]
#[ListensTo(event: UserRegistered::class)]
#[DependsOn(class: MailService::class)]
final class SendWelcomeEmail
{
// ...
}
This is the kind of relationship detail that usually lives only in someone's head or in a diagram nobody updates.
3. Documenting Methods
The third group targets individual methods. #[Route] records an HTTP method and path, #[Authorize] notes the required ability, #[Validates] captures field-level rules, and #[Cached] records a TTL. Three more attributes surface behaviour invisible in a method signature: #[Emits] for dispatched events, #[Throws] for exceptions, and #[SideEffect] for observable work like charging a card or writing to a queue.
#[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
{
// ...
}
#[SideEffect] and #[Throws] are particularly valuable — they document facts a return type alone cannot convey.
Configuration and Output
Signal reads a signal.json file at the project root:
{
"input": "src/",
"output": {
"format": ["markdown", "json"],
"path": "docs/"
},
"exclude": [
"src/Attributes/"
]
}
Then generate your docs with a single command:
php vendor/bin/signal generate
The Markdown output includes a table of contents organised by class type. The JSON output carries the same metadata in a machine-readable form, making it a useful starting point for generating OpenAPI descriptions or feeding an internal service catalogue.
Installation
composer require juststeveking/signal
Key Takeaways
- Signal uses native PHP attributes — no docblock parsing, no external annotation syntax.
- 24 attributes cover class roles, inter-class relationships, and method-level behaviour.
- Outputs both Markdown (human-readable) and JSON (machine-readable) from one command.
- Keeping docs next to code means they update when the code updates, reducing documentation drift.
- Requires PHP 8.5 and Symfony Console; MIT licensed.
- The JSON output can seed OpenAPI specs or internal service catalogues.
Source: Turn PHP Attributes Into Docs With Signal — Laravel News