Markdown Blog Example
The Markdown blog example is a little bit more complex than the other examples,
but it demonstrates how to create a simple blog using Markdown files as the content source.
Each blog post is stored as a separate Markdown file in the articles/ directory.
The example includes an action handler to read the Markdown files, convert them to HTML,
and render them using a template.
There is already a plugin for that: Blog Plugin
Prerequisites
Installing Parsedown and Yaml-Front-Matter
For this example an external libraries are used called Parsedown and YAML Front Matter are used and has to be installed using composer.
composer require erusev/parsedown
composer require spatie/yaml-front-matter
This would create vendor/ directory in the project root directory.
The vendor/autoload.php would be automatically included if it's present.
URL and File Structure
To create a blog functionality into an existing website with the following URLs:
https://example.com/blog/ # would map to /blog/index.php
https://example.com/blog/article # would map to /blog/article.php
The following file structure is needed:
/var/www/project-root
|- /actions/blog
│- |- /index.php # Handle article list requests (with pagination)
│- |- /article.php # Handle single article requests
|- /views/blog
│- |- /index.html # Template for article list
│- |- /article.html # Template for single article
|- /articles # Markdown files for blog posts
Actions
blog/index.php
<?php
class Index {
public function get() {
//Get pagination parameter
$currentPage = getQuery('page', 1);
// Get the markdown file directory
$filesdir = ROOT_DIR . "/articles";
// get the files in the directory
$files = scandir($filesdir);
// Subtracting 2 to account for "." and ".."
// this would be used for pagination total
$fileCount = count($files) - 2;
// Sort the files by modified time so new files would be first
$filesList = [];
foreach ($files as $file) {
// Ignore . and ..
if ($file === '.' || $file === '..') {
continue;
}
//Make an array with filenames and their modified time for sorting
$path = $filesdir . "/" . $file;
if (is_file($path)) {
// Store filename as key and MTime as value
$filesList[$file] = filemtime($path);
}
}
// Sort by modified time (arsort for newest first, asort for oldest first)
arsort($filesList);
// Get just the filenames (the keys)
$sortedFilenames = array_keys($filesList);
// Slice the array for the pagination
$articles = array_slice($sortedFilenames, ($currentPage - 1) * $_ENV['PAGINATION'], $_ENV['PAGINATION']);
// Loop through the slice of the files and construct the articles array
foreach ($articles as $key => $article) {
// Parse the markdown file for YAML front matter and content
$object = Spatie\YamlFrontMatter\YamlFrontMatter::parse(file_get_contents($filesdir . "/" . $article));
// Create the limited description displayed in the articles list the max length is 400 characters.
$description = substr(strip_tags((new Parsedown())->text($object->body())), 0, 400);
$description = substr($description, 0, strrpos($description, ' ')) . '...';
// Construc the final article data
$articles[$key] = [
'url' => pathinfo($article, PATHINFO_FILENAME), // the filename without extension for url
'title' => $object->title, // This is the article title
'image' => $object->featured, //article featured image
'description' => $description // limited description for the articles list
];
}
//reindex the array to have sequential keys
$articles = array_values($articles);
return [
'totalRecords' => $fileCount, //Used for pagination
'articles' => $articles, // Articles data
];
}
}
blog/article.php
<?php
class Article
{
public function get()
{
// Get the markdown file name from the url query parameter
$url = getQuery('url'); // get article name (md file name without extension)
// ... more validation can be done on the $url here ...
// $url is missing or empty
if (!$url) {
redirect('/404', 301); // Redirect to 404 page
}
// Construct the file path where the markdown file is located
$filepath = ROOT_DIR . "/articles/" . $url . ".md";
// The file is missing
if (!file_exists($filepath)) {
redirect('/404', 301); // Redirect to 404 page
}
// Parse the markdown file for YAML front matter and content
$object = Spatie\YamlFrontMatter\YamlFrontMatter::parse(file_get_contents($filepath));
// Return data to be used in the template
return [
'title' => $object->title, // This is the article title
'image' => $object->image, // This is the article featured image
'content' => (new Parsedown())->text($object->body()) // This is the article content in HTML format
];
}
}
Views
blog/index.htlm
<!--layout:/blog-layout.html-->
<section>
<!--foreach($articles as $article)-->
<article>
<img src="<!--$article['image']-->" alt="<!--$article['title']-->">
<h2><a href="/blog/article?url=<!--$article['url']-->"><!--$article['title']--></a></h2>
<p><!--$article['description']--></p>
<a href="/blog/article?url=<!--$article['url']-->">Read more</a>
</article>
<!--endforeach-->
<!--pagination-->
</section>
blog/article.html
<!--layout:/blog-layout.html-->
<article>
<h1><!--$title--></h1>
<!--($content)-->
<a href="/blog/">Back to Articles list</a>
</article>
What a markdown file would look like?
The markdown file should have the following structure to allow extracting the page title and image from the file for the article list.
---
title: "My First Blog Post"
image: "/assets/blog/img/my-first-blog-post-featured.jpg"
---
# This is the content of my first blog post
Welcome to my blog! This is where I share my thoughts and experiences.

Here is some more content in **Markdown** format.