PHP TypeLang Help

Extending

None of the tags listed in the sidebar are special-cased inside the parser. @param, @see, @template and every other tag are declared the exact same way, and a tag of your own is added through that very same mechanism: a platform.

Adding one takes a few small parts that fit together — a tag object, its definition, the grammar the definition is written in, the combinators it leans on, and the platform that registers the lot. This page shows how they connect; each part is covered in depth on its own page, linked as it comes up and again under Where to Go Next.

Adding a Tag in Practice

Recognizing a @money tag of your own — documenting a monetary amount and a currency — starts from the shape of the line it should accept:

"@money" <Amount> <Currency> [ <Description> ]

Describing that shape is all the parser needs: it becomes the tag's specification, written with the Spec grammar DSL — an integer, then a name, then an optional description:

$spec = Spec::sequence( Spec::rule(IntegerCombinator::NAME, 'amount', 'Amount'), Spec::rule(NameCombinator::NAME, 'currency', 'Currency'), Spec::maybe( Spec::rule(DescriptionCombinator::NAME, 'description'), ), );

Each Spec::rule() takes the combinator that reads that part of the line, the alias its captured value is stored under, and — optionally — the name (e.g. Amount or Currency) it renders as in the grammar above.

The value object the parsed line becomes is a small, immutable tag class:

final class MoneyTag extends Tag { public function __construct( string $name, public readonly int $amount, public readonly string $currency, ?DescriptionInterface $description = null, ) { parent::__construct($name, $description); } }

A specification is not used on its own — it is declared inside a definition, which pairs it with a create() method turning a match into that tag object:

final class MoneyTagDefinition extends TagDefinition { public const string NAME = 'money'; public function __construct() { parent::__construct(self::NAME, Spec::sequence( Spec::rule(IntegerCombinator::NAME, 'amount', 'Amount'), Spec::rule(NameCombinator::NAME, 'currency', 'currency'), Spec::maybe( Spec::rule(DescriptionCombinator::NAME, 'description'), ), )); } public function create(string $name, TagPayload $result): MoneyTag { return new MoneyTag( name: $name, amount: $result->get('amount'), currency: $result->get('currency'), description: $result->find('description'), ); } }

The aliases declared in the spec — 'amount', 'currency', 'description' — are exactly how create() reads the matched pieces back: the TagPayload it receives is keyed by them, so $result->get('amount') returns whatever the amount rule captured.

Finally, the definition is registered by contributing a platform to the parser:

final class MoneyPlatform implements PlatformInterface { public string $name = 'money'; public iterable $aliases = []; public iterable $combinators = []; public iterable $tags { get => [ MoneyTagDefinition::NAME => new MoneyTagDefinition(), ]; } } $parser = new DocBlockParser(platforms: [ new MoneyPlatform(), ]); $block = $parser->parse('/** @money 100 USD */');

A platform's four properties register the three kinds of thing the parser needs:

  • $tags registers the definitions, keyed by each tag's canonical (lower-case) name.

  • $combinators registers any combinators the definitions reference that are not built in already (@money needs none).

  • $aliases maps an alternative spelling to a canonical tag name.

Where to Go Next

Tags

What a parsed tag object is. See Tags.

Definitions

How a tag's parsing and construction are declared. See Definitions.

Grammar

The Spec DSL a definition's body is written in. See Grammar.

Combinators

The building blocks a grammar reads with. See Combinators.

Platforms

Bundling and registering it all. See Platforms.

12 July 2026