PHP 8.5+ Microformats2 parser.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-07 10:24:32 -05:00
spec docs: minor formatting fix 2026-09-04 15:02:52 -05:00
src feat: expand enums to cover property types 2026-09-07 10:24:32 -05:00
test feat: expand enums to cover property types 2026-09-07 10:24:32 -05:00
.gitignore Initial project creation 2026-09-04 10:47:55 -05:00
composer.json docs: add README and CONTRIBUTING docs, use PHP 8.5+ 2026-09-04 10:57:07 -05:00
composer.lock docs: add README and CONTRIBUTING docs, use PHP 8.5+ 2026-09-04 10:57:07 -05:00
CONTRIBUTING.md docs: add README and CONTRIBUTING docs, use PHP 8.5+ 2026-09-04 10:57:07 -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 feat: expand enums to cover property types 2026-09-07 10:24:32 -05:00

weierophinney/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 weierophinney/microformats2

Usage

Parsing Microformats

use Weierophinney\Microformats2\Parser;
use Weierophinney\Microformats2\MicroformatType;
use Weierophinney\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 \Weierophinney\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 Weierophinney\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 Weierophinney\Microformats2\Parser;
use Weierophinney\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): ?\Weierophinney\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 Weierophinney\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 Weierophinney\Microformats2\Webmention\Comment;

/** @var Comment $webmention */
$webmention->source;  // 'https://source.example/reply'
$webmention->target;  // 'https://target.example/post'
$webmention->comment; // Microformat (h-entry) — the reply itself
$webmention->author;  // Microformat (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 Weierophinney\Microformats2\Webmention\Repost;

/** @var Repost $webmention */
$webmention->source; // 'https://source.example/repost'
$webmention->target; // 'https://target.example/post'
$webmention->author; // Microformat (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 Weierophinney\Microformats2\Webmention\Like;

/** @var Like $webmention */
$webmention->source; // 'https://source.example/like'
$webmention->target; // 'https://target.example/post'
$webmention->author; // Microformat (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 Weierophinney\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; // Microformat (h-card) or null — page author

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

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