Tutorials

Een simpele URL rewriter maken

Een simpele, doch effectieve manier om URL`s te herschrijven. Een klein beetje HTACCESS en een simpele URL class. Let wel: de klasse die ik gemaakt heb, is niet verplicht. Het kan ook procedureel. Maar om toch OO te leren aan de mensen die hier belang bij hebben, heb ik er een klasse van gemaakt.

Pagina 1

HTACCES gebruiken

Om deze URL rewriter te kunnen gebruiken, is een klein stukje Apache nodig. Dit betekent heel simpel dat alle URI requests ( alle requests vanaf je root ) door verwezen kunnen worden.

Om te beginnen zullen we moeten zeggen dat de rewriteEngine aan gezet moet worden, oftewel:


RewriteEngine On


Vervolgens willen we alle URI requests verwerken MITS het niet om een bestand ( .jpg, .css etc ) of map gaat.

We geven dus condities op waarin dit mee gegeven kan worden. Zo`n conditie ziet er als volgt uit:


RewriteCond %{REQUEST_FILENAME} -s [OR]
RewriteCond %{REQUEST_FILENAME} -l [OR]
RewriteCond %{REQUEST_FILENAME} -d [OR]
RewriteCond %{REQUEST_FILENAME} -f


Wat hier letterlijk staat is:
conditie (gaat om) {vraag naar bestand} -(van het type)

De REQUEST_FILENAME is de dispatcher van Apache. Mocht de URI_REQUEST een bestand ( f ) zijn, wordt er niet aan de voorwaarde voldaan. Een bestand zou niet meer toegankelijk zijn nu. Dit lossen we op door er een negatieve van te maken ( - ). Dus -f is geen file. alleen f zou wel een file zijn.

Als het dus niet om een -s of een -l of een -d of -f gaat, kunnen we een regel toepassen.

Een regel in ons geval zal iets simpels zijn als:


RewriteRule ^.*$ - [NC,L]
RewriteRule ^.*$ index.php [NC,L]


We geven hier 2 regels op. RewriteRule spreekt voor zich. Vervolgens zeggen we dat als we een URI REQUEST zien, het moet beginnen met .*. Het dakje (^) betekent dat de voorwaarde wordt toegepast aan het begin van de URI REQUEST. Het $ betekent dat het om het einde moet gaan.

Laten we voor ons gemak eens uitgaan van het volgende:

1. www.domein.nl/
2. www.domein.nl/test

Als we de HTACCESS zouden gebruiken, zou in het eerste geval niets gebeuren. Dit omdat er geen URI_REQUEST plaatsvindt en we dus gewoon op onze root zitten, echter bij de 2e krijgen we wel een request. Namelijk: test.

Als we nu alle voorwaarden doorlopen ( is het niet een bestand en ook geen map etc ) dan rerouten we de request naar onze root. Oftewel 1.

We zitten nu dus weer op 1. Maar, onze HTACCES is nog niet klaar, want vervolgens stellen we dat we ook naar onze index.php moeten.

Kort gezegd betekent dit hele verhaal dat we index.php weg filteren, maar alle requests ( zolang het niet om een f,s,d en l gaat) rerouten naar de root. Dus eigenlijk de index.php, maar die filteren we lekker weg ;)



Pagina 2

De index.php maken

Nu we alle URI Requests terug linken naar onze index.php op de root van onze server, kunnen we met PHP alle URI REQUESTS gaan afhandelen.

Maar om te beginnen zullen we eerst maar eens een index.php aan moeten maken lijkt me ;)

index.php

<?php

require 'library/class.UrlDispatcher.php';

$url = new UrlDispatcher();

?>


Dit is praktisch alles wat we nodig hebben straks.

Nu we dit hebben, kunnen de klasse voor de URL dispatcher gaan schrijven!
Pagina 3

De URL class schrijven

Om een klasse te schrijven in PHP dien je rekening te houden met enkele basis regels. Ik ga ze niet allemaal toepassen, maar waar nodig zal ik zeker commentaar geven.

Goed, we verwijzen in onze index.php naar een map genaamd lib waarin onze class.UrlDispatcher.php moet staan.

Trouwens, een gouden regel binnen het OO programmeren is dat een klasse altijd met Hoofdletters begint, om vervolgens over te gaan op CamelCase. Dit betekent in ons geval dat het bestandsnaam dus UrlDispatcher moet heten. Binnen PHP, maar ook zeker in andere talen wordt er vaak class aan toegevoegd. Om zo onderscheid te maken dat het hier om een klasse gaat.

Laten we eens beginnen deze te schrijven:

class.UrlDispatcher.php

<?php

// een klasse in PHP begint altijd met het gereserveerde 
// woord: class, gevolgd door de naam van de klasse. Een 
// classname zou eigenlijk altijd hetzelfde moeten zijn
// als de naam van het bestand. In ons geval dus 
// UrlDispatcher

class UrlDispatcher 
{
  // hier komt de body van onze klasse. De body van een 
  // klasse bestaat over het algemeen uit een constructor 
// en "methods", oftwel functions in PHP :)


}
?>


We hebben nu een klasse gemaakt, genaamd UrlDispatcher.

Om er voor te zorgen dat wanneer er een instantie ( object ) aangemaakt wordt, er alvast code wordt uitgevoerd, maken we een constructor. Deze functie is uniek, omdat deze ( zonder anders aangegeven ) standaard wordt uitgevoerd.

Het unieke aan een constructor in PHP is dat je deze standaard de naam geeft van de klasse. Dit is in veel talen zo, dus ook in PHP. Onze constructor heet dus ook
UrlDispatcher.

Daarnaast gaan we ook een paar velden toevoegen aan onze klasse. Een veld in een klasse betekent dat we voor elk object variabelen aan maken. Deze variabelen zijn echter ten allen tijde te benaderen ( kom ik later op terug ) en betekenen dat zolang het object bestaat, alle "methods" oftewel functions binnen de klasse gebruik kunnen maken van deze variabelen.

In ons geval zou de constructor zoiets zijn als:

<?php

class UrlDispatcher 
{
  private $Uri;
  private $nodes;
  private $firstNode;
  private $lastNode;
  private $root;

public function UrlDispatcher ()
{
  // body van de constructor
}

}
?>


Als je goed kijkt, zie je dat er voor function nog public staat. De public is een "visibility modifier". Dit wil zeggen dat de constructor gewoon aangeroepen kan worden. Als je een constructor bijvoorbeeld private maakt, kun je geen object aanmaken van die klasse. Dit kan handig zijn, maar daar ga ik hier niet verder op in. Ik weet niet eens of een private constructor wel werkt in PHP... Anyone?

In ieder geval, als je dus oplet, zie je dat er bovenin nu 5 private velden bestaan. Private betekent voor een veld dat het ALLEEN benaderd kan worden binnen de klasse zelf. Of eigenlijk nog preciezer, binnen het object.

Om de waardes op te halen, zul je dus getters en setters moeten gebruiken. Oftewel publieke "methods" ( functions ) om deze waarden extern te gebruiken/wijzigen. Getters betekent ophalen van waardes, setters het wijzigen van waardes.

Laten we dit eens uittesten:

<?php

public function setNodes ($array)
{
    $this->nodes = $array;
}

?>


Zoals je ziet gebruik je hier $this. $this betekent dat je refereert naar het huidige object. Als er dus een instantie oftewel een object wordt aangemaakt, kun je binnen dat object naar jezelf verwijzen. Je kunt op deze manier methods alsook instantie variabelen aanroepen. Een method ( oftewel functie.. ) roep je aan door: $this->methodName()

Om het niet al te lang te maken, ga ik verder op de volgende pagina :)
Pagina 4

De URL class af maken

Om het niet al te langdradig te maken, ga ik niet alle code meteen mee geven.

In ieder geval hebben we nu dus 5 instantie variabelen:
uri, nodes, firstNode, lastNode, root.

Om hier getters en setters voor te maken hou je rekening met de naamgeving. Een getter begint met "get" en een setter met , je raadt het al, een "set".

Als we dus een getter voor nodes maken, doen we dus:

<?php

public function getNodes ()
{
  return $this->nodes;
}

?>


Als we dus een setter maken, doen we:

<?php

public function setNodes ($array)
{
  //$this->nodes is de instantie variabele
  // $array is een lokale variabele, welke we aan 
  // $this->nodes binden

  $this->nodes = $array;
}
?>


Als we dit voor alle 5 de velden doen, krijg je dus een lange lap met 10 methods.

Nu we alle rompslomp geregeld hebben, kunnen we de daadwerkelijke logica opzetten. Om te beginnen hebben we een URI REQUEST nodig. Althans, een string die deze terug geeft. Een URI REQUEST kan namelijk leeg zijn, remember ;)

Om hier op verder te gaan, we zullen eerst het volledige adres moeten verkrijgen. Dit kunnen we opvragen met $_SERVER['PHP_SELF']. Dit geeft in ons eerdere voorbeeld zoiets als:

1. http://www.domein.nl/index.php

Omdat we die index.php niet leuk vinden, en we deze al wegfilteren door middel van de HTACCESS, gaan we dat hier ook doen.


<?php

$index = strrpos( $_SERVER['PHP_SELF'] , "index.php" );
$root  = substr( $_SERVER['PHP_SELF'] , -0 , $index );

?>


De $index geeft nu een positie terug vanaf waar gerekend kan worden.

$root gaat nu dus de PHP_SELF bij langs, en gaat de string opknippen vanaf het einde, tot en met de plek van wat $index geeft.

Dus:
1. http://www.domein.nl/index.php

$index = 21 ( tel maar na )
$root is dus nu de complete string vanaf het einde tot 21.

In dit geval is het dus helemaal leeg.
Maar als we nu een URL zouden opgeven zoals bijvoorbeeld deze:

2. http://www.domein.nl/artikel/nummer

Dan is onze $root dus gelijk aan : "artikel/nummer".

Zoals je kunt zien gebruik in het voorbeeld de / als scheiding. Dit kun je gewoon doen, omdat we in de .HTACCESS controleren of het hier niet om een bestand of map etc gaat, dus kunnen we de / gebruiken om onderscheid te maken tussen verschillende pagina`s.

Je kunt ook een ander teken gebruik als je wil. Bijvoorbeeld een & of een ?. Zolang het een geldig teken is, kun je het als seperator gebruiken!

Ik gebruik dus de / omdat deze gewoon het duidelijkste is.

Vervolgens gaan de $root gebruiken om te filteren met de $_SERVER['REQUEST_URI']. De REQUEST_URI methode geeft alles na de root weer. dus in ons geval bijvoorbeeld: index.php of /artikel/nummer


<?php

$argument = str_replace( $root , "" , $_SERVER['REQUEST_URI'] );

?>


Vervolgens halen we onze laatste / weg, voor de netheid:


<?php

$uriString = trim( $argument , "/" );

?>


Nu hebben we een hele string welke overeen kan komen met bijvoorbeeld: nieuws/categorie/artikel

Alleen de string is niet genoeg, we willen hier een array van maken!

Een array maken van een string kan op meerdere manieren, maar omdat we een standaard seperator gebruiken ( en nogmaals, je mag hier zelf ook wat invullen ) gaan we de string opknippen:


<?php

$nodes = explode("/", $uriString);

?>


Dat is alles eigenlijk :D

Maar omdat we met een klasse werken, zouden we eigenlijk onze instantie variabelen moeten gebruiken.

Omdat een constructor altijd aangeroepen wordt als een object geinstantieerd ( aangemaakt wordt ), gaan we nu een method schrijven welke aangeroepen zal worden in de constructor.


<?php

    public function UrlDispatcher ()
    {
        // body van de constructor
        $index      = strrpos( $_SERVER['PHP_SELF'] , "index.php");
        $root       = substr( $_SERVER['PHP_SELF'] , -0 , $index );

        // Stel de instantie variabele van root in
        $this->setRoot($root);

        // Vervang de root met de huidige URI
        $argument   = str_replace( $this->getRoot() , "" ,$_SERVER['REQUEST_URI'] );

        // Maak een URI string, en strip de laatste /
        $this->setUri(trim($argument, "/"));

        // Zorg ervoor dat alle nodes in een array komen
        // de String die we gebruiken halen we op door de method getUri()
        $this->setNodes(explode("/", $this->getUri()));

        // Nu alles verder afgehandeld is, kunnen we de eerste en laatste node ophalen
        $this->createNodes();

    }

?>


De mensen die nog opletten na deze lange tutorial merken dat er een method aangeroepen wordt genaamd $this->createNodes();

Deze methode set het eerste en laatste node.
Hieronder de code:


<?php

    public function createNodes ()
    {
        // sla alle nodes op in een lokale variabele
        $nodes = $this->getNodes();

        // Controleer of $nodes een eerste rij heeft ( een array begint met 0 )
        if(isset($nodes[0]))
        {
            // Deze is nog vrij makkelijk, als er een rij is op 0, is dat onze eerste node
            $this->setFirstNode($nodes[0]);
        }

        // Gooi de array over de kop, eerste wordt laatste en vice versa
        $reversedNodes = array_reverse($nodes);

        // Lijkt raar, maar nu hebben we ook de laatste node.
        // Als er maar 1 URI string is opgegeven, krijg je dus hetzelfde resultaat als de eerste
        $this->setLastNode($reversedNodes[0]);
        
    }

?>


De uitleg staat in de code. Ik gebruik de array om door heen te lussen. Mocht je nou bijvoorbeeld veel nodes willen gebruiken, kun je ook meerdere instantie variabelen aan maken. Dit is OPTIONEEL, omdat je dit vaak niet nodig zult hebben.

En hou er rekening mee dat wanneer je dit dynamisch doet, je alsnog getters moet hebben om de waardes op te halen!


<?php

class UrlDispatcher
{
    // optioneel
    private $node1;
    private $node2;
    private $node3;
    private $node4;
    private $node5;

    /**
     * Optionele method om gewoon alle nodes te nummeren en in de instantie variabelen te zetten
     */
    public function createNummericNodes ()
    {
        // sla alle nodes op in een lokale variabele
        $nodes = $this->getNodes();

        // Als die geset is, en $nodes[0] is aanwezig
        if(isset($nodes[0]))
        {
            // Lokale variabelen 
            $string = "node";
            $counter = 1;
            
            foreach($nodes as $node)
            {
                // Creer de naam voor de instantie variabele door "node" en het huidige nummer aan elkaar
                // te plakken. Deze kun je vervolgens door $$ te gebruiken, als nieuwe naam voor je
                // variabele opgeven

                $tmp_name = $string.$counter;
                $this->$$tmp_name = $node;

                // Werk de counter bij, tel er 1 bij op
                $counter++;
            }
        }
    }
?>


Pagina 5

Ter afsluiting de code

De index.php:

<?php

require 'lib/class.UrlDispatcher.php';

$url = new UrlDispatcher();

echo '<p>De eerste node is: '.$url->getFirstNode().'</p>';
echo '<p>De laatste node is: '.$url->getLastNode().'</p>';

$counter = 1;
foreach($url->getNodes() as $node)
{
    echo '<p>Node nummer:'.$counter.' met waarde: '.$node.'</p>';
    $counter++;
}

?>


De klasse UrlDispatcher ( inclusief de optionele parameters. Zie ook dat ik de dynamische nodes method NIET aanroep. Dit kun je doen door $this->createNummericNodes();

class.UrlDispatcher.php

<?php

class UrlDispatcher
{
    private $uri;
    private $nodes;
    private $firstNode;
    private $lastNode;
    private $root;

    // optioneel
    private $node1;
    private $node2;
    private $node3;
    private $node4;
    private $node5;


    public function UrlDispatcher ()
    {
        // body van de constructor
        $index      = strrpos( $_SERVER['PHP_SELF'] , "index.php");
        $root       = substr( $_SERVER['PHP_SELF'] , -0 , $index );

        // Stel de instantie variabele van root in
        $this->setRoot($root);

        // Vervang de root met de huidige URI
        $argument   = str_replace( $this->getRoot() , "" ,$_SERVER['REQUEST_URI'] );

        // Maak een URI string, en strip de laatste /
        $this->setUri(trim($argument, "/"));

        // Zorg ervoor dat alle nodes in een array komen
        // de String die we gebruiken halen we op door de method getUri()
        $this->setNodes(explode("/", $this->getUri()));

        // Nu alles verder afgehandeld is, kunnen we de eerste en laatste node ophalen
        $this->createNodes();

        // ######## OPTIONEEL ##########
        // $this->createNummericNodes();

    }

    /**
     * Vind de eerste en laatste URI
     */
    public function createNodes ()
    {
        // sla alle nodes op in een lokale variabele
        $nodes = $this->getNodes();

        // Controleer of $nodes een eerste rij heeft ( een array begint met 0 )
        if(isset($nodes[0]))
        {
            // Deze is nog vrij makkelijk, als er een rij is op 0, is dat onze eerste node
            $this->setFirstNode($nodes[0]);
        }

        // Gooi de array over de kop, eerste wordt laatste en vice versa
        $reversedNodes = array_reverse($nodes);

        // Lijkt raar, maar nu hebben we ook de laatste node.
        // Als er maar 1 URI string is opgegeven, krijg je dus hetzelfde resultaat als de eerste
        $this->setLastNode($reversedNodes[0]);
        
    }

    /**
     * Optionele method om gewoon alle nodes te nummeren en in de instantie variabelen te zetten
     */
    public function createNummericNodes ()
    {
        // sla alle nodes op in een lokale variabele
        $nodes = $this->getNodes();

        // Als die geset is, en $nodes[0] is aanwezig
        if(isset($nodes[0]))
        {
            // Lokale variabelen 
            $string = "node";
            $counter = 1;
            
            foreach($nodes as $node)
            {
                // Creer de naam voor de instantie variabele door "node" en het huidige nummer aan elkaar
                // te plakken. Deze kun je vervolgens door $$ te gebruiken, als nieuwe naam voor je
                // variabele opgeven

                $tmp_name = $string.$counter;
                $this->$tmp_name = $node;

                // Werk de counter bij, tel er 1 bij op
                $counter++;
            }
        }
    }

    /**
     * Haal de huidige URI op
     * @return String complete URI string
     */
    public function getUri ()
    {
        return $this->uri;
    }

    /**
     * Haal alle nodes op
     * @return Array Alle nodes
     */
    public function getNodes ()
    {
        return $this->nodes;
    }

    /**
     * Haal de eerste node op
     * @return String eerste node
     */
    public function getFirstNode ()
    {
        return $this->firstNode;
    }

    /**
     * Haal de laatste node op
     * @return String laatste node
     */

    public function getLastNode ()
    {
        return $this->lastNode;
    }

    /**
     * Haal de root van de server op
     * @return String root ( de plek waarvandaan je werkt )
     */

    public function getRoot ()
    {
        return $this->root;
    }
    
    /**
     * Stel de huidige URI in
     * @param String de complete URI string
     */
    private function setUri ($uri)
    {
        $this->uri = $uri;
    }

    /**
     * Sla elk segment van de URI op in een array
     * @param Array array met alle nodes
     */
    private function setNodes ($array)
    {
        $this->nodes = $array;
    }

    /**
     * Stel de eerste URI node in
     * @param String eerste node
     */
    private function setFirstNode ($node)
    {
        $this->firstNode = $node;
    }

    /**
     * Stel de laatste URI node in
     * @param String laatste node
     */
    private function setLastNode ($node)
    {
        $this->lastNode = $node;
    }

    /**
     * Geef de root van de server op
     * @param String ( welke blanco moet worden, tenzij het om een map gaat
     */
    private function setRoot ($root)
    {
        $this->root = $root;
    }

}

?>

Reacties

0
Nog geen reacties.