Lav dit eget CMS - del 3 26. okt 2010

I første del af denne artikelserie blev du introduceret til ideen om at gentagelser er af det onde, og du fik ændret dine websider så de alle sammen inkluderer den samme header, footer og menu – i stedet for at gentage dem på hver eneste side. I anden del lavede du menu-komponenten om, så den markerer den aktuelle side, og det blev nemmere at tilføje og ændre links. Og her i tredje del får du hjælp til at eliminere den største gentagelse af dem alle – nemlig siderne!

Brug en skabelon

Alle dine sider har den samme opbygning – den der blev introduceret i første del:

<html>
<head><title>sidens navn</title></head>
<body>
<?php include("header.php") ?>
<?php include("menu.php") ?>

<div id="content">sidens indhold</div>

<?php include("footer.php") ?>
</body>
</html>

Hvis du er smart – og det er du vel, siden du vil lave dit eget cms – så har du en tom side liggende, en fil der kun indeholder de ting der er fælles for alle sider, og når du skal oprette en ny side, så kopierer du denne skabelon, og laver dine ændringer her. Header, menu og footer er allerede i separate komponenter, så det kunne næsten ikke være simplere. Eller kunne det?

Måske vil du skifte design i takt med årstiderne, og derfor skifte mellem hvilke css-filer du inkluderer, måske vil du bruge en masse javascript og inkludere noget jQuery, måske vil du tilføje nogle <link>-tags til billeder, rss-feeds eller andet sjovt – der sker altid noget med en webside.

Og alle disse ændringer giver problemer, for de skal jo gå igen for samtlige sider, men de passer ikke rigtig ind i nogle af de eksisterende includes. Den umiddelbare løsning kunne være at tilføje endnu en include-linje, for eksempel inde i <head>-tag'et. Det virker dog kun indtil der bliver behov for at tilføje en attribut til <body> eller <html>. Så er der igen en masse der skal ændres.

Brug to skabeloner

Den næste løsning er at klippe skabelonen i to stykker – nemlig den del der skal være før sidens indhold, og den del der skal være efter. Og i stedet for at kopiere skabelonen hver gang, så laver du dine sider så de alle sammen starter med at inkludere før-delen umiddelbart før sidens indhold, og slutter med at inkludere efter-delen.

Dine to skabeloner – eller skabelon-dele om du vil – kommer altså til at se sådan ud:

Før indhold:

before.php

<html>
<head><title>sidens navn</title></head>
<body>
<?php include("header.php") ?>
<?php include("menu.php") ?>
<div id="content">

Efter indhold:

after.php

</div>
<?php include("footer.php") ?>
</body>
</html>

og alle dine sider skal så følge det her mønster:

sideeksempel.php

<?php include("before.php") ?>
 … sidens indhold …
<?php include("after.php") ?>

Nu kan du tilføje nye sider uden at skulle bekymre dig om nogensinde at være nødt til at ændre andet end indholdet i dem – og her kunne dagens artikel egentlig slutte.

Men der er nogle ulemper ved denne løsning; først og fremmest er alle siderne nu helt ens, blandt andet har de det samme navn i <title>-tag'et – det ser ikke alene kikset ud, det skader også sidens placering i googles søgeresultater. Så det er stadig en alt for ufleksibel løsning

Brug ikke nogen skabelon

En noget mere kompliceret, men også meget fleksibel løsning, er at lade siderne være helt fri for skabeloner og includes, og i stedet have én central .php-fil der bygger siden op af header-, menu- og footer-komponenter, inkluderer den ønskede side, sørger for at sætte <title>-tag'et og hvad der nu ellers skal til i den fremviste side.

Det ændrer markant ved den måde hjemmesiden skal besøges på – for i stedet for at bede direkte om en side som for eksempel produkter.php, så skal en besøgende bede om denne centrale .php-fil, og på en eller anden måde fortælle hvilken side hun ønsker at se.

Request

Når browseren beder serveren om en side, så sender den en request (anmodning på dansk) – denne request indeholder som regel kun filnavnet på den side der skal hentes, altså forside.php, produkter.php eller lignende, men den kan indeholde meget mere. Efter filnavnet kan der komme en query (forespørgsel på dansk) – og denne query kan indeholde en eller flere navngivne parametre, hver med en eller anden værdi.

En query indledes altid med et spørgsmålstegn (?) for at adskille den fra filnavnet, og hver parameternavn efterfølges af et lighedstegn (=) og parameterens værdi. Hvert parameternavn/værdi-sæt adskilles af et og-tegn (&). Det giver vel næsten sig selv at disse tegn ikke må indgå i filnavne, parameternavne eller værdier. Der er også en masse andre restriktioner, men hvis man kan holde sig til tallene 0-9 og bogstaverne a-z, så er man godt kørende.

Du behøver ikke bekymre dig om spørgsmålstegn, og-tegn og andre symboler, for PHP klarer det meste for dig – alle parametre kan findes i variablen $_REQUEST der er et associativt array, hvor hvert variabelnavn svarer til et parameternavn, og den tilsvarende værdi til parameterens værdi.

Det vil sige at hvis du i din browser indtaster /index.php?page=forside, så kan du i din PHP-kode (i index.php) få variablen $page til at indeholde teksten "forside" med denne ene linje:

$page = $_REQUEST["page"];

Måske kan du allerede nu se hvor det bærer hen – for ved at sammenligne $page med forskellige navne, kan du få index.php til at inkludere den matchende side. Noget i stil med:

if( $page == "forside" ) {
	include("forside.php");
}
if( $page == "kontakt" ) {
	include("kontakt.php");
}

og så videre for alle dine sider.

Det betyder selvfølgelig også at menuen skal ændres så der bliver linket til /index.php?page=forside i stedet for til /forside.php, og til /index.php?page=kontant i stedet for /kontakt.php og så videre.

Og der skal flere ændringer til, for det er mildest talt ikke særlig elegant med sådan en lang liste af if-sætninger, og bestemt heller ikke særlig fleksibelt.

Lister (Arrays)

Før noget andet, er det på sin plads med en smule analysearbejde, så frem med det kvadrerede papir, og lav en oversigt over hvad der egentlig er behov for af informationer om hver enkelt side:

Disse informationer (om en enkelt side) kan samles i et associativt array – altså en variabel der indeholder et antal navngivne variabler, ligesom $_SERVER og $_REQUEST. Det er nemt at lave sine egne associative arrays i PHP, du skriver blot array( ) og inde i parentesen en liste over de navne-værdi-sæt du vil have at array'et skal indeholde. Eksempelvis sådan her for produkter-siden:

$produkter = array( "file" => "produkter.php", "page" => "produkter",
 			"title" => "Produkter", "menu" => "produkter");

Husk at både navne og værdier skal være i anførselstegn. Og mønsteret er altid navn => værdi, hvor hvert navn-værdi-sæt er adskilt af komma. Du kan have lige så mange linjeskift og indrykninger i din array-definition som du har lyst til. Bare husk at afslutte med parentes-slut og semikolon.

Arrays for de andre sider skal indeholde de samme navne, men naturligvis med nogle andre værdier – på den måde får du et antal ensartede arrays. Det er en markant fordel, for så kan du skrive din kode så generel som muligt, så den bare arbejder med sådan et side-array, og ikke bekymrer sig om hvor mange sider der er, og hvilke variabelnavne du vælger til dem.

Faktisk kan du også samle alle disse side-arrays i et overordnet array – en slags "array af arrays". Der er nemlig ikke noget i vejen for at værdien i et array er en variabel, og heller ikke noget i vejen for at den variabel er et andet array. Så du kan samle $forside, $om, $kontakt (forudsat du har lavet dem ligesom $produkter ovenfor) og $produkter på denne måde:

$pages = array( "forside" => $forside, "om" => $om, "produkter" => $produkter, "kontakt => $kontakt);

Der er dog ingen grund til at variablerne i array'et her gentager sidernes navne, og faktisk så kan man godt have arrays hvor variablerne ikke hedder noget. Det her vil virke fint:

$pages = array( $forside, $om, $produkter, $kontakt );

Når variablerne i et array ikke bliver givet nogle navne, så bliver de bare nummereret – startende fra 0. Det vil sige at i stedet for at skrive $pages["forside"] for at få fat i $forside, så skriver du $pages[0] – uden anførselstegn – og i stedet for $pages["om"] så skriver du $pages[1].

Så skrot den gamle index.php og påbegynd en ny fil – en der starter med at oprette en liste over alle dine sider:

index.php

<?php
 $forside = array( "file" => "forside.php", "page" => "forside",
 			"title" => "Min hjemmeside", "menu" => "forsiden" );
 $om_side = array( "file" => "om.php", "page" => "om",
 			"title" => "Om hjemmesiden", "menu" => "om" );
 $produkter = array( "file" => "produkter.php", "page" => "produkter",
 			"title" => "Produkter", "menu" => "produkter" );
 $kontakt = array( "file" => "kontakt.php", "page" => "kontakt",
 			"title" => "Kontakt", "menu" => "kontakt" );
 
 $pages = array( $forside, $om_side, $produkter, $kontakt );

Så langt, så godt. Nu gælder det om at finde den rette side i $pages – altså den hvis ["page"]-variabel svarer til indholdet af page-parameteren. Men der er ingen grund til at have en lang række if-sætninger som i de tidligere eksempler, du kan nemlig udnytte at siderne ligger i et array, og lave en løkke der bladrer igennem dette array.

Løkker

Som du nok kan huske, bruger man funktioner og løkker til at undgå gentagelser i sin kode. En funktion bruges når de samme operationer skal udføres flere gange fra forskellige steder i et program, og en løkke bruges når de samme operationer skal udføres for alle elementer i en liste. For at lave en løkke der gennemgår alle siderne i array'et $pages, skriver du:

foreach( $pages as $a_page ) {
	…
}

Koden i tuborg-parenteserne bliver udført én gang for hver variabel der er i $pages – det vil sige at først bliver variablen $a_page sat til at være den første variabel i $pages (her i dette tilfælde det sammen som $forside), så udføres koden, og derefter sættes $a_page til at være den næste variabel (her $om_side) – på den måde kan du skrive al din kode til variablen $a_page, og være mere eller mindre ligeglad med hvor mange sider der egentlig er i $pages, og hvilke variabelnavne de ellers har haft.

Så du kan udføre testen for om $_REQUEST["page"] svarer til ["page"]-variablen for hver enkelt side i $pages, og så sætte variablen $page til at svare til denne side:

if( $a_page["page"] == $_REQUEST["page"] ) {
	$page = $a_page;
}

Når løkken har fundet den rigtige side, så er der sådan set ingen grund til at den fortsætter – derfor kan du tilføje kommandoen break inde i if-sætningen. Det break'er den omsluttende løkke, så den ikke kører igennem næste variabel.

isset

Der dog også lige den lille detalje at der måske slet ikke er nogen page-parameter. Hvis en besøgende bare indtaster index.php (eller får den leveret af serveren) så vil $_REQUEST["page"] ikke indeholde noget, ja, den vil faktisk slet ikke eksistere. Derfor er programmet nødt til at tjekke om variablen overhovedet findes, før løkken påbegyndes – og hvis den ikke gør, så skal $page sættes til den første side i $pages-array'et.

I PHP bruger man den indbyggede funktion isset( ) til at spørge om en variabel er sat, altså om den eksisterer. Så hvis du pakker din løkke ind i:

if( isset( $_REQUEST["page"] ) ) {
 	… 
}

så er du noget bedre kørende.

I stedet for at lave en ekstra test på om $_REQUEST["page"] ikke eksisterer, så er det enklere blot at sætte $page til standardværdien (altså den første side i $pages) før if-sætningen. Hvis der bliver fundet en ny værdi for $page, så bliver den gamle bare overskrevet, og hvis der ikke bliver fundet nogen, jamen så er der heldigvis en værdi at falde tilbage på.

Hvis man ændrer en variabel inde i en løkke (eller en if-sætning), og også vil bruge den uden for, så er det god stil at erklære den før løkken (eller if-sætningen), altså sætte den til en værdi. Det sikrer også at variablen altid har et indhold, uanset hvad der sker inde i løkken (eller if-sætningen).

I andre programmeringssprog skal man erklære sine variabler før de bruges første gang, men PHP er ikke ret streng med den slags; hvis en variabel sættes til en værdi et eller andet sted i et program, så kan resten af programmet uden videre bruge den variabel. Funktioner kender dog normalt kun til de variabler de bliver givet.

For at finde den aktuelle side – eller forsiden, hvis der ikke er nogen page-parameter – tilføjer du altså disse linjer:

index.php

 $page = $pages[0];
 if( isset( $_REQUEST["page"] ) ) {
 	foreach( $pages as $a_page ) {
		if( $a_page["page"] == $_REQUEST["page"] ) {
			$page = $a_page;
			break;
		}
	}
 }
?>

<title> og indhold

Nu indeholder $page alle de informationer der skal bruges om den aktuelle side, så først og fremmest kan det besværlige <title>-tag blive gjort lidt pænere:

index.php

<html>
<head><title><?php echo $page["title"];?></title></head>
<body>

Og så kan selve sidens indhold også blive inkluderet sammen med header og menu.

index.php

<?php 
 include("header.php");
 include("menu.php");

 echo '<div id="content">';
 include( $page["file"] );
 echo '</div>';
  
 include("footer.php");
?>
</body>
</html>

Og det er sådan set det – nu har du én indgang til at vise alle dine sider, men der mangler lige en lille detalje, menu-komponenten skal også opdateres så den linker til de rigtige sider.

Menu

Nu hvor alle siderne er i en liste, er det jo nærmest oplagt at bruge selvsamme liste til at lave menuen. I den gamle menu-komponent blev menuen opbygget mere eller mindre manuelt, ved at en funktion (menulink) blev kaldt for hver side der skulle være i menuen. Disse funktionskald kan du roligt skrotte nu, men selve funktionen kan med lidt snilde laves om til en løkke der kører gennem alle siderne i $pages, og bygger menuen derfra.

Så hent den gamle menu.php frem, og begynd at ændre i den.

menu.php den gamle udgave med funktionen

<div id="menu">
<?php
 function menulink( $link, $name ) {
	echo "<li ";
 	if( $_SERVER["SCRIPT_NAME"] == $link ) { echo "class='active'"; }
 	echo ">";
 	echo "<a href='$link'>";
 	echo $name;
 	echo "</a></li>";
 }

Start med at lave funktionen om til en løkke der kører gennem alle siderne

menu.php

<div id="menu">
<ul>
<?php
 foreach( $pages as $a_page ) {
	echo "<li ";

For at se om det er den aktuelle side der laves et menupunkt for, er det nok at tjekke om $a_page svarer til den $page der blev fundet tidligere.

	if( $a_page == $page ) { echo "class='active'"; }
	echo ">";

Den næste linje brugte variablen $link til linket til siden. Den variabel findes ikke længere, men du kan nemt konstruere den – linket skal være "index.php?page=" efterfulgt af $a_page["page"], og for at sætte to tekststumper sammen, skriver man dem med et punktum i mellem sig.

Det hedder "at konkatenere" (engelsk: concatenate) tekststumper, når man sætter dem i forlængelse af hinanden. I de fleste andre sprog benytter man et plustegn (+) mellem de tekster der skal konkateneres, men PHP bruger altså punktum. Et plustegn vil altid forsøge at lægge tal sammen – uanset om de tal er i form af en tekst eller en binær værdi.
Det har ingen betydning om der er mellemrum før eller efter et punktum – det er kun teksterne i anførselstegn eller variabler der bliver brugt.

	$link = "index.php?page=" . $a_page["page"];
	echo "<a href='$link'>";

Den næste linje igen brugte variablen $name – den er der ingen grund til at genbruge, du kan bare erstatte den med $a_page["menu"] der indeholder det navn der skal stå i menuen.

Og med afslutning af diverse tags og løkken, så er den nye menu-komponent færdig.

	echo $a_page["menu"];
 	echo "</a></li>";
 }
?>

</ul>
</div>

Nu er det en sand svir at tilføje nye sider – de skal bare skrives i deres egen .php-fil, og så skal der defineres en variabel for den nye sides data i starten af index.php, og den variabel skal tilføjes $pages-array'et. Det kan næsten ikke blive nemmere … ikke før i næste afsnit i hvert fald!

God fornøjelse – og husk endelig at give mig en kommentar eller to med på vejen. Du er også velkommen til at skrive direkte til mitegetcms@peterlind.dk.

For en god ordens skyld er her lige links til de samlede index.php og menu.php.

Tilføj kommentar

www.peterlind.dk

Nyeste blog-indlæg