Building a Blog
Complete tutorial - build a blog from scratch with DALT.PHP
Let's build a complete blog application from scratch using DALT.PHP. You'll learn routing, database queries, CRUD operations, and security best practices.
What You'll Build
A fully functional blog with:
- List all posts
- View single post
- Create new post
- Edit existing post
- Delete post
- Basic validation
Prerequisites
- DALT.PHP installed and running
- Completed the Quick Start guide
- Basic understanding of PHP and SQL
Time Required: 30-45 minutes
Step 1: Create the Database Table
First, create a migration for the posts table.
Generate Migration
php artisan make:migration create_posts_tableThis creates a file like database/migrations/20240315120000_create_posts_table.sql.
Write the SQL
Open the migration file and replace the content with:
-- Migration: create_posts_table
-- Created: 2024-03-15
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title VARCHAR(255) NOT NULL,
body TEXT NOT NULL,
published BOOLEAN DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
);
-- Index for faster queries
CREATE INDEX IF NOT EXISTS idx_posts_published ON posts(published);Run Migration
php artisan migrateYou should see:
Running migration: 20240315120000_create_posts_table.sql
✓ Success
Ran 1 migrations.Step 2: Create Controllers
Now create controllers for each blog operation.
Create Controllers Directory
mkdir -p app/Http/controllers/postsIndex Controller (List All Posts)
Create app/Http/controllers/posts/index.php:
<?php
$db = App::resolve(Core\Database::class);
$posts = $db->query('SELECT * FROM posts ORDER BY created_at DESC')->get();
view('posts/index.view.php', [
'posts' => $posts,
'title' => 'All Posts'
]);Show Controller (View Single Post)
Create app/Http/controllers/posts/show.php:
<?php
$db = App::resolve(Core\Database::class);
$post = $db->query('SELECT * FROM posts WHERE id = ?', [$_GET['id']])->findOrFail();
view('posts/show.view.php', [
'post' => $post,
'title' => $post['title']
]);Create Controller (Show Form)
Create app/Http/controllers/posts/create.php:
<?php
view('posts/create.view.php', [
'title' => 'Create Post'
]);Store Controller (Save New Post)
Create app/Http/controllers/posts/store.php:
<?php
$db = App::resolve(Core\Database::class);
// Validate input
$errors = [];
if (empty($_POST['title'])) {
$errors['title'] = 'Title is required';
}
if (empty($_POST['body'])) {
$errors['body'] = 'Body is required';
}
if (!empty($errors)) {
view('posts/create.view.php', [
'errors' => $errors,
'old' => $_POST
]);
exit;
}
// Insert post
$db->query('INSERT INTO posts (title, body, published) VALUES (?, ?, ?)', [
$_POST['title'],
$_POST['body'],
isset($_POST['published']) ? 1 : 0
]);
// Redirect with success message
Core\Session::flash('success', 'Post created successfully!');
header('Location: /posts');
exit;Edit Controller (Show Edit Form)
Create app/Http/controllers/posts/edit.php:
<?php
$db = App::resolve(Core\Database::class);
$post = $db->query('SELECT * FROM posts WHERE id = ?', [$_GET['id']])->findOrFail();
view('posts/edit.view.php', [
'post' => $post,
'title' => 'Edit Post'
]);Update Controller (Save Changes)
Create app/Http/controllers/posts/update.php:
<?php
$db = App::resolve(Core\Database::class);
// Validate input
$errors = [];
if (empty($_POST['title'])) {
$errors['title'] = 'Title is required';
}
if (empty($_POST['body'])) {
$errors['body'] = 'Body is required';
}
if (!empty($errors)) {
$post = $db->query('SELECT * FROM posts WHERE id = ?', [$_GET['id']])->findOrFail();
view('posts/edit.view.php', [
'post' => $post,
'errors' => $errors
]);
exit;
}
// Update post
$db->query('UPDATE posts SET title = ?, body = ?, published = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?', [
$_POST['title'],
$_POST['body'],
isset($_POST['published']) ? 1 : 0,
$_GET['id']
]);
Core\Session::flash('success', 'Post updated successfully!');
header('Location: /posts');
exit;Destroy Controller (Delete Post)
Create app/Http/controllers/posts/destroy.php:
<?php
$db = App::resolve(Core\Database::class);
$db->query('DELETE FROM posts WHERE id = ?', [$_POST['id']]);
Core\Session::flash('success', 'Post deleted successfully!');
header('Location: /posts');
exit;Step 3: Add Routes
Register all blog routes in routes/routes.php:
<?php
global $router;
// Existing routes
$router->get('/', 'welcome.php');
// Blog routes
$router->get('/posts', 'posts/index.php');
$router->get('/posts/create', 'posts/create.php');
$router->post('/posts', 'posts/store.php');
$router->get('/posts/{id}', 'posts/show.php');
$router->get('/posts/{id}/edit', 'posts/edit.php');
$router->patch('/posts/{id}', 'posts/update.php');
$router->delete('/posts/{id}', 'posts/destroy.php');Route Order Matters! Put specific routes (/posts/create) before generic routes (/posts/{id}).
Step 4: Create Views
Create view templates for the blog interface.
Create Views Directory
mkdir -p resources/views/postsIndex View (List Posts)
Create resources/views/posts/index.view.php:
<!DOCTYPE html>
<html>
<head>
<title><?= $title ?></title>
</head>
<body>
<h1>Blog Posts</h1>
<?php if (Core\Session::has('success')): ?>
<p style="color: green;"><?= Core\Session::get('success') ?></p>
<?php endif; ?>
<a href="/posts/create">Create New Post</a>
<?php if (empty($posts)): ?>
<p>No posts yet. <a href="/posts/create">Create one!</a></p>
<?php else: ?>
<?php foreach ($posts as $post): ?>
<article>
<h2>
<a href="/posts/<?= $post['id'] ?>">
<?= htmlspecialchars($post['title']) ?>
</a>
</h2>
<p><?= htmlspecialchars(substr($post['body'], 0, 200)) ?>...</p>
<small>
<?= $post['published'] ? 'Published' : 'Draft' ?> |
<?= $post['created_at'] ?>
</small>
</article>
<hr>
<?php endforeach; ?>
<?php endif; ?>
</body>
</html>Show View (Single Post)
Create resources/views/posts/show.view.php:
<!DOCTYPE html>
<html>
<head>
<title><?= htmlspecialchars($post['title']) ?></title>
</head>
<body>
<a href="/posts">← Back to all posts</a>
<article>
<h1><?= htmlspecialchars($post['title']) ?></h1>
<small>
<?= $post['published'] ? 'Published' : 'Draft' ?> |
<?= $post['created_at'] ?>
</small>
<div>
<?= nl2br(htmlspecialchars($post['body'])) ?>
</div>
<hr>
<a href="/posts/<?= $post['id'] ?>/edit">Edit</a>
<form method="POST" action="/posts/<?= $post['id'] ?>" style="display: inline;">
<input type="hidden" name="_method" value="DELETE">
<button type="submit" onclick="return confirm('Are you sure?')">Delete</button>
</form>
</article>
</body>
</html>Create View (New Post Form)
Create resources/views/posts/create.view.php:
<!DOCTYPE html>
<html>
<head>
<title>Create Post</title>
</head>
<body>
<h1>Create New Post</h1>
<a href="/posts">← Back to all posts</a>
<form method="POST" action="/posts">
<div>
<label>Title:</label>
<input type="text" name="title" value="<?= htmlspecialchars($old['title'] ?? '') ?>" required>
<?php if (isset($errors['title'])): ?>
<p style="color: red;"><?= $errors['title'] ?></p>
<?php endif; ?>
</div>
<div>
<label>Body:</label>
<textarea name="body" rows="10" required><?= htmlspecialchars($old['body'] ?? '') ?></textarea>
<?php if (isset($errors['body'])): ?>
<p style="color: red;"><?= $errors['body'] ?></p>
<?php endif; ?>
</div>
<div>
<label>
<input type="checkbox" name="published" <?= isset($old['published']) ? 'checked' : '' ?>>
Published
</label>
</div>
<button type="submit">Create Post</button>
</form>
</body>
</html>Edit View (Edit Post Form)
Create resources/views/posts/edit.view.php:
<!DOCTYPE html>
<html>
<head>
<title>Edit Post</title>
</head>
<body>
<h1>Edit Post</h1>
<a href="/posts">← Back to all posts</a>
<form method="POST" action="/posts/<?= $post['id'] ?>">
<input type="hidden" name="_method" value="PATCH">
<div>
<label>Title:</label>
<input type="text" name="title" value="<?= htmlspecialchars($post['title']) ?>" required>
<?php if (isset($errors['title'])): ?>
<p style="color: red;"><?= $errors['title'] ?></p>
<?php endif; ?>
</div>
<div>
<label>Body:</label>
<textarea name="body" rows="10" required><?= htmlspecialchars($post['body']) ?></textarea>
<?php if (isset($errors['body'])): ?>
<p style="color: red;"><?= $errors['body'] ?></p>
<?php endif; ?>
</div>
<div>
<label>
<input type="checkbox" name="published" <?= $post['published'] ? 'checked' : '' ?>>
Published
</label>
</div>
<button type="submit">Update Post</button>
</form>
</body>
</html>Step 5: Test Your Blog
Make sure your development server is running, then test all the functionality.
Start the Server
If not already running:
php artisan serveYou should see:
Starting development server: http://127.0.0.1:8000Visit the Blog
Open http://localhost:8000/posts
You should see "No posts yet" with a link to create one.
Create a Post
- Click "Create New Post"
- Fill in title and body
- Check "Published" if you want
- Click "Create Post"
You should be redirected to the posts list with a success message.
View a Post
Click on a post title to view the full post.
Edit a Post
- Click "Edit" on a post
- Modify the content
- Click "Update Post"
Security Notes
Your blog uses prepared statements to prevent SQL injection and htmlspecialchars() to prevent XSS attacks. These are built into the examples above - no extra configuration needed.
For production, add CSRF protection to forms:
// In routes
$router->post('/posts', 'posts/store.php')->only('csrf');
// In form
<?= csrf_field() ?>What's Next
You've built a complete CRUD application with validation, flash messages, and security. From here you can add authentication, pagination, or build an API.
Done! You now understand routing, database queries, validation, and security basics.