Spring naar hoofdtekst

Doe-Het-Zelf weblog (zonder database)

Geplaatst op door ,
Laatste aanpassing op .

Inleiding

Een programmeerproject bestaat, volgens mij, uit de volgende onderdelen:

  • het denkwerk vooraf
  • de broncode
  • de bijbehorende documentatie

Met name het laatste punt leek vroeger niet zo belangrijk. Hoofdzaak is, dat het programma werkt; toch?

Nee dus. Want als je zelf (of iemand anders) na een aantal maanden of jaren opnieuw de code induikt om een aanpassing te maken of een fout op te zoeken, dan kom je er zonder degelijke documentatie niet uit. Maar, geldt dat ook voor kleine huis- tuin- en keukenprojectjes?

Ja dus. Zelfs het analyseren van een simpel shell-scriptje kan uren kosten, als er nergens wordt beschreven wat er gebeurt.

Aldus begon ik met het schrijven van een handleiding voor mijzelf, toen ik de firmware van mijn smartphone wilde vervangen. Toen ik eindelijk klaar was, bedacht ik dat ik deze tutorial graag met de rest van de wereld wilde delen, of op zijn minst voor mijzelf op internet wilde vereeuwigen.

Het plan

Dan wordt het wel een project op zich, dat documenteren! In een ver grijs verleden stonden er op www.fwiep.nl al een klein aantal artikelen, maar sinds ik van WordPress af ben gestapt, waren die niet meer toegankelijk. Toch was dit de stijl waarin ik wilde gaan schrijven; een weblog, maar dan 'met de hand'.

De onderdelen

De artikelen zouden deel gaan uitmaken van mijn website, maar ik wilde er geen database aan spenderen. Het moest een zogenaamd flatfile-CMS worden. Ik zocht naar een manier om met simpele tekstbestanden tóch met opmaak en semantiek te kunnen werken. Uiteindelijk kwam ik uit bij MarkdownExtra - een formaat dat voor mensen makkelijk lees- en schrijfbaar is, en dat zonder problemen naar HTML kan worden vertaald.

Omdat ik houd van overzicht, besloot ik de metadata van elke post (zoals titel, auteur, datum van publicatie en tags) in een apart bestand op te nemen. Ik koos voor XML, omdat het zo'n mooi universeel formaat is en alle vrijheid geeft. Bovendien is het goed te verwerken in andere talen en te valideren (als je een bijpassend XML-schema (XSD) schrijft).

Code

Hieronder zal ik een deel van de code en logica achter dit weblog bespreken.

XML voorbeeld

Het onderstaande bestand is een voorbeeld van een weblog XML-bestand. Het bevat de titel, de SEO-titel (die in hyperlinks wordt gebruikt), van elke wijziging de auteur en een datumtijd, en tot slot nog een verzameling steekwoorden (tags).

<?xml version="1.0" encoding="UTF-8"?>
<post xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="fwiepblog.xsd" xmlns="">
    <title>Doe-Het-Zelf weblog (zonder database)</title>
    <seoTitle>doe-het-zelf-weblog</seoTitle>
    <edits>
        <edit>
            <author>
                <name>Voornaam Achternaam</name>
                <email>email@example.com</email>
            </author>
            <dateTime>2017-01-01T20:13:12+01:00</dateTime>
        </edit>
    </edits>
    <tags>
        <tag>php</tag>
        <tag>markdown</tag>
        <tag>xml</tag>
        <tag>xsd</tag>
    </tags>
</post>

XSD schema

Hieronder staat het XML-schema waarmee de XML-bestanden voor het weblog kunnen worden gevalideerd. Een editor die schema's ondersteunt, kan eventuele fouten direct tijdens het typen aangeven. Ook kun je dan vaak gebruik maken van automatisch aanvullen (autocompletion).

<?xml version="1.0" encoding="UTF-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">

<xs:element name="post">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="title" type="xs:string"></xs:element>
            <xs:element name="seoTitle" type="SEOTitle"></xs:element>
            <xs:element name="edits" type="EditsType">
                <xs:unique name="edit-unique">
                    <xs:selector xpath="edit"></xs:selector>
                    <xs:field xpath="./dateTime"></xs:field>
                </xs:unique>
            </xs:element>
            <xs:element name="tags" type="TagsType">
                <xs:unique name="tag-unique">
                    <xs:selector xpath="tag"></xs:selector>
                    <xs:field xpath="."></xs:field>
                </xs:unique>    
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- ==================================================================== -->

<xs:complexType name="AuthorType">
    <xs:sequence>
        <xs:element name="name" type="xs:string"></xs:element>
        <xs:element name="email" type="EmailAddress"></xs:element>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="EditType">
    <xs:sequence>
        <xs:element name="author" type="AuthorType"></xs:element>
        <xs:element name="dateTime" type="xs:dateTime"></xs:element>
    </xs:sequence>
</xs:complexType>

<xs:complexType name="EditsType">
    <xs:sequence minOccurs="1" maxOccurs="unbounded">
        <xs:element name="edit" type="EditType"></xs:element>
    </xs:sequence>
</xs:complexType>

<xs:simpleType name="TagType">
    <xs:restriction base="xs:string">
        <xs:minLength value="1"></xs:minLength>
    </xs:restriction>
</xs:simpleType>

<xs:complexType name="TagsType">
    <xs:sequence minOccurs="0" maxOccurs="unbounded">
        <xs:element name="tag" type="TagType"></xs:element>
    </xs:sequence>
</xs:complexType>

<xs:simpleType name="SEOTitle">
    <xs:restriction base="xs:string">
        <xs:pattern value="[a-zA-Z0-9]|[a-zA-Z0-9][a-zA-Z0-9-]*[a-zA-Z0-9]"></xs:pattern>
    </xs:restriction>
</xs:simpleType>

<xs:simpleType name="EmailAddress">
    <xs:restriction base="xs:string">
        <xs:pattern value="[a-zA-Z0-9][a-zA-Z0-9._%+-]{0,63}@([a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?\.){1,8}[a-zA-Z]{2,63}"></xs:pattern>
    </xs:restriction>
</xs:simpleType>

<!-- ==================================================================== -->

</xs:schema>

PHP code

Met de onderstaande PHP-code wordt het bovengenoemde XML-bestand uitgelezen en een reeks (array $blogs) gevuld voor later gebruik. De klassen BlogPost, BlogPostEdit en BlogTag maken eenvoudige objecten met een aantal eigenschappen (properties) die ik hier niet nader zal beschrijven.

Let tijdens het uitlezen van het XML-bestand op de validatie. Niet alleen wordt de extensie van het bestand gecontroleerd, maar het bestand wordt ook nog gevalideerd met behulp van het XML-schema (XSD). Hierdoor kan de rest van het script er 100% op vertrouwen dat alle velden gevuld zijn en op de juiste positie staan.

Met behulp van DOMDocument en DOMXPath (syntax) wordt de XML-data uitgelezen en in de properties van de betreffende objecten opgeslagen.

$blogs = array();
$blogdir = 'path/to/xml-files';
$xml = new \DOMDocument();

if ($dir = opendir($blogdir)) {
    while (false !== ($f = readdir($dir))) {
        if (!is_dir($f) && pathinfo($f, PATHINFO_EXTENSION) == 'xml') {
            if (@$xml->load($blogdir.$f)
                && @$xml->schemaValidate($blogdir.'fwiepblog.xsd')
            ) {
                $xp = new \DOMXPath($xml);
                $edits = $xp->query('./edits/edit');
                $tags = $xp->query('./tags/tag');

                $blog = new BlogPost();
                $blog->setID(pathinfo($f, PATHINFO_FILENAME));

                $blog->setTitle(
                    $xp->query('./title')->item(0)->nodeValue
                );
                $blog->setSeoTitle(
                    $xp->query('./seoTitle')->item(0)->nodeValue
                );

                foreach ($edits as $e) {
                    $edit = new BlogPostEdit();

                    $edit->setAuthorName(
                        $xp->query('./author/name', $e)->item(0)->nodeValue
                    );
                    $edit->setAuthorEmail(
                        $xp->query('./author/email', $e)->item(0)->nodeValue
                    );
                    $edit->setDateTime(
                        new \DateTime(
                            $xp->query('./dateTime', $e)->item(0)->nodeValue
                        )
                    );
                    $blog->addEdit($edit);
                }

                foreach ($tags as $t) {
                    $tag = new BlogTag($t->nodeValue);
                    $blog->addTag($tag);
                }

                $blogs[] = $blog;
            }
        }
    }
    closedir($dir);
}

TODO

Punten waar ik al aan gedacht heb, maar die nog verder denkwerk, informatie of ondersteuning vragen:

  • Een zoekfunctie op auteur, inhoud, datum van plaatsing, datum laatste aanpassing of tags...
  • Een beheermogelijkheid vanuit het weblog zelf. Om een nieuw artikel aan te maken, moet ik nu nog op de server (of lokaal) een shell-commando uitvoeren. Het zou mooi zijn als dit via de 'voorkant' van de applicatie zou kunnen. Maar, dan moet die functie ook degelijk worden afgeschermd...
  • Een zinvolle toepassing van de tags bij elke post. Denk daarbij bijvoorbeeld aan een (toegankelijke) tagcloud of post-filtering...

Inhoudsopgave

Klik op één van de onderstaande categorieën om de lijst met artikelen te filteren.