Build a PHP minimal Blog
When I was about to create this new fancy blog for my website, I was wondering what would be the easiest way to implement it without losing much time programming. Moments later, I was doing the same thing I always do when something could be just straight forward. Using an existing framework? Would you say...
... 🤦♂️
Noup! I created my own ultra-minimal framework to handle it. But that is great because now I can blog in my blog about the blog! 🤯
If that makes any sense at all.
Let's start defining what we need
First of all, I refused to mess around with any database. But on the other hand, I wanted to separate the title (so that I can use it for a custom URL), store the writing date, and have everything separated and well ordered. So I decided to store each post in separate files within the same directory, using their names to save the corresponding date and title.
Utilizing this method, this post, for example, would be stored as:20191028-Build a PHP minimal Blog.html
To handle it, I defined a Post class with two main methods (from_url
and get_posts
) as described below:
class Post { const POSTS_CONTENT_DIR = 'posts/'; // Posts directory const NUM_PREVIEW_PARAGRAPHS = 4; // Number of elements for the post preview public $title; public $content; public $preview; public $date_time; public $date_string; private function __construct ($file) { // Configure Post data from file } public static function from_url ($url) { // * Get title pattern from $url // * Find closest matching post // * Use newest post as a fallback, instead of 404 // * Generate and return corresponding post object } public static function get_posts () { // * Get list of posts from the directory specified in POSTS_CONTENT_DIR // * Return an array containing each corresponding Post instance } }
Getting into the code
/blog.php
This file is used only to print the correspondent post, according to the URL. Once the Post object is defined, the only thing left to do is writing the information with the desired design.
<?php include 'php/Post.php'; $POST = Post::from_url($_SERVER['REQUEST_URI']); ?> <article> <h1><?php echo $POST->title ?></h1> <time datetime="<?php echo $POST->date_time ?>"> <?php echo $POST->date_string ?> </time> <br> <?php echo $POST->content ?> </article>
Last but not least, configure the server to use the blog.php script for all URLs like /blog/...
. Modifying the .htaccess file, you can achieve it by adding the following lines:
/.htaccess
RewriteEngine On RewriteBase / RewriteRule ^blog blog.php
/index.php
This file shows a list of the latest blogs, previewing them with their main starting paragraphs. Its functionality is almost identical to blog.php
but iterating through the post array given by Post::get_posts()
<?php include 'php/Post.php'; $POSTS = Post::get_posts(); ?> <?php foreach ($POSTS as $post) { ?> <article> <a href="/blog/<?php echo $post->title ?>"> <h1><?php echo $post->title ?></h1> </a> <time datetime="<?php echo $post->date_time ?>"> <?php echo $post->date_string ?> </time> <br> <?php echo $post->preview; ?> <a class="read-more-button" href="/blog/<?php echo $post->title ?>"> Read more ↦ </a> </article> <hr/> <?php } ?>
/php/Post.php
Finally, here is the Post class complete code, just in case you want to take a look:
class Post { const POSTS_CONTENT_DIR = 'posts/'; const NUM_PREVIEW_PARAGRAPHS = 4; public $title; public $content; public $preview; public $date_time; public $date_string; private function __construct ($file) { preg_match('/^(\d{8})-(.+)\.html$/', $file, $matches); $this->content = mb_convert_encoding( file_get_contents(self::POSTS_CONTENT_DIR . $file), 'HTML-ENTITIES', 'UTF-8'); $document = new DOMDocument(); $document->loadHTML("<body>{$this->content}</body>"); $children = $document->getElementsByTagName('body')->item(0)->childNodes; $paragraphs = 0; foreach ($children as $child) { $this->preview .= $document->saveHTML($child); if (get_class($child) !== 'DOMText') $paragraphs++; if ($paragraphs === self::NUM_PREVIEW_PARAGRAPHS) break; } list(, $rawDate, $this->title) = $matches; $date = new DateTime($rawDate); $this->date_time = $date->format('Y-m-d'); $this->date_string = $date->format('F j, Y'); } public static function from_url ($url) { $title_pattern = urldecode(basename($url)); $post_files = glob(self::POSTS_CONTENT_DIR . '????????-*.html'); $file = array_pop($post_files); foreach ($post_files as $post_file) if (preg_match("/\d{8}-.*$title_pattern.*.html/i", $post_file)) $file = $post_file; return new self(basename($file)); } public static function get_posts () { return array_map(function ($post_file) { return new self(basename($post_file)); }, array_reverse(glob(self::POSTS_CONTENT_DIR . '????????-*.html'))); } }