Atom View
May 11, 2026 · View on GitHub
A CakePHP view class for generating Atom 1.0 feeds (RFC 4287).
- Template-less by default (same
serializeflow as RssView) - Friendly shorthands for the common case, full attribute control when you need it
- Mandatory fields enforced where the spec is strict (
id,title,updated) - Auto-emits only the namespace declarations actually used
- CDATA-wraps HTML content; leaves plain text un-CDATA'd for cleaner output
If you're new to Atom, the README has a RSS vs Atom guidance section explaining when to pick which.
Configs
setNamespace() registers a custom namespace prefix that can be used as a prefix:tag key in the input. The matching xmlns:prefix="..." decl is only emitted at the root if the prefix actually appears somewhere in the rendered tree.
The Atom default namespace (http://www.w3.org/2005/Atom) is bound automatically — you do not need to register it.
Setup
Enable the Atom extension in your routes (or bootstrap):
Router::extensions(['atom']);
Activate the view class in your action:
$this->viewBuilder()->setClassName('Feed.Atom');
Or wire it once via view negotiation so every .atom request resolves to AtomView:
// AppController.php
public function viewClasses(): array {
return [\Feed\View\AtomView::class];
}
Set the data and tell the builder which viewVar to serialize:
$this->set(['feed' => $feed]);
$this->viewBuilder()->setOption('serialize', 'feed');
Input shape
All fields are optional unless noted. Bare strings and structured arrays are accepted interchangeably:
$feed = [
'id' => 'http://example.org/feed', // REQUIRED — globally unique feed identifier (URI)
'title' => 'My blog', // REQUIRED
'updated' => '2026-05-11T12:00:00Z', // REQUIRED — DateTime / DateTimeImmutable / int / string
'subtitle' => 'Notes from the field',
'link' => 'http://example.org/', // shorthand: <link rel="alternate" href="..."/>
'author' => 'Jane Doe', // shorthand: <author><name>Jane Doe</name></author>
'rights' => 'Copyright 2026 Example, Inc.',
'generator' => 'CakePHP Feed Plugin',
'icon' => '/favicon.ico',
'logo' => '/logo.png',
'category' => 'tech',
'entries' => [
[
'id' => 'http://example.org/posts/1', // REQUIRED — globally unique entry identifier
'title' => 'A post', // REQUIRED
'updated' => '2026-05-11', // REQUIRED
'published' => '2026-05-10', // optional, distinct from `updated`
'link' => 'http://example.org/posts/1',
'summary' => 'A short teaser.',
'content' => ['@type' => 'html', '@' => '<p>Full HTML body.</p>'],
'author' => 'Jane',
'category' => 'tech',
],
],
];
Field-by-field reference
id — required (feed and per-entry)
Globally unique IRI. Atom readers use this to deduplicate, so it must be stable across renders. A common choice is the canonical permalink; for synthetic feeds a urn:uuid: is fine. The view does not generate one for you — pick one and stick with it.
title, subtitle, rights, summary, content — text constructs
Atom distinguishes plain text, HTML, and XHTML. Plain string input becomes type="text" (escaped). For HTML, pass the full attribute shape:
'content' => ['@type' => 'html', '@' => '<p>This <em>is</em> HTML.</p>'],
The view CDATA-wraps type="html" bodies so the markup survives the XML encode.
XHTML text constructs are not currently supported by AtomView. The serializer only supports plain text and type="html" text constructs at the moment; passing @type => 'xhtml' raises a SerializationFailureException instead of emitting invalid escaped markup.
updated, published — date constructs
Accepts DateTimeInterface, an int unix timestamp, or any string DateTime can parse. All inputs are normalized to RFC 3339 (Y-m-d\TH:i:sP), which is what Atom requires.
updated is the last modification time and is required. published is the original publication time and is optional but strongly recommended — distinguishing the two is one of Atom's main advantages over RSS. Missing required fields (id, title, updated on the feed; id, title, updated on each entry) raise a SerializationFailureException.
link — single, multiple, or shorthand
// Shorthand — defaults to rel="alternate"
'link' => 'http://example.org/posts/1',
// Single, explicit attributes
'link' => ['@href' => '/feed.atom', '@rel' => 'self', '@type' => 'application/atom+xml'],
// Multiple links on one entry (only Atom supports this — RSS does not)
'link' => [
['@href' => 'http://example.org/posts/1', '@rel' => 'alternate'],
['@href' => 'http://example.org/posts/1/comments','@rel' => 'replies'],
['@href' => 'http://example.org/audio/1.mp3', '@rel' => 'enclosure',
'@type' => 'audio/mpeg', '@length' => '12345'],
],
The default for a string-shorthand link is rel="alternate". The default xmlns is the Atom namespace, so a self-link to the feed itself is ['@href' => '/feed.atom', '@rel' => 'self', '@type' => 'application/atom+xml'].
author, contributor — person constructs
Atom person constructs have a required <name> plus optional <email> and <uri>. A bare string becomes the name. Multiple people pass as a list:
// Shorthand
'author' => 'Jane Doe',
// Structured
'author' => ['name' => 'Jane Doe', 'email' => 'jane@example.org', 'uri' => 'http://example.org/~jane'],
// Multiple (Atom-only — RSS has one author per item)
'author' => [
['name' => 'Alice'],
['name' => 'Bob', 'email' => 'bob@example.org'],
],
contributor works identically.
category — single, list, or shorthand
// Shorthand (term only)
'category' => 'tech',
// Single, with scheme + label
'category' => ['@term' => 'php', '@scheme' => 'http://example.org/tags', '@label' => 'PHP'],
// Multiple
'category' => [
'tech',
['@term' => 'php', '@scheme' => 'http://example.org/tags'],
],
Extension namespaces
Built in: dc, content, itunes. Use them as prefix:tag keys and the matching xmlns:prefix decl appears automatically:
$feed = [
'id' => '...', 'title' => '...', 'updated' => '...',
'entries' => [
[
'id' => '...', 'title' => '...', 'updated' => '...',
'dc:creator' => 'Jane',
'content:encoded' => '<p>HTML body</p>', // not CDATA-wrapped automatically here; use the html-content form instead
],
],
];
For a custom namespace, register the prefix once (either via setNamespace() on the view, or by passing a top-level namespace key in the input):
$feed = [
'namespace' => ['ex' => 'http://example.org/ext'],
'id' => '...', 'title' => '...', 'updated' => '...',
'ex:owner' => 'Mark',
];
Using a prefix:tag whose prefix has not been registered raises RuntimeException — silently emitting a broken namespace declaration is worse than failing loudly.
Examples
Minimal feed
$feed = [
'id' => 'http://example.org/feed',
'title' => 'My blog',
'updated' => time(),
];
$this->set(['feed' => $feed]);
$this->viewBuilder()->setClassName('Feed.Atom');
$this->viewBuilder()->setOption('serialize', 'feed');
Renders:
<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<id>http://example.org/feed</id>
<title>My blog</title>
<updated>2026-05-11T12:00:00+00:00</updated>
</feed>
Self-link + alternate, multiple entries
$feed = [
'id' => 'http://example.org/feed',
'title' => 'My blog',
'updated' => '2026-05-11T12:00:00Z',
'link' => [
['@href' => 'http://example.org/', '@rel' => 'alternate'],
['@href' => 'http://example.org/feed.atom', '@rel' => 'self', '@type' => 'application/atom+xml'],
],
'author' => ['name' => 'Jane Doe', 'email' => 'jane@example.org'],
'entries' => [
[
'id' => 'http://example.org/posts/2',
'title' => 'Second post',
'updated' => '2026-05-11T10:00:00Z',
'published' => '2026-05-11T08:00:00Z',
'link' => 'http://example.org/posts/2',
'summary' => 'About Atom.',
'content' => ['@type' => 'html', '@' => '<p>Atom is great.</p>'],
],
[
'id' => 'http://example.org/posts/1',
'title' => 'First post',
'updated' => '2026-05-10T12:00:00Z',
'link' => 'http://example.org/posts/1',
],
],
];
Podcast feed (RSS-only territory)
If you need a podcast feed, use RssView with the itunes: namespace — Apple's spec requires RSS. See Rss.md.