In this tutorial, we will be making a lightweight blog system with
PHP and CSS3. The blog posts will be stored in a folder as files (in the
markdown format). The system will not use a database and will not have
an admin panel, which makes it perfect for small blogs with only one
author. It will have an RSS feed and a JSON read-only API, and will
feature a completely responsive design that adapts to the screen size of
the device.
In this first part, we are going to write the PHP backend, and in
part 2, we will code the HTML and CSS. Let’s begin!
The posts
Posts will be named in the following pattern:
2013-03-14_url-of-the-post.md and they will be stored in the
posts/
folder. The first part is the date of the post (you can optionally
include a time as well if you have more than one post for that day),
followed by an underscore, and the title of the post. The underscore is
used by the system to separate the title from the date. This convention
also has the benefit that posts can easily be sorted by publication
date. When displayed on your blog, this post will have an URL like
http://example.com/2013/03/url-of-the-post
Each post will be in the markdown format (hence the extension). The
only requirement is that the first line of the markdown document is
formatted as a H1 heading. For example:
1 | # The title of the blog post |
This will make the first line a H1 heading. If this is the first time you’ve seen markdown, the next section is for you.
What is markdown
Markdown is a
lightweight format for writing text. It has a minimal and intuitive
syntax, which is transformed into HTML. This makes it perfect for our
simple blog system, as it won’t have an admin panel nor database – all
the posts will be stored in a folder as files.
We can’t simply display the raw markdown directly, first we have to
transform it into HTML. There are a number of libraries that can do this
(in every major language), but for this tutorial I chose the
PHP Markdown library, as it integrates nicely with composer.
Composer
Composer
is a dependency manager for PHP. With it, you can declare what
libraries your project depends on, and they will be downloaded
automatically for you. We will use it to include two libraries in this
project:
You could simply download these libraries and use them straight away,
but let’s see how we can do the same with composer (note that you need
to follow these steps only if you are starting from scratch; the zip
accompanying the tutorial already includes all the necessary files).
First we have to declare which packages the current project depends on, in a file named
composer.json:
3 | "dflydev/markdown" : "v1.0.2" , |
4 | "suin/php-rss-writer" : ">=1.0" |
You can obtain the specific identifiers for the libraries and their versions from the
composer package repository, or by following the instructions that each library included on its github repo.
The next steps are to install composer into your project (follow
these instructions), and to run the install command. This will download all the appropriate libraries and place them in the
vendor/ folder. All that is left is to include the composer autoloader in your php scripts:
1 | require 'vendor/autoload.php' ; |
This will let you create instances of the libraries without having to include their PHP files individually.
The configuration file
I am using one additional PHP library -
Dispatch. This is a tiny routing framework that
I’ve mentioned before.
It will let us listen for requests on specific URLs and render views.
Unfortunately it doesn’t have Composer support at the moment, so we have
to download it separately and include it into the project.
A neat feature of this framework is that it lets you write configuration settings in an INI file, which you can access with the
config() function call. There are a number of settings you need to fill in for your blog to work:
app/config.ini
06 | blog.title = "Tutorialzine Demo Blog" |
07 | blog.description = "This is a lightweight and responsive blogging system.." |
08 | blog.authorbio = "Created by ..." |
12 | ; Framework config. No need to edit. |
These settings are used throughout the views, so when you are setting up a new blog you won’t need to edit anything else.
The PHP code
Here is our main PHP file:
index.php
03 | require 'vendor/autoload.php' ; |
07 | require 'app/includes/dispatch.php' ; |
08 | require 'app/includes/functions.php' ; |
11 | config( 'source' , 'app/config.ini' ); |
15 | get( '/index' , function () { |
17 | $page = from( $_GET , 'page' ); |
18 | $page = $page ? (int) $page : 1; |
20 | $posts = get_posts( $page ); |
22 | if ( empty ( $posts ) || $page < 1){ |
30 | 'has_pagination' => has_pagination( $page ) |
35 | get( '/:year/:month/:name' , function ( $year , $month , $name ){ |
37 | $post = find_post( $year , $month , $name ); |
44 | 'title' => $post ->title . ' ⋅ ' . config( 'blog.title' ), |
50 | get( '/api/json' , function (){ |
52 | header( 'Content-type: application/json' ); |
55 | echo generate_json(get_posts(1, 10)); |
61 | header( 'Content-Type: application/rss+xml' ); |
64 | echo generate_rss(get_posts(1, 30)); |
The
get() function of Dispatch creates a pattern
that is matched against the currently visited URL. If they match, the
callback function is executed and no more patterns are matched. The last
get() call sets up a pattern that matches every URL in which case we show a 404 error.
Some of the functions you see above are not part of the Dispatch
framework. They are specific to the blog and are defined in the
functions.php file:
- get_post_names() uses the glob function to find all
the posts and to sort them in alphabetical order (as the date is the
first part of the file name, this effectively sorts them by their
publishing date);
- get_posts() takes the list of names returned by
get_post_names() and parses the files. It uses the Markdown library to
convert them to HTML;
- find_post() searches for a post by month, day and name;
- has_pagination() is a helper function that is used in the views (we will look at them in the next part);
- not_found() is a wrapper around Dispatch’s error function but renders a view as the message;
- generate_rss() uses the RSS Library we mentioned above, and turns an array of posts into a valid RSS feed;
- generate_json() is only a wrapper around json_encode, but I included it for consistency with the generate_rss function.
And here is the source:
app/includes/functions.php
001 | use dflydev\markdown\MarkdownParser; |
002 | use \Suin\RSSWriter\Feed; |
003 | use \Suin\RSSWriter\Channel; |
004 | use \Suin\RSSWriter\Item; |
008 | function get_post_names(){ |
010 | static $_cache = array (); |
017 | $_cache = array_reverse ( glob ( 'posts/*.md' )); |
025 | function get_posts( $page = 1, $perpage = 0){ |
028 | $perpage = config( 'posts.perpage' ); |
031 | $posts = get_post_names(); |
034 | $posts = array_slice ( $posts , ( $page -1) * $perpage , $perpage ); |
039 | $md = new MarkdownParser(); |
041 | foreach ( $posts as $k => $v ){ |
043 | $post = new stdClass; |
046 | $arr = explode ( '_' , $v ); |
047 | $post -> date = strtotime ( str_replace ( 'posts/' , '' , $arr [0])); |
050 | $post ->url = site_url(). date ( 'Y/m' , $post -> date ). '/' . str_replace ( '.md' , '' , $arr [1]); |
053 | $content = $md ->transformMarkdown( file_get_contents ( $v )); |
056 | $arr = explode ( '</h1>' , $content ); |
057 | $post ->title = str_replace ( '<h1>' , '' , $arr [0]); |
058 | $post ->body = $arr [1]; |
067 | function find_post( $year , $month , $name ){ |
069 | foreach (get_post_names() as $index => $v ){ |
070 | if ( strpos ( $v , "$year-$month" ) !== false && strpos ( $v , $name . '.md' ) !== false){ |
075 | $arr = get_posts( $index +1,1); |
085 | function has_pagination( $page = 1){ |
086 | $total = count (get_post_names()); |
090 | 'next' => $total > $page *config( 'posts.perpage' ) |
096 | error(404, render( '404' , null, false)); |
100 | function generate_rss( $posts ){ |
103 | $channel = new Channel(); |
106 | ->title(config( 'blog.title' )) |
107 | ->description(config( 'blog.description' )) |
111 | foreach ( $posts as $p ){ |
116 | ->description( $p ->body) |
118 | ->appendTo( $channel ); |
125 | function generate_json( $posts ){ |
126 | return json_encode( $posts ); |
At the top of the file, we have a number of
use statements, which import the necessary namespaces (read
more about PHP’s namespaces).
The last thing we need to do, is to rewrite all requests so they hit
index.php. Otherwise our fancy routes wouldn’t work. We can do this with
an .htaccess file:
3 | RewriteCond %{REQUEST_FILENAME} !-f |
4 | RewriteCond %{REQUEST_FILENAME} !-d |
6 | RewriteCond $1 !^(index\.php) |
7 | RewriteRule ^(.*)$ index.php/$1 [L] |
If a file or folder does not exist, the request will be redirected to index.php.
by
Martin Angelov
source: http://tutorialzine.com/2013/03/simple-php-blogging-system/
0 komentar:
Post a Comment