Less talk, more code

The blog of Iskandar Soesman

Membuat Aplikasi Blog (Bagian 1)

Sebenarnya sangat banyak sekali apliaksi blog yang tersedia secara gratis yang bisa kita langsung download. Bahkan kita juga bisa memanfaatkan layanan seperti Blogger, Wordpress, Tumblr. Kenapa harus membuat lagi? Belajar, itu jawabannya.

Artikel ini terutama ditujukan bagi mereka yang baru saja belajar pengembangan aplikasi berbasis PHP. Semua pendekatan yang digunakan pada aplikasi ini dupayakan untuk seminimal mungkan menggunakan konsep-konsep yang rumit dengan harapan setiap bagian dari kode yang akan ditulis bisa mudah untuk dipahami. Namun demikian beberapa konsep design patern yang sederhana akan coba diperkenalkan untuk menambah wawasan.

Design Aplikasi

  • Sederhana; Alur aplikasi yang sederhana agar cukup mudah untuk dipahami bagi teman-teman yang baru belajar bahasa pemgrograman PHP.
  • Markdown; Media penyimpanan content adalah berupa file yang berformat markdown dan tidak menggunakan sistem database. Penggunaan file ini juga membuat aplikasi lebih fleksible untuk dijalankan di berbagai macam environment.
  • URL friendly; URL aplikasi harus url friendly untuk meningkatkan SEO.
  • Pemisahan view dan controller; Agar kode yang kita tulis tetap rapih, antara logic request dan tampilan kita pisahkan.

Struktur Folder

Struktur folder aplikasi blog yang akan kita buat adalah:

contents/
public/
template/
vendor/

Contents adalah folder tempat menyimpan file-file markdown dari content blog. File markdown ber-ekstensi .md contohnya adalah 1-hello-world.md. Nama file mengikuti dari judul content dan di awal nama file ditambahkan nilai angka increament yang mengikuti jumalah content keseluruhan. Ini diperlukan untuk proses pengurutan content.

Public adalah folder yang digunakan untuk menempatkan file index.php dan file-file aset seperti css dan images. Semua requst akan di-handle oleh index.php, dan di dalam file inilah setiap request yang datang di-routing ke bagian yang khusus menangani requst tersebut. Pola seperti ini biasa disebut juga dengan isitilah front controller design pattern.

Sisanya untuk persentasi kita kumpulkan di folder template dan untuk library helper kita kumpulkan di folder vendor.

Untuk tahap ini semua source kode-nya bisa anda clone dari sini https://github.com/ikandars/gia/tree/de9f819bde79bd86cfb46bb076d3522f4b2acaf7

index.php

Kita akan mulai membahas setiap bagian kode yang ada di file index.php. Sebelum lebih jauh pastikan versi PHP yang digunakan adalah 5.6 atau yang lebih baru.

<?php

date_default_timezone_set('Asia/Jakarta');

const CONF = [
    'contentPath' => '../contents',
    'templatePath' => '../template/blog/',
    'blogTitle' => 'Less talk, more code',
    'blogSubTitle' => 'The blog of Iskandar Soesman'
];

Pada kode di atas kita mulai dengan setting time zone pada daerah yang kita gunakan. Anda bisa menyesuaikan dengan daerah tempat anda berada. List lengkapnya bisa dilihat di halaman ini http://php.net/manual/en/timezones.php

Pada line berikutnya, kita defenisikan konstanta yang berisikan informasi-informasi yang dibutuhkan oleh aplikasi kita. ContentPath adalah informasi lokasi folder tempat kita menyimpan file-file markdown dari content blog. tempaltePath adalah folder tempat mengumpulkan file-file presentasi atau view. BlogTitle adalah judul untuk blog dan blogSubTitle adalah subjudulnya.

Selanjutnya kita membutuhkan library bantuan untuk memparsing content markdown dan kemudian dirubah menjadi HTML. Untuk melakukan ini kita gunakan Parsedown.

Selain itu kita juga membutuhkan class DOMDocument, FilesystemIterator dan RegexIterator. Semua class ini sudah tersedia secara bawaan di dalam PHP.

require '../vendor/erusev/parsedown/Parsedown.php';

$parsedown = new Parsedown();
$doc = new DOMDocument();

Setelah kita inisiasi class-class yang kita butuhkan, selanjutnya fungsi pertama yang kita buat adalah fungsi untuk mem-populasi file-file markdown yang ada di dalam folder CONF['contentPath']. Kita hanya membutuhkan file yang berekstensi .md. Oleh karena itu kita gunakan class RegexIterator untuk memfilter isi folder tersebut.

Setiap data file yang sudah didapatkan, ambil nilai id integer yg terdapat di awal nama file yang nanti kita akan manfaatkan untuk melakukan pengurutan. Data id dan nama file kemudian kita simpan sebagai data array di variable $files dimana nilai id kita jadikan key dan nama file sebagai value. Dengan cara ini kita bisa melakukan sorting. Untuk sorting ini kita gunakan fungsi krsort yang ini berarti diurutkan dari yg terbesar ke terkecil.

$getMDFiles = function() {

    $resource = new FilesystemIterator(CONF['contentPath']);
    $resource = new RegexIterator($resource,'/^.+\.md/i');

    $files = [];

    foreach ($resource as $fileInfo) {
        $file = $fileInfo->getFilename();
        $key = explode('-', $file)[0];
        $files[$key] = $file;
    }

    krsort($files);

    return $files;
};

Untuk mendapatkan judul artikel dan paragraf pertama yang akan digunakan sebagai lead, kita buat sebuah fungsi yang bertugas mem-parsing content html. Untuk judul kita akan ambil dari tag h1 dan lead kita ambil dari tag p yang paling pertama. Untuk melakukan ini kita gunakan class DOMDocument. Berikut adalah kodenya.

$getTagContent = function($html, array $tags) use ($doc) {

    $doc->loadHTML($html);

    $return = [];

    foreach($tags as $tag) {
        $text = $doc->getElementsByTagName($tag);
        $text = iterator_to_array($text)[0];
        $text = $text->textContent;

        $return[$tag] = $text;
    }

    return $return;
};

Selanjutnya kita akan buat sebuah fungsi yang bertugas untuk mendapatkan informasi setiap file dan sekaligus merubah isi yang berformat markdown menjadi html. Pada fungsi ini kita juga akan mendapatkan id content, tanggal terakhir file diupdate, judul, lead dan url slug.

$getContent = function($filePath, $html = false) use ($parsedown, $getTagContent) {

    $id         = explode('-', $filePath)[0];
    $filePath   = str_replace('.md', '', $filePath).'.md';
    $filePath   = CONF['contentPath'].'/'.$filePath;

    if(!$html) {
        $html   = file_get_contents($filePath);
    }

    $html       = $parsedown->text($html);
    $date       = date ('F d Y H:i:s', filemtime($filePath));

    $text       = $getTagContent($html, ['h1', 'p']);

    return [
        'html' => $html,
        'lastModified' => $date,
        'id' => $id,
        'title' => $text['h1'],
        'url' => '/'.pathinfo($filePath, PATHINFO_FILENAME),
        'date' => $date,
        'lead' => $text['p']
    ];
};

Pada halaman index, kita akan membuat list daftar artikel terbaru. Untuk kita membutuhkan sebuah fungsi yang mengambil setiap data artikel dan akan pada halaman view daftar ini kita akan iterasi satu-persatu. Untuk menghemat proses, pada fungsi ini kita akan gunakan generator. Seperti yang kita lihat pada kode di bawah, kita tidak perlu mengumpulkan data ke dalam sebuah array, tapi data langsung berbentuk pointer yang baru diproses pada saat iterasi. Pada saat pengambilan data kita manfaatkan variable $getContent yang sudah kita buat sebelumnya.

$postList = function ($files) use ($getContent) {

    foreach($files as $file) {
        yield $getContent($file);
    }
};

Selanjutnya kita membutuhkan sebuah fungsi yang akan kita gunakan untuk mengelola template secara sederhana. Pada intinya fungsi ini akan meng-include file template dan kemudian isinya kita buffer sehingga bisa kita return sebagai string. Pada fungsi ini kita juga men-extract variable yang nilainya akan digunakan di dalam template. Berikut adalah fungsinya.

$template = function($file, &$vars, $header = 'HTTP/1.1 200 OK') use (&$template) {

    extract($vars, EXTR_SKIP);

    ob_start();

    require CONF['templatePath'].$file.'.php';

    return ['body' => ob_get_clean(), 'header' => $header];
};

Setelah template, kita juga membutuhkan fungsi untuk melakukan routing. Fungsi ini bertugas menampilkan halaman yang sesuai berdasarkan URL yang dikirimkan user. Untuk blog ini setidaknya kita membutuhkan 3 routng, yaitu

  • / untuk halaman home
  • /page/2 untuk halaman artikel-artikel sebelumnya
  • /id-judul-artikel untuk halaman detail artikel
$route = function($map, $uri) use ($template) {

    $path   = strtok($uri, '?');
    $uri    = array_replace(['page', 1], explode('/', trim($path, '/')));

    foreach($map as $pattern => $callBack) {
        if(preg_match($pattern, $path)) {
            return call_user_func_array($callBack, $uri);
        }
    }

    $vars = [];
    return $template('404', $vars, 'HTTP/1.1 404 Not Found');;
};

Berdasarkan fungsi di atas, kita membutuhkan sebuah array yang berisi 3 element routing yang mana key array-nya kita beri nilai format regex dan sebagai value adalah fungsi untuk handler halaman sesuai routing yang telah ditentukan.

$map = [
    // home anda index page eg: myblog.com or myblog.com/page/2
    '/(^\/$)|(^\/page)/' => function($path, $page) use ($getMDFiles, $postList, $template) {

        $limit  = 10;
        $offset = ($limit * $page) - $limit;
        $files  = array_slice($getMDFiles(), $offset, $limit);

        $vars = [
            'section' => 'home',
            'title' => 'Less talk, more code',
            'posts' => $postList($files),
            'olderPage' => $page + 1,
            'newerPage' => $page - 1,
        ];

        return $template('layout', $vars);
    },

    // article page eg: myblog.com/1-hello-world
    '/^\/[0-9]+\-[\/A-Za-z0-9\-]/' => function($slug) use ($getMDFiles, $getContent, $postList, $template) {

        $filePath = CONF['contentPath'].'/'.$slug.'.md';

        if(! $md = @file_get_contents($filePath)) {
            $vars = [];
            return $template('404', $vars, 'HTTP/1.1 404 Not Found');
        }

        $files  = array_slice($getMDFiles(), 0, 10);
        $post   = $getContent($slug, $md);

        $vars = [
            'section' => 'post',
            'title' => $post['title'],
            'posts' => $postList($files),
            'post' => $post,
            'template' => $template
        ];

        return $template('layout', $vars);
    }
];

Variable $route mengembalikan data berupa array yang terdiri dari dua element. Pertama, string header yaitu http header yang akan kita kirimkan ke user dan body. Body adalah output html akhir yang siap kita echo ke user.

$output = $route($map, $_SERVER['REQUEST_URI']);

header($output['header']);
echo $output['body'];

Sampai titik ini kita sudah bisa menampilkan artikel dari file-file markdown yang ada di dalam folder contents. Untuk percobaan pertama kita buat sebuah file dengan nama 1-hello-world.md yang isinya:

# Hello World

Hello world from gia

Terakhir kita membutuhkan template sederhana untuk tampilan blog kita. Untuk file template anda bisa download dan kemudian simpan di folder template/blog dari halaman ini.

Yang perlu diperhatikan adalah template terdiri dari 5 file terpisah yaitu:

  • 404.php; digunakan untuk menampilkan halaman error 404
  • home.php; digunakan untuk halaman home
  • layout.php; digunakan untuk base layout dari semua tampilan
  • post.php; digunakan untuk menampilkan halaman detail artikel
  • widget.php digunakan untuk menampilkan widget artikel terbaru di sisi kanan halaman detail

Sampai titik ini pembahasan kita sudah selesai. Pada artikel lain akan kita bahas aplikasi cms untuk membuat dan mengelola artikel.