PHP 8.5+ Microformats2 parser.
  • PHP 92.9%
  • HTML 6.5%
  • Makefile 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-20 15:04:59 -05:00
spec docs: minor formatting fix 2026-09-04 15:02:52 -05:00
src Rename: weierophinney -> phly 2026-09-20 14:15:31 -05:00
test Rename: weierophinney -> phly 2026-09-20 14:15:31 -05:00
.gitattributes qa: package build/publish toolchain 2026-09-20 15:04:59 -05:00
.gitignore qa: package build/publish toolchain 2026-09-20 15:04:59 -05:00
composer.json Rename: weierophinney -> phly 2026-09-20 14:15:31 -05:00
composer.lock Rename: weierophinney -> phly 2026-09-20 14:15:31 -05:00
CONTRIBUTING.md docs: add README and CONTRIBUTING docs, use PHP 8.5+ 2026-09-04 10:57:07 -05:00
Makefile qa: package build/publish toolchain 2026-09-20 15:04:59 -05:00
phpcs.xml.dist refactor/feature: Enum for microformat types, ability to classify webmention types 2026-09-06 17:20:58 -05:00
phpunit.xml.dist Initial implementation 2026-09-04 12:26:07 -05:00
README.md Rename: weierophinney -> phly 2026-09-20 14:15:31 -05:00

phly/microformats2

A PHP 8.5+ Microformats2 parser with Webmention classification support.

Why PHP 8.5?

  • PHP 8.4 introduced Dom\HtmlDocument, which includes querySelector() and querySelectorAll(), simplifying DOM traversal and detection of elements with specific attributes or CSS classes.
  • PHP 8.5 introduced the Uri namespace and its URI parsing/representation implementations. This library uses Uri\WhatWg\Url for validating, parsing, and manipulating URLs discovered in microformats.

Specifications

Specifications are pulled from the Microformats wiki and converted to markdown in ./spec/.

Installation

composer require phly/microformats2

Usage

Parsing Microformats

use Phly\Microformats2\Parser;
use Phly\Microformats2\MicroformatType;
use Phly\Microformats2\MicroformatPropertyType;

$parser = new Parser();

// Parse HTML with a base URL for resolving relative URLs
$result = $parser->parse($html, $sourceUrl);

// Access parsed microformat items
foreach ($result->items as $item) {
    // $item is a Microformat object
    echo $item->type[0]->value;  // 'h-entry' (MicroformatType enum)
    $item->type[0] === MicroformatType::HEntry; // true
    echo $item->properties;      // ['name' => ['My Post'], ...]
    echo $item->id;              // DOM element id, or null
    echo $item->children;        // Nested Microformat objects
    echo $item->findParent();    // Parent Microformat, or null
}

// Access rel microformats
$authorUrls = $result->rels['author'] ?? [];
$meUrls     = $result->rels['me'] ?? [];

// Serialize to JSON (mf2 JSON format)
$json = json_encode($result);

Working with Microformat Properties

Microformat properties are stored as arrays keyed by property name. The structure depends on the property type:

$entry = $result->items[0];

// p-* properties (text): array of strings
$name = $entry->properties['name'][0];      // 'My Post'
$note = $entry->properties['note'][0];      // 'A short note'

// u-* properties (URLs): array of strings (resolved URLs)
$url   = $entry->properties['url'][0];      // 'https://example.com/post'
$photo = $entry->properties['photo'][0];    // 'https://example.com/photo.jpg'

// dt-* properties (dates/times): array of strings
$published = $entry->properties['published'][0]; // '2024-01-15T10:30:00Z'

// Nested microformats (e.g., p-author with an h-card)
$author = $entry->properties['author'][0];
if ($author instanceof \Phly\Microformats2\Microformat) {
    echo $author->type[0]->value;             // 'h-card'
    echo $author->properties['name'][0];      // 'Jane Doe'
    echo $author->properties['photo'][0];     // 'https://example.com/photo.jpg'
}

Property Types

The library provides a MicroformatPropertyType enum for type-safe property references. This enum covers all officially documented properties from the microformats2 wiki.

use Phly\Microformats2\MicroformatPropertyType;

// Look up a property type by its class name
$type = MicroformatPropertyType::tryFrom('p-name');
// Returns MicroformatPropertyType::PName

$type = MicroformatPropertyType::tryFrom('u-in-reply-to');
// Returns MicroformatPropertyType::UInReplyTo

$type = MicroformatPropertyType::tryFrom('myorg-p-name');
// Returns null (unknown/vendor-prefixed properties are not in the enum)

// Get the type prefix
MicroformatPropertyType::PName->prefix();     // 'p'
MicroformatPropertyType::UUrl->prefix();      // 'u'
MicroformatPropertyType::DTPublished->prefix(); // 'dt'
MicroformatPropertyType::EContent->prefix();  // 'e'

// Get the property name without the prefix
MicroformatPropertyType::PName->name();       // 'name'
MicroformatPropertyType::UInReplyTo->name();  // 'in-reply-to'

Querying Property Types

The Microformat object exposes methods to check property types:

$entry = $result->items[0];

// Get the property type for a specific property
$type = $entry->getPropertyType('name');
// Returns MicroformatPropertyType::PName

$type = $entry->getPropertyType('url');
// Returns MicroformatPropertyType::UUrl

// Check if properties of specific types exist
$hasText = $entry->hasPropertiesOfType([MicroformatPropertyType::PName]);
$hasUrl  = $entry->hasPropertiesOfType([MicroformatPropertyType::UUrl]);

// Check by prefix
$hasPProperties = $entry->hasPropertiesWithPrefix(['p']); // true if any p-* exist
$hasEProperties = $entry->hasPropertiesWithPrefix(['e']); // true if any e-* exist

// Get the raw class name for a property
$rawClass = $entry->getRawPropertyClass('name'); // 'p-name'

Vendor-Prefixed Properties

Properties with vendor prefixes (e.g., myorg-p-name) are recognized by the parser but represented as MicroformatPropertyType::Extension:

// A vendor-prefixed property is parsed but uses the Extension type
$type = $entry->getPropertyType('name'); // MicroformatPropertyType::Extension
$raw  = $entry->getRawPropertyClass('name'); // 'myorg-p-name'

Accessor Methods

The Microformat class provides convenient accessor methods for common property lookups:

$entry = $result->items[0];

// getProperty() — returns the first string value, or null
$name = $entry->getProperty('name');         // 'My Post'
$note = $entry->getProperty('note');         // 'A short note' or null
$miss = $entry->getProperty('nonexistent');  // null

// getProperties() — returns all string values as an array
$categories = $entry->getProperties('category'); // ['php', 'web', 'html']

// Content accessors (for e-content properties)
// getContent() and related methods access the 'content' property specifically.
// Note: getProperty('content') returns null — use these instead.
$htmlContent = $entry->getHtmlContent();     // Raw HTML from first e-content
$textContent = $entry->getTextContent();     // Plain text from first e-content
$allHtml     = $entry->getAllHtmlContent();   // Array of all raw HTML values
$allText     = $entry->getAllTextContent();   // Array of all plain text values

Accessing the DOM Element

Each Microformat provides access to its underlying DOM element:

$mf = $result->items[0];

// Get the DOM element
$element = $mf->getElement();

// Get the owning HTML document
$doc = $mf->getDocument();

// The element is a Dom\Element — use standard DOM methods
$element->getAttribute('class');
$element->textContent;
$element->innerHTML;

Webmention Classification

The library can classify webmentions by analyzing the source page's microformats relative to the target URL. This implements the response type detection from the IndieWeb.

Classifying a Webmention

use Phly\Microformats2\Parser;
use Phly\Microformats2\Webmention\Classifier;

$parser = new Parser();

// Parse the source page (the page that sent the webmention)
$result = $parser->parse($sourceHtml, $sourceUrl);

// Classify the webmention
$classifier = new Classifier(
    result:     $result,
    sourceUrl:  $sourceUrl,   // URL of the page sending the mention
    targetUrl:  $targetUrl,   // URL being mentioned
    urlFetcher: fn(string $url): ?\Phly\Microformats2\Result => $this->fetchAndParse($url),
);

$webmention = $classifier->classify();

The $urlFetcher is a callable that fetches a URL and returns a parsed Result, or null on failure. It is used by the AuthorResolver to fetch external author pages (e.g., when the author is specified as a URL rather than an embedded h-card). If omitted, external author resolution is skipped.

Webmention Result Types

The classify() method returns one of five result types. All extend WebmentionResult which provides $source and $target properties.

Deletion

The source page no longer links to the target. The content has been deleted or removed.

use Phly\Microformats2\Webmention\Deletion;

/** @var Deletion $webmention */
$webmention->source; // 'https://source.example/post'
$webmention->target; // 'https://target.example/post'

Comment

The source page is a reply to the target. Detected via u-in-reply-to.

use Phly\Microformats2\Webmention\Comment;

/** @var Comment $webmention */
$webmention->source;  // 'https://source.example/reply'
$webmention->target;  // 'https://target.example/post'
$webmention->comment; // Webmention (h-entry) — the reply itself
$webmention->author;  // Webmention (h-card) or null — the reply's author

// Access the comment content using accessor methods
$entry = $webmention->comment;
$name  = $entry->getProperty('name');     // Reply title
$html  = $entry->getHtmlContent();        // Reply HTML
$text  = $entry->getTextContent();        // Reply plain text

// Access the author
if ($webmention->author !== null) {
    $authorName  = $webmention->author->getProperty('name');
    $authorPhoto = $webmention->author->getProperty('photo');
    $authorUrl   = $webmention->author->getProperty('url');
}

Repost

The source page is a repost/boost of the target. Detected via u-repost-of.

use Phly\Microformats2\Webmention\Repost;

/** @var Repost $webmention */
$webmention->source; // 'https://source.example/repost'
$webmention->target; // 'https://target.example/post'
$webmention->author; // Webmention (h-card) or null — who reposted

if ($webmention->author !== null) {
    $name  = $webmention->author->getProperty('name');
    $photo = $webmention->author->getProperty('photo');
}

Like

The source page is a like/favorite of the target. Detected via u-like-of.

use Phly\Microformats2\Webmention\Like;

/** @var Like $webmention */
$webmention->source; // 'https://source.example/like'
$webmention->target; // 'https://target.example/post'
$webmention->author; // Webmention (h-card) or null — who liked

if ($webmention->author !== null) {
    $name  = $webmention->author->getProperty('name');
    $photo = $webmention->author->getProperty('photo');
}

Mention

A generic mention — the source page links to the target but does not have a specific response type.

use Phly\Microformats2\Webmention\Mention;

/** @var Mention $webmention */
$webmention->source; // 'https://source.example/mention'
$webmention->target; // 'https://target.example/post'
$webmention->title;  // Page title string or null
$webmention->author; // Webmention (h-card) or null — page author

if ($webmention->author !== null) {
    $name  = $webmention->author->getProperty('name');
    $photo = $webmention->author->getProperty('photo');
}

Serializable Webmentions

The Webmention class provides a DOM-free, serializable representation of microformat data. It mirrors the Microformat API but without getElement() or getDocument() methods, making it safe to store, cache, or transmit.

All properties are readonly and use constructor promotion.

use Phly\Microformats2\Webmention\Webmention;

// Convert a Microformat to a Webmention (recursively)
$webmention = Webmention::fromMicroformat($microformat);

// Access the same data as Microformat
$webmention->type;       // list<MicroformatType>
$webmention->properties; // array<string, list<mixed>>
$webmention->id;         // ?string
$webmention->children;   // list<Webmention>
$webmention->parent;     // ?Webmention

// Same accessor methods as Microformat
$name  = $webmention->getProperty('name');
$html  = $webmention->getHtmlContent();
$text  = $webmention->getTextContent();

// Serialize to array (mf2 JSON format)
$array = $webmention->toArray();

// Reconstruct from serialized data
$restored = Webmention::fromJson($array);

Nested microformat objects in properties are automatically converted to Webmention instances during fromMicroformat(), and reconstructed from arrays during fromJson().

Author Resolution

The AuthorResolver implements the full authorship-spec algorithm. It resolves authors in this order:

  1. Embedded h-card — The entry's author property is an h-card microformat.
  2. Author URL — The entry's author property is a URL; fetches the author page via $urlFetcher and looks for a representative h-card (matching url == uid == page URL, or matching a rel=me link).
  3. String name — The entry's author property is a plain text name; creates a minimal h-card.
  4. Parent h-feed author — The entry's parent h-feed has an author property.
  5. rel=author link — A rel="author" link on the source page (backcompat).
  6. Source page h-card — An h-card on the source page whose url matches the author-page URL.

If no $urlFetcher is provided, steps requiring external fetches are skipped. If no author can be determined, null is returned.

Using Without a URL Fetcher

If you only need to classify based on inline markup (no external author-page fetching):

$classifier = new Classifier($result, $sourceUrl, $targetUrl);
$webmention = $classifier->classify();

// Author will be null if it requires fetching an external page
// Author will be resolved if it's embedded in the source HTML

Testing

php vendor/bin/phpunit