Sniðmát með PHP (tilraun 2) Notkun sniðmáta (e. Template) í vefsíðugerð er mjög góð leið til að auðvelda viðhald vefsíðna. Þannig vill það oft verða að þegar maður er að vinna með lítil verkefni og vinnur þau utan ramma (e. Framework) að meira er lagt uppúr hraða við það að koma síðunni upp en minna á að gera það svo að viðhald og viðbætur síðar meir verði einfaldar.
Þetta skiptir líka verkinu niður í nokkra parta þannig að það er auðvelt að breyta bara um útlit án þess að eiga það á hættu að fokka í einhverju öðru.

Ég sjálfur kynntist ekki hversu frábært var að vinna með sniðmát fyrr en ég fór að nota svona n-tier (t.d. MVC) ramma (Django og TurboGears). Hafði alltaf séð þetta sem auka vesen sem tæki bara tíma og myndi ekki skila mér neinu, hvað þá auknum hraða.

Um daginn var ég svo að hjálpa bróður mínum með lítið verkefni og ég vildi endilega gera hlutina eins og ég kann vel við þá, nota sniðmát og fleira sem mér finnst einfalda vinnuna. Svo að ég fór að leita að einhverju sem myndi henta í verkið. Það sem ég var að leita að var ekki eitthvað risa stórt kerfi (eins og Smarty) sem krefðist þess að maður lærði nýja málskipan (e. Syntax) heldur bara eitthvað einfalt til að skilja að rökfræðina (e. Logic) í verkefninu og framsetninguna.

Þegar ég skoðaði það sem var í boði þá varð ég fyrir svolitlum vonbrigðum með það sem kom svona fyrst upp við leit. Þetta var rosalega mikið annaðhvort eitthvað XML dót (sem kemur auðvitað með slatta overhead) eða Regular Expression sem þáttar (e. Parse) sniðmáts kóðann. Sama hvort er þá er þetta einhver ný málskipan sem hefur í flestum tilvikum takmarkaða getu á við tungumál eins og PHP. Oft er þetta gert vísvitandi en það er eitthvað sem mér finnst ekki rétt.

Að mínu mati á það sem sniðmátsvél gerir að skilja rökfræði frá framsetningu en ekki forritunarkóða frá einhverju sem er hvorki fugl né fiskur.

Þess vegna, eftir að hafa skoðað nokkrar aðferðir við þetta ákvað ég að skrifa þennan litla klasa (innan við 100 línur). Aðferðin sem ég nota er að ég nota úttaksbiðminnis (e. Output buffer) aðferðirnar sem PHP hefur (ob_get_contents() og ob_end_clean()) sem leyfir manni að henda því sem yrði undir venjulegum kringumstæðum prentað út á skjáinn í breytu og skila henni.

Svo… Án frekari málalenginga er hérna klasinn:
 <?php
class Template {
    private $vars; //breytur sem við sendum sniðmátinu
    private $template; //hvaða template við ætlum að nota
    
    //hlutir sem tengjast flýtiminnis möguleikanum
    private $use_cache; //eigum við að nota flýtiminni yfir höfuð
    private $is_cached; //er til útgáfa af síðunni í flýtiminninu (sem er ekki útrunnin)
    private $expire; //hvenær rennur flýtiminnisútgáfan út
    
    //kyrreignir sem allir hlutir af tegundinni Template geta notað
    private static $path; //staðsetning sniðmátanna á skráarkerfinu
    private static $cache_path; //staðsetningin sem við geymum flýtiminnið í
    
    public function __construct($template, $expire = null) {
        $this->template = $template;
        $this->vars = Array();
        
        $this->use_cache = ($expire !== null);
        $this->expire = $expire;
    }
    
    public static function set_path($path) {
        self::$path = (substr($path, -1) == '/') ? $path : $path . '/';
    }
    
    public static function set_cache_path($path) {
        self::$cache_path = (substr($path, -1) == '/') ? $path : $path . '/';
    }
    
    public function set($name, $value) {
        $this->vars[$name] = $value;
    }
    
    public function set_vars($array, $clear = false) {
        if(is_array($array)) {
            if($clear) {
                $this->vars = $array;
            } else {
                $this->vars = array_merge($this->vars, $array);
            }
        }
    }
    
    public function render() {
        if($this->is_cached()) {
            $fp = fopen($this->c_filename(), 'r');
            $content = fread($fp, filesize($this->c_filename()));
            fclose($fp);
        } else {
            extract($this->vars);
            ob_start();
            include(self::$path . $this->template);
            $content = ob_get_contents();
            ob_end_clean();
            if($this->use_cache) {
                $fp = fopen($this->c_filename(), 'w');
                fwrite($fp, $content);
                fclose($fp);
            }
        }
        return $content;
    }
    
    private function is_cached() {
        if(!$this->use_cache) return false;
        if($this->is_cached) return true;
        
        $mtime = filemtime($this->c_filename());
        
        //tékka hvort tími sé á skjalinu (ef ekki er það ekki til) og hvort að hann sé útrunninn
        if(!$mtime) return false;
        if(($mtime + $this->expire) < time()) {
            @unlink($this->c_filename());
            return false;
        } else {
            $this->is_cached = true;
            return true;
        }
    }
    
    private function c_filename() {
        return self::$cache_path . md5($this->template . $_SERVER['REQUEST_URI']);
    }
}
?>
Þetta er ekki hugsað sem einhver kennsla í PHP, maður sendir kannski inn eitthvað slíkt síðar. Og þar sem þessi kóði er (finnst mér) mjög sjálf-útskýranlegur þá er hann lítið útskýrður (nema hvaða hlutverki vissar breytur gegna í byrjun).

Notkunin á þessu er mjög einföld, hérna eru smá dæmi:
test01.php
<?php  $start = microtime(true);  require_once('template.php');  Template::set_path('./templates/'); Template::set_cache_path('./cache/');  $template = new Template('sumth.html', 10); //$template = new Template('sumth.html');  $vars = array('title' => 'Þetta er síða!', 'heading' => 'Prufun á sniðmátum'); $template->set_vars($vars);  $list = array('Jón Jónsson' => 'nonni@ble.com', 'Gunni Gunn' => 'gunnz@gunnz0r.im','j00z0r' => 'im4r3al@w00t.is');  $template->set('list', $list);  echo $template->render();  $time = microtime(true) - $start;  echo $time; 
?>[code][code]sumth.html
<html>
<head>
    <title><?=$title?></title>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
</head>
<body>
    <h1><?=$heading?></h1>
    <h2>Listi:</h2>
    <ul>
    <?php
    
    foreach( $list as $name => $email ) {
        printf('<li>%s, <a href="mailto:%s">%s</a></li>', $name, $email, $email);
    }
    
    sleep(2);
    
    ?>
    </ul>
</body>
</html>[/code]
Eitt sem vert er að taka fram. Þetta er [b]bara[/b] mjööög (það eiga að vera mörg ö þarna) einföld sniðmátsvél. Þessi flýtiminnisfítus kemur ekki til með að spara ykkur mikinn tíma nema í mjög miklum jaðartilvikum, s.s. [b]ef[/b] þið eruð með mikla rökfræði í framsetningunni ykkar (forma milljón dagsetningar eða eitthvað, hvað veit ég) þá gæti þetta sparað smá, bæði fyrir örgjörvann og fyrir notandann að bíða.
En flýtiminnið er með og ég sýni hvernig það hugsanlega gæti sparað tíma með sleep() fallinu þar sem það bíður í 2 sekúndur, venjulega er það samt bara að spara einhverja hundraðshluta úr sekúndu.
Ég nenni ekki að gera þetta lengra en bið ykkur endilega um að gera athugasemdir við þetta, bæði hvað ykkur finnst um þessa útfærslu á sniðmátum (að nota bara PHP kóða í það) og ef þið hafið einhverjar athugasemdir við kóðann, skiljið ekki eða eitthvað annað.
“If it isn't documented, it doesn't exist”