Forum Views
Views live under src/Views. The demo uses server-rendered templates so the app works before JavaScript loads.
Goal
Create unsuffixed forum templates that render prepared data and show controls based on permissions. These deliberately use explicit view responses rather than direct GET pages: the action prepares auth, permissions, and database results first. Keep method files and _middleware.php in the same private Views tree.
Create these plain PHP templates by hand. make:view creates a direct GET endpoint in 0.4.1, so it is not the generator for these unsuffixed files.
Files Created
src/Views/forum-demo/_index.php
src/Views/forum-demo/login.php
src/Views/forum-demo/topics.php
src/Views/forum-demo/topic.php
src/Views/forum-demo/admin.php
src/Views/forum-demo/admin-users.php
Step: Render The Forum Landing Page
The landing page receives $topics, $currentUser, and $permissions. Keep it focused on the newest discussions and the current account state. Do not show non-clickable category blocks just because the database has categories.
You are here in the flow:
Handler
ForumActions::index() prepares the landing page data.
Render call
$this->render('forum-demo/_index', ['topics' => ...]) chooses the view name.
View file
src/Views/forum-demo/_index.php receives $topics, $currentUser, and $permissions.
<h1>Forum with permissions</h1>
<?php foreach ($topics as $topic): ?>
<a href="/forum-demo/topics/<?= (int) $topic['id'] ?>">
<?= $topic['title'] ?>
</a>
<?php endforeach; ?>
The view does not decide where topics come from. It only renders what the handler prepared.
Step: Render The Topic List
<?php foreach ($topics as $topic): ?>
<a href="/forum-demo/topics/<?= (int) $topic['id'] ?>">
<?= $topic['title'] ?>
</a>
<?php endforeach; ?>
The framework renderer recursively HTML-escapes string values in the passed arrays. Output these prepared values directly; escaping them again would display entities such as & to readers. Objects and JavaScript/CSS contexts still need explicit handling. The template must always be rendered through the framework, never directly included from a public URL.
Use a forum-style row layout for the topic index: title and preview first, then updated time, reply count, and status. This is easier to scan than large repeated cards.
Categories still matter as metadata and form input. Show the category name beside each topic, and keep the category select in the create-topic form. Avoid a separate category sidebar unless categories are clickable filters.
Step: Render The Original Post
The topic detail page should show the original post before any replies.
<article>
<h1><?= $topic['title'] ?></h1>
<p><?= $topic['body'] ?></p>
</article>
Replies should be displayed under the original post as a timeline. Do not make replies look like the main topic content.
Step: Add Permission-Based UI
The topic creation form should only appear when the user can create topics.
<?php if ($permissions['topic.create'] ?? false): ?>
<form method="POST" action="/forum-demo/topics">
<?= \CorianderCore\Core\Security\Csrf::input() ?>
<select name="category_id">
<?php foreach ($categories as $category): ?>
<option value="<?= (int) $category['id'] ?>"><?= $category['name'] ?></option>
<?php endforeach; ?>
</select>
<input name="title" placeholder="Topic title">
<textarea name="body" placeholder="What do you want to ask?"></textarea>
<button>Create topic</button>
</form>
<?php else: ?>
<a href="/forum-demo/login">Log in to create a topic</a>
<?php endif; ?>
Hiding a form is UX, not security. The handler and write service still enforce permissions.
The same rule applies to admin buttons. If the view hides "Lock topic" from members, that is only to reduce noise. The admin route group and write service must still reject the action server-side.
Step: Add Admin Moderation UI
Admin users should see moderation forms near the content they can moderate.
<?php if ($permissions['topic.lock'] ?? false): ?>
<form method="POST" action="/forum-demo/admin/topics">
<?= \CorianderCore\Core\Security\Csrf::input() ?>
<input type="hidden" name="return_to" value="topic">
<input type="hidden" name="topic_id" value="<?= (int) $topic['id'] ?>">
<button name="action" value="lock">Lock topic</button>
</form>
<?php endif; ?>
Use the same pattern for reply moderation with /forum-demo/admin/replies. Include both return_to=topic and topic_id when the form appears on a topic page.
Moderation POST routes are action endpoints, not pages users should land on. The hidden return_to field tells the handler which GET page should receive the flash message after the action:
return_to=topickeeps the admin on the topic detail page.return_to=adminkeeps the admin on the moderation queue.
This makes the UI feel stable. Clicking "Hide reply" or "Lock topic" should not move the user away from the content they are reviewing, and the redirect prevents form resubmission warnings.
Step: Show Write Feedback
When a write succeeds, show the result message returned by the write service. In public demo read-only mode, that message must be explicit that nothing was saved.
<?php if (($flash['ok'] ?? false) === true): ?>
<p><?= $flash['message'] ?></p>
<?php endif; ?>
Use the same flash shape for topic, reply, and admin actions.
Keep flash rendering close to the page heading so users notice the result without losing their place in the forum content.
Step: Keep JavaScript Optional
Create shared _header.php/_footer.php under src/Views, as described in Static View Guide. They receive the same title and shared data as the forum template. Keep a header/footer pair that produces valid HTML; nearest ancestor layouts do not stack.
TypeScript can improve interactions, but the core demo should work as server-rendered HTML. Add TypeScript for small enhancements only.
src/Views/forum-demo/_script.ts
public/assets/js/pages/forum-demo/_script.js
The framework includes this directory module automatically for full forum view responses. Do not add the same script manually in the footer. Build it with php coriander nodejs run build-prod; see Page Scripts for shared imports and fragments.
Checkpoint
Open /forum-demo/topics as a guest, member, and admin. The content should stay readable, while forms and admin links adapt to the user.
If a variable is undefined in a view, do not create it inside the template. Go back to the handler render call and pass the missing data explicitly.
Common Mistakes
- Putting permission rules directly in the template.
- Rendering unsanitized strings.
- Making JavaScript required for basic navigation or form submission.
Next
Continue with Demo Authentication.