- PHP 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| spec | ||
| src | ||
| test | ||
| .gitignore | ||
| composer.json | ||
| composer.lock | ||
| CONTRIBUTING.md | ||
| phpcs.xml.dist | ||
| phpunit.xml.dist | ||
| README.md | ||
weierophinney/microformats2
A PHP 8.5+ Microformats2 parser with Webmention classification support.
Why PHP 8.5?
- PHP 8.4 introduced
Dom\HtmlDocument, which includesquerySelector()andquerySelectorAll(), simplifying DOM traversal and detection of elements with specific attributes or CSS classes. - PHP 8.5 introduced the
Urinamespace and its URI parsing/representation implementations. This library usesUri\WhatWg\Urlfor 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:
- Embedded h-card — The entry's
authorproperty is an h-card microformat. - Author URL — The entry's
authorproperty is a URL; fetches the author page via$urlFetcherand looks for a representative h-card (matchingurl == uid == page URL, or matching arel=melink). - String name — The entry's
authorproperty is a plain text name; creates a minimal h-card. - Parent h-feed author — The entry's parent
h-feedhas anauthorproperty. - rel=author link — A
rel="author"link on the source page (backcompat). - Source page h-card — An h-card on the source page whose
urlmatches 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