<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://draganescu.github.io/feed.xml" rel="self" type="application/atom+xml" /><link href="https://draganescu.github.io/" rel="alternate" type="text/html" /><updated>2026-09-26T13:19:40+00:00</updated><id>https://draganescu.github.io/feed.xml</id><title type="html">RTO and Raster</title><subtitle>A design pattern and a PHP framework for websites made by people and agents</subtitle><author><name>Andrei Draganescu</name></author><entry><title type="html">RTO: Request, Template, Object</title><link href="https://draganescu.github.io/rto/specs/2014/06/29/rto.html" rel="alternate" type="text/html" title="RTO: Request, Template, Object" /><published>2026-09-26T12:00:00+00:00</published><updated>2026-09-26T12:00:00+00:00</updated><id>https://draganescu.github.io/rto/specs/2014/06/29/rto</id><content type="html" xml:base="https://draganescu.github.io/rto/specs/2014/06/29/rto.html"><![CDATA[<p><em>Version 2 (2026). The first version, from 2014, described the three steps.
This version describes the whole pattern: how data is written, formats,
the URL, failure, caching and the standard objects. Raster is the reference
implementation.</em></p>

<h2 id="1-what-rto-is-for">1. What RTO is for</h2>

<p>RTO is a design pattern for <strong>web artefacts</strong>: sites, landing pages, blogs,
newsletters and small apps. It is not for applications in general.</p>

<p>The difference matters. A website is a collection of information presented in
various ways; an application is a tool that does something. A website has a
sitemap, and an application doesn’t. RTO is built around that fact: <strong>the page
is the unit</strong>. You can list the pages of an artefact before building it, and
most people who visit only read.</p>

<p>RTO grew out of MVC, but reverses who decides. In MVC a controller decides what
data a view receives. In RTO the <strong>template declares what it needs, and pulls
it</strong>.</p>

<h2 id="2-the-three-steps">2. The three steps</h2>

<ol>
  <li>A <strong>request</strong> names a resource and a format.</li>
  <li>The request is mapped to a <strong>template</strong>.</li>
  <li>The template is mapped to <strong>objects</strong>, which return data.</li>
</ol>

<p>A single envelope (one front controller) does the mapping, runs the objects the
template points to, and returns the response. There are no per-page
controllers.</p>

<h3 id="21-the-request">2.1 The request</h3>

<p>A request is a key-value pair: the <strong>URL</strong> describes the data, and the
<strong>format</strong> is how to present it. <code class="language-plaintext highlighter-rouge">/news</code> is the news in HTML. <code class="language-plaintext highlighter-rouge">/news.rss</code> is the
same news as RSS. <code class="language-plaintext highlighter-rouge">/news/news_page/2</code> is the second page of it.</p>

<p>The URL is a small query language written as a path. An RTO implementation
defines its grammar. Raster’s is:</p>

<table>
  <thead>
    <tr>
      <th>URL</th>
      <th>Meaning</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/about</code></td>
      <td>the page <code class="language-plaintext highlighter-rouge">about</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/news/news_item/&lt;slug or id&gt;</code></td>
      <td>one item of the collection <code class="language-plaintext highlighter-rouge">news</code></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/news/news_page/&lt;n&gt;</code></td>
      <td>page <em>n</em> of the collection</td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/news/news_items/&lt;field&gt;/&lt;value&gt;</code></td>
      <td>the items where <em>field</em> is <em>value</em></td>
    </tr>
    <tr>
      <td><code class="language-plaintext highlighter-rouge">/news.rss</code></td>
      <td>the page <code class="language-plaintext highlighter-rouge">news</code> in the format <code class="language-plaintext highlighter-rouge">rss</code></td>
    </tr>
  </tbody>
</table>

<p>What the grammar can’t express (joins, references between records) belongs
in objects, not in templates.</p>

<h3 id="22-the-template">2.2 The template</h3>

<p>A template is the page as it will look: real markup with real content in it.
It must render in a browser as a static file, so a designer can open it as a
mock-up and an agent can check it by looking.</p>

<p>The dynamic parts are marked with a <strong>small, closed vocabulary</strong> of
annotations that point at objects. In Raster these are HTML comments:</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">print.object.method</code>: replace this block with a value.</li>
  <li><code class="language-plaintext highlighter-rouge">render.object.method</code>: repeat this block for each row.</li>
  <li><code class="language-plaintext highlighter-rouge">remove</code>: mock-up content, always removed.</li>
  <li><code class="language-plaintext highlighter-rouge">res</code> and <code class="language-plaintext highlighter-rouge">dry</code>: define a fragment and reuse it.</li>
</ul>

<p>Templates contain <strong>no general-purpose logic</strong>. The vocabulary is fixed.
Anything more (conditions, formatting, access rules) is an object’s job. A
closed vocabulary is what makes templates checkable: a tool can list every
object a template needs, find every mistake, and derive what data it will
store.</p>

<h3 id="23-objects">2.3 Objects</h3>

<p>Objects wrap the logic that fetches and changes data: databases, files,
sessions, mail, other services. They return plain data: strings for single
values, lists of rows for repeated blocks, and <code class="language-plaintext highlighter-rouge">false</code> for “keep what the
template has”.</p>

<h2 id="3-rules-the-pattern-depends-on">3. Rules the pattern depends on</h2>

<p><strong>3.1 The template owns the text.</strong> Every word a person reads is in a
template: headings, error messages, confirmations, translations, emails.
Objects decide whether a block shows and which data fills it, but never write
prose. An error message is a block in the template that an object reveals. An
email is a template rendered for one recipient.</p>

<p><strong>3.2 The markup is the schema.</strong> A template that points at stored content
declares that content: its name, its place and its default (the mock-up text).
An implementation can derive the storage from the templates. It must be able
to report the difference between what the templates declare and what is
stored.</p>

<p><strong>3.3 Inner blocks are evaluated first.</strong> When blocks nest, the inner ones run
before the block around them. A form’s validation messages therefore run
before the object that handles the form, and that object already knows the
result.</p>

<p><strong>3.4 Writes go through the template.</strong> A form posts to the page it is on. The
object that renders the form also handles the post. When the post is invalid,
the object shows the form again with the submitted values. When it succeeds,
the object redirects back to the same page with a marker naming the outcome,
and the template shows the matching message. There is no separate write
endpoint or controller.</p>

<p><strong>3.5 Constraints live in the markup.</strong> A form’s inputs already describe what
they accept (<code class="language-plaintext highlighter-rouge">required</code>, <code class="language-plaintext highlighter-rouge">type</code>, lengths, ranges, patterns). The server
enforces the same constraints, so they are written once.</p>

<p><strong>3.6 Formats are templates.</strong> A feed, a sitemap or a JSON document is a
template in that format, pointing at the same objects. Values are escaped for
the format. An email is a template too.</p>

<p><strong>3.7 Fail softly for visitors, loudly for authors.</strong> At runtime, a missing
object or empty data leaves the template’s own content in place: a visitor
sees a stale sentence rather than an error. While building, the same mistakes
are errors: unknown objects, unclosed blocks, forms nothing handles, messages
nothing reveals. An implementation should provide both behaviours.</p>

<p><strong>3.8 Extension through events.</strong> The envelope announces the steps of a
request (route found, before render, before output). Objects can subscribe to
them for cross-cutting work such as access control, caching and
instrumentation, without changing templates.</p>

<h2 id="4-caching">4. Caching</h2>

<p>Because templates declare their data and content changes go through known
objects, whole pages can be cached for anonymous readers. The cache is thrown
away whenever content changes. Requests with a session or a query are not
cached.</p>

<h2 id="5-standard-objects">5. Standard objects</h2>

<p>The pattern doesn’t require them. An implementation should still offer these,
following the rules above: every one of them is written in templates, with
text owned by the template.</p>

<table>
  <thead>
    <tr>
      <th>Object</th>
      <th>Provides</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td><strong>cms</strong></td>
      <td>Page fields and collections derived from the markup (3.2). It also provides site-wide fields, readable slugs, drafts, scheduled items, revision history, and filters or links by shared field values.</td>
    </tr>
    <tr>
      <td><strong>validation</strong></td>
      <td>Form constraints from the markup (3.5), message blocks, and outcome alerts.</td>
    </tr>
    <tr>
      <td><strong>authentication</strong></td>
      <td>Log in, sign up, reset by email, account, log out, roles, and protected pages.</td>
    </tr>
    <tr>
      <td><strong>mail</strong></td>
      <td>Emails as templates, over configurable transports.</td>
    </tr>
    <tr>
      <td><strong>newsletter</strong></td>
      <td>Double opt-in sign-up, confirm, unsubscribe, and sending any page as an issue.</td>
    </tr>
    <tr>
      <td><strong>feed</strong></td>
      <td>Items and pages for feeds and sitemaps (3.6).</td>
    </tr>
    <tr>
      <td><strong>pagination</strong></td>
      <td>Page links for collections and objects.</td>
    </tr>
    <tr>
      <td><strong>i18n</strong></td>
      <td>Translations, where the template’s text is the default language.</td>
    </tr>
  </tbody>
</table>

<h2 id="6-agents">6. Agents</h2>

<p>RTO suits artefacts made by software agents because the template is both the
design and the contract:</p>

<ul>
  <li>An agent writes real HTML with real content, which it is good at, and
annotates it.</li>
  <li>A checker reports every mistake with its position (3.7).</li>
  <li>The content model is derived, not designed (3.2), so a site can describe
itself. Its pages, fields and collections can be exposed as tools, and an
agent can edit content without editing code.</li>
</ul>

<h2 id="7-where-rto-stops">7. Where RTO stops</h2>

<p>RTO is the wrong pattern when:</p>

<ul>
  <li>the data model would be drawn before the pages;</li>
  <li>content has references between records and is reused away from its pages;</li>
  <li>the artefact is mostly interaction: multi-step flows, dashboards, state that
lives across requests.</li>
</ul>

<p>In those cases, write objects that do the work, or use a framework built for
applications.</p>]]></content><author><name>Andrei Draganescu</name></author><category term="rto" /><category term="specs" /><summary type="html"><![CDATA[A design pattern for web artefacts made by people and agents. A request picks a template, and the template pulls its data from objects.]]></summary></entry><entry><title type="html">The Raster framework</title><link href="https://draganescu.github.io/raster/specs/2014/06/29/raster.html" rel="alternate" type="text/html" title="The Raster framework" /><published>2026-09-26T11:00:00+00:00</published><updated>2026-09-26T11:00:00+00:00</updated><id>https://draganescu.github.io/raster/specs/2014/06/29/raster</id><content type="html" xml:base="https://draganescu.github.io/raster/specs/2014/06/29/raster.html"><![CDATA[<p>Raster is the framework that implements the <a href="/rto/specs/2014/06/29/rto.html">RTO design pattern</a>.
It is written in PHP and lives at <a href="https://github.com/draganescu/rasterPHP">github.com/draganescu/rasterPHP</a>.
It is made for web artefacts: sites, landing pages, blogs, newsletters and
small apps, made by people or by agents.</p>

<h2 id="how-it-works">How it works</h2>

<p>A request picks a view, which is a plain HTML file. The parts of the page that
change are HTML comments that name a model and a method:</p>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;h1&gt;</span><span class="c">&lt;!-- print.cms.headline --&gt;</span>Good coffee, slow mornings.<span class="c">&lt;!-- /print.cms.headline --&gt;</span><span class="nt">&lt;/h1&gt;</span>

<span class="c">&lt;!-- render.cms.menu('order=name') --&gt;</span>
<span class="nt">&lt;article&gt;</span>
  <span class="nt">&lt;h2&gt;</span><span class="c">&lt;!-- print.name --&gt;</span>Flat white<span class="c">&lt;!-- /print.name --&gt;</span><span class="nt">&lt;/h2&gt;</span>
  <span class="nt">&lt;p&gt;</span><span class="c">&lt;!-- print.price --&gt;</span>14<span class="c">&lt;!-- /print.price --&gt;</span> lei<span class="nt">&lt;/p&gt;</span>
<span class="nt">&lt;/article&gt;</span>
<span class="c">&lt;!-- /render.cms.menu('order=name') --&gt;</span>
</code></pre></div></div>

<p>The view still opens in a browser as a mock-up. When Raster serves it, each
annotation pulls its data from the model it names. The template owns all the
text on the page, including error messages and emails; models only decide what
shows.</p>

<h2 id="what-the-markup-gives-you">What the markup gives you</h2>

<ul>
  <li><strong>A CMS with no schema files.</strong> <code class="language-plaintext highlighter-rouge">print.cms.headline</code> declares an editable
field on that page, and <code class="language-plaintext highlighter-rouge">render.cms.menu</code> declares a collection whose fields
are the keys inside it. Items get slugs, drafts, scheduled publishing,
ordering, filters, pagination and their own URLs. Editors change content in
the page, and every save is a new revision.</li>
  <li><strong>Forms with their rules in the HTML.</strong> <code class="language-plaintext highlighter-rouge">required</code>, <code class="language-plaintext highlighter-rouge">type</code>, <code class="language-plaintext highlighter-rouge">minlength</code>,
<code class="language-plaintext highlighter-rouge">pattern</code> and the rest are enforced on the server too, and every message is
written in the template. Posts from other sites and bots are refused.</li>
  <li><strong>Accounts and a newsletter</strong>, with every screen written in your templates:
sign-up, login, password reset, roles, protected pages, double opt-in and
sending any page as an issue.</li>
  <li><strong>Feeds and data views.</strong> <code class="language-plaintext highlighter-rouge">news.rss</code>, <code class="language-plaintext highlighter-rouge">sitemap.xml</code> or <code class="language-plaintext highlighter-rouge">feed.json</code> are views
too, escaped for their format.</li>
  <li><strong>Events between models.</strong> When a booking should subscribe the guest to the
newsletter, one model sends an event and the other listens. The template
never has to know.</li>
  <li><strong>A page cache</strong>, translations, mail over SMTP, and a JSON API for every
model.</li>
</ul>

<h2 id="made-for-agents-too">Made for agents too</h2>

<p>Raster is built so an AI agent can make and run a site as easily as a person:</p>

<ul>
  <li><a href="https://github.com/draganescu/rasterPHP/blob/master/AGENTS.md">AGENTS.md</a> is
the complete specification, in one file.</li>
  <li><code class="language-plaintext highlighter-rouge">raster lint</code> checks every view, form, alert, event binding and query, and
a broken template in development shows a list of what is wrong instead of
half a page.</li>
  <li><code class="language-plaintext highlighter-rouge">raster schema</code> shows the content model the templates define, compared with
the database.</li>
  <li>An MCP server lets an agent read and edit the content, limited to the fields
the templates declare.</li>
  <li><code class="language-plaintext highlighter-rouge">raster doctor</code> says whether a site is healthy, up to date and ready for
production.</li>
</ul>

<h2 id="compared-with-the-big-frameworks">Compared with the big frameworks</h2>

<p>Raster is small: one controller, one view file per URL, no build step and
SQLite by default. It does less than Laravel or Symfony on purpose. What it
generates is easy to follow, pages are fast and cached for visitors, and every
page is editable as soon as it exists.</p>

<p><a href="/raster/specs/php/2014/06/29/raster-php.html">Getting started</a> shows how to
make a site with it.</p>]]></content><author><name>Andrei Draganescu</name></author><category term="raster" /><category term="specs" /><summary type="html"><![CDATA[Raster is the PHP framework for RTO. You write HTML, mark the parts that change with comments, and the content model, the CMS and the editing tools follow from the markup.]]></summary></entry><entry><title type="html">Getting started with Raster</title><link href="https://draganescu.github.io/raster/specs/php/2014/06/29/raster-php.html" rel="alternate" type="text/html" title="Getting started with Raster" /><published>2026-09-26T10:00:00+00:00</published><updated>2026-09-26T10:00:00+00:00</updated><id>https://draganescu.github.io/raster/specs/php/2014/06/29/raster-php</id><content type="html" xml:base="https://draganescu.github.io/raster/specs/php/2014/06/29/raster-php.html"><![CDATA[<p>Raster needs PHP 8.1 or newer, with SQLite. There is nothing else to install.</p>

<h2 id="a-new-site">A new site</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>git clone https://github.com/draganescu/rasterPHP
php rasterPHP/bin/raster new mysite
<span class="nb">cd </span>mysite
php bin/raster serve          <span class="c"># http://localhost:8000</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">raster new</code> copies the framework and a starter app into <code class="language-plaintext highlighter-rouge">mysite/</code>. Your site
lives in <code class="language-plaintext highlighter-rouge">application/</code>:</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>application/
  config/the_app.php          settings
  models/&lt;name&gt;/&lt;name&gt;.php    your models
  views/&lt;theme&gt;/              views (.html, .rss, .xml, .json), css, images
  data/                       the SQLite database, cache and logged mail
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">/about</code> renders <code class="language-plaintext highlighter-rouge">views/&lt;theme&gt;/about.html</code>, and <code class="language-plaintext highlighter-rouge">/docs/setup</code> renders
<code class="language-plaintext highlighter-rouge">docs/setup.html</code>. Start from static HTML with real content, then mark the
parts that change.</p>

<h2 id="editing-it">Editing it</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>php bin/raster user you@example.com <span class="nt">--role</span><span class="o">=</span>editor   <span class="c"># log in at /login to edit in the page</span>
php bin/raster lint                                 <span class="c"># after every template change</span>
php bin/raster schema                               <span class="c"># what the CMS will store</span>
php bin/raster render /about                        <span class="c"># a page's HTML, without a server</span>
</code></pre></div></div>

<p>An agent can do all of this too. <code class="language-plaintext highlighter-rouge">AGENTS.md</code> in the site is the full
specification, <code class="language-plaintext highlighter-rouge">CLAUDE.md</code> points agents at it, and <code class="language-plaintext highlighter-rouge">.mcp.json</code> sets up the
MCP server that lets them edit the content.</p>

<p>The <a href="https://github.com/draganescu/rasterPHP/tree/master/demo">demo café</a> is a
complete site that uses every feature, with a test for each one. When you
wonder how something is written, look there.</p>

<h2 id="putting-it-online">Putting it online</h2>

<p>Any PHP host with Apache (the <code class="language-plaintext highlighter-rouge">.htaccess</code> is included) or a server that sends
every request to <code class="language-plaintext highlighter-rouge">index.php</code> works. On the server:</p>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nb">export </span><span class="nv">RASTER_ENV</span><span class="o">=</span>production
<span class="nb">export </span><span class="nv">RASTER_URL</span><span class="o">=</span>https://example.com/
php bin/raster schema <span class="nt">--apply</span>     <span class="c"># create the tables the templates need</span>
php bin/raster doctor             <span class="c"># anything missing before going live?</span>
</code></pre></div></div>

<p>Production freezes the database schema, caches pages for visitors and sends
mail with PHP’s <code class="language-plaintext highlighter-rouge">mail()</code>, or over SMTP when you set <code class="language-plaintext highlighter-rouge">RASTER_MAIL</code>.</p>

<h2 id="keeping-it-up-to-date">Keeping it up to date</h2>

<div class="language-sh highlighter-rouge"><div class="highlight"><pre class="highlight"><code>php bin/raster update             <span class="c"># the latest release</span>
php bin/raster doctor
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">update</code> replaces only the framework files (<code class="language-plaintext highlighter-rouge">system/</code>, <code class="language-plaintext highlighter-rouge">bin/raster</code>,
<code class="language-plaintext highlighter-rouge">index.php</code>, <code class="language-plaintext highlighter-rouge">.htaccess</code> and <code class="language-plaintext highlighter-rouge">AGENTS.md</code>), refuses if you edited them, keeps
the old ones in a backup, and then runs the upgrade steps your app needs.
Changes you want in the framework go in your app instead, as overrides; the
<a href="https://github.com/draganescu/rasterPHP/blob/master/CHANGELOG.md">changelog</a>
lists what each release changes.</p>

<h2 id="earlier-versions">Earlier versions</h2>

<p>Raster started as Spartan PHP, with ports to CodeIgniter and WordPress
and a Node.js version planned. Those are no longer maintained. Raster 2, from
2026, is the PHP framework described here.</p>]]></content><author><name>Andrei Draganescu</name></author><category term="raster" /><category term="specs" /><category term="php" /><summary type="html"><![CDATA[Make a Raster site, edit it, put it online and keep it up to date. PHP 8.1 and nothing else to install.]]></summary></entry><entry><title type="html">Introducing Raster</title><link href="https://draganescu.github.io/raster/specs/php/2014/06/29/introducing-raster-php.html" rel="alternate" type="text/html" title="Introducing Raster" /><published>2026-09-26T09:00:00+00:00</published><updated>2026-09-26T09:00:00+00:00</updated><id>https://draganescu.github.io/raster/specs/php/2014/06/29/introducing-raster-php</id><content type="html" xml:base="https://draganescu.github.io/raster/specs/php/2014/06/29/introducing-raster-php.html"><![CDATA[<p><em>First published in 2014 as “Introducing RasterPHP”. Rewritten in 2026 for
Raster 2.</em></p>

<p>Raster is a PHP framework for websites, not for web apps. Its idea is small:
the view is plain HTML, and the parts that change are HTML comments. The
comments don’t break the layout or remove the dummy content, so the same file
is both the mock-up and the working page. The pattern behind it is
<a href="/rto/specs/2014/06/29/rto.html">RTO</a>: the request picks a template, and the
template pulls its data from objects.</p>

<h2 id="a-view-and-a-model">A view and a model</h2>

<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;html&gt;</span>
  <span class="nt">&lt;body&gt;</span>
    <span class="nt">&lt;h1&gt;</span>A list of things<span class="nt">&lt;/h1&gt;</span>
    <span class="c">&lt;!-- render.tasks.all --&gt;</span>
    <span class="nt">&lt;p&gt;</span><span class="c">&lt;!-- print.task_name --&gt;</span>Lorem ipsum<span class="c">&lt;!-- /print.task_name --&gt;</span><span class="nt">&lt;/p&gt;</span>
    <span class="c">&lt;!-- /render.tasks.all --&gt;</span>
  <span class="nt">&lt;/body&gt;</span>
<span class="nt">&lt;/html&gt;</span>
</code></pre></div></div>

<p><code class="language-plaintext highlighter-rouge">render.tasks.all</code> loads a plain PHP class named <code class="language-plaintext highlighter-rouge">tasks</code> and repeats the block
once for each row its <code class="language-plaintext highlighter-rouge">all</code> method returns:</p>

<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cp">&lt;?php</span> <span class="c1">// application/models/tasks/tasks.php</span>
<span class="kd">class</span> <span class="nc">tasks</span> <span class="p">{</span>
    <span class="k">function</span> <span class="n">all</span><span class="p">()</span> <span class="p">{</span>
        <span class="k">return</span> <span class="k">array</span><span class="p">(</span>
            <span class="k">array</span><span class="p">(</span><span class="s1">'task_name'</span> <span class="o">=&gt;</span> <span class="s1">'Walk the dog'</span><span class="p">),</span>
            <span class="k">array</span><span class="p">(</span><span class="s1">'task_name'</span> <span class="o">=&gt;</span> <span class="s1">'Fill in the groceries list'</span><span class="p">),</span>
            <span class="k">array</span><span class="p">(</span><span class="s1">'task_name'</span> <span class="o">=&gt;</span> <span class="s1">'Convince people to use Raster'</span><span class="p">),</span>
        <span class="p">);</span>
    <span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>

<p>The page then lists the three tasks, and “Lorem ipsum” is gone. Models only
return data; the view decides how it looks, and every word on the page stays
in the template.</p>

<h2 id="the-rest-all-optional">The rest, all optional</h2>

<ul>
  <li><strong>A CMS from the markup.</strong> Write <code class="language-plaintext highlighter-rouge">&lt;!-- print.cms.headline --&gt;Hello&lt;!-- /print.cms.headline --&gt;</code>
and the headline is editable on that page. Use <code class="language-plaintext highlighter-rouge">render.cms.news</code> instead of
your own model and you get a collection with its own URLs, drafts,
scheduled posts and pagination. There are no schema files: the templates
are the schema.</li>
  <li><strong>Forms.</strong> The rules are the HTML attributes you already write (<code class="language-plaintext highlighter-rouge">required</code>,
<code class="language-plaintext highlighter-rouge">type="email"</code>, <code class="language-plaintext highlighter-rouge">maxlength</code>), checked on the server too. The error messages
are blocks in the template that show when a rule fails.</li>
  <li><strong>Accounts, a newsletter, feeds, translations, a page cache and mail</strong>, as
bundled models that follow the same rule: your templates hold every screen
and every email.</li>
  <li><strong>Events.</strong> Models tell each other what happened
(<code class="language-plaintext highlighter-rouge">event::dispatch('reservation.booked', …)</code>) and listen with a
<code class="language-plaintext highlighter-rouge">listens()</code> method, so the view never wires models together.</li>
  <li><strong>SQL in files.</strong> <code class="language-plaintext highlighter-rouge">models/tasks/sql/due_today.sql</code> runs as
<code class="language-plaintext highlighter-rouge">database::instance()-&gt;due_today()</code>.</li>
  <li><strong>An API.</strong> Every public method of your models is also JSON at
<code class="language-plaintext highlighter-rouge">/api/&lt;model&gt;/&lt;method&gt;</code>.</li>
</ul>

<h2 id="why-now">Why now</h2>

<p>The comments-in-HTML idea turns out to suit agents as well as designers. An
agent can write a view as ordinary HTML, check it with <code class="language-plaintext highlighter-rouge">raster lint</code>, see the
content model it created with <code class="language-plaintext highlighter-rouge">raster schema</code>, and edit content through the
MCP server. <code class="language-plaintext highlighter-rouge">AGENTS.md</code> is the whole specification in one file, and the
<a href="https://github.com/draganescu/rasterPHP/tree/master/demo">demo café</a> uses
every feature, with a test for each.</p>

<p>Raster is not trying to replace Laravel. It is for the many projects that
would be crushed under a full framework: a site, a blog, a newsletter, a
landing page, a small app.</p>

<p><a href="/raster/specs/php/2014/06/29/raster-php.html">Get started</a>, or read the code
at <a href="https://github.com/draganescu/rasterPHP">github.com/draganescu/rasterPHP</a>.</p>]]></content><author><name>Andrei Draganescu</name></author><category term="raster" /><category term="specs" /><category term="php" /><summary type="html"><![CDATA[A framework even designers can understand, and now agents too. A short tour of what a Raster site is made of.]]></summary></entry></feed>