Check alle échte Black Friday-deals Ook zo moe van nepaanbiedingen? Wij laten alleen échte deals zien

[PHP] Router class opzetten

Pagina: 1
Acties:

  • Vinze
  • Registratie: Augustus 2006
  • Laatst online: 20-11 10:29
Ik ben bezig met het maken van een simpele router voor een framework. Nu heb ik dit wel werkend alleen volgens mij kan het veel effectiever..

Voorbeeldje:
URL: users/view/736
Matched aan: users/view/*

URL: documents/all
Matched aan: document/all

URL: search/products/Asus
Matched aan: search/*/*

Nu los ik dit momenteel op door eerst door de routes te lopen, iedere route exploden op de / en vervolgens ieder deel van de route te matchen aan de huidige URL. Wanneer alle onderdelen van een route matchen aan de URL stopt hij en wordt een functie uitgevoerd.

Zoals te zien vereist dit nogal wat stappen en een hele for/if-else structuur en volgens mij moet dit veel netter kunnen. Nu ben ik echt een leek op het gebied van regex, maar het lijkt mij ideaal om door de routes te lopen, en door een preg_match meteen de match eruit te pikken.

Iemand die me hierbij kan helpen, of een in ieder geval een duwtje in de goede richting kan geven?

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 20:57

Matis

Rubber Rocket

Vinze schreef op vrijdag 22 maart 2013 @ 19:49:
Zoals te zien vereist dit nogal wat stappen en een hele for/if-else structuur en volgens mij moet dit veel netter kunnen. Nu ben ik echt een leek op het gebied van regex, maar het lijkt mij ideaal om door de routes te lopen, en door een preg_match meteen de match eruit te pikken.
Some people, when confronted with a problem, think
“I know, I'll use regular expressions.” Now they have two problems.
Even zonder dollen. Kijk eens hoe gerenommeerde MVC frameworks dat doen.

Waar het in feite op neer komt is dat ze in een map op zoek gaan naar een bestand genaamd users.php, documents.php of search.php en die dan inladen en de desbetreffende functie aanroepen (view, all en __construct respectievelijk).
Daarin komt dan de controller code welke een model en/of een view uitpoept of wat jij dan ook wilt.

Edit; CodeIgniter lost dat zo op: https://github.com/EllisL...op/system/core/Router.php

[ Voor 26% gewijzigd door Matis op 22-03-2013 20:12 ]

If money talks then I'm a mime
If time is money then I'm out of time


  • Vinze
  • Registratie: Augustus 2006
  • Laatst online: 20-11 10:29
Ja ik snap hoe het werkt om de juiste controller en functie aan te roepen, ik heb echter een array met routes. Bij iedere route zit al een functie welke aangeroepen moet worden. Ik hoef dus alleen te checken of een van die routes matched aan de URL, en dan weet ik ook meteen welke functie uitgevoerd moet worden. Het probleem ligt hem erin dat ik ook wildcards (*) wil gebruiken. Anders kon ik gewoon checken of de route en de URL exact gelijk zijn..

  • Matis
  • Registratie: Januari 2007
  • Laatst online: 20:57

Matis

Rubber Rocket

Ik snap er werkelijk waar geen reet van wat je precies bedoelt. Misschien brengt een stukje (voorbeeld)code verheldering.
Ik mag op zijn minst hopen dat je in de "route array" ieder segment van de URL los hebt opgeslagen.

If money talks then I'm a mime
If time is money then I'm out of time


  • Vinze
  • Registratie: Augustus 2006
  • Laatst online: 20-11 10:29
Ik zal straks even een voorbeeldje posten, zit nu op m'n tablet en kan zo snel niet bij de code. Was al bang dat het verhaal niet duidelijk zou zijn :P

  • Vinze
  • Registratie: Augustus 2006
  • Laatst online: 20-11 10:29
Hierbij even een stukje voorbeeld code, op de volgende manier voeg ik routes toe:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// In mijn app.php doe ik het volgende:
$app->route('users', function() use ($app) {
    // Uit te voeren code
});
$app->route('users/view/*', function($id) use ($app) {
    // Uit te voeren code
});
$app->route('users/edit/*', function($id) use ($app) {
    // Uit te voeren code
});
$app->route('search/*/*', function($category, $keyword) use ($app) {
    // Uit te voeren code
});

// In de router verzamel ik alle routes:
public function route($route, $func) {
    $this->routes[$route] = $func;
}


Vervolgens wil ik gaan kijken welke route matched aan de huidige url, dit doe ik als volgt:
PHP:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public function getMatched() {

    $this->matched = null;
    $this->url = explode('/', $_GET['request']);

    foreach ($this->routes as $route => $func) {
        $parts = explode('/', $route);
        $vars = array();
        $match = 0;

        if (count($parts) == count($this->url)) {
            for ($i = 0; $i < count($parts); $i++) {
                if ($parts[$i] == $this->url[$i]) {
                    $match += 1;
                }
                elseif ($parts[$i] == '*') {
                    $vars[] = $this->url[$i];
                    $match += 1;
                }
            }
            if ($match == count($this->url)) {
                $this->matched['func'] = $func;
                $this->matched['vars'] = $vars;
                break;
            }
        }
    }

    return $this->matched;
    
}


En nu is mijn vraag of ik die for loop er ook uit zou kunnen halen, en vervangen door een regex. Dat is een stuk korter qua code, en volgens mij ook een veel nettere oplossing. Ik kan er alleen niet achter komen hoe ik dat moet doen, en of het uberhaupt mogelijk is. Iemand die me richting de oplossing kan wijzen?

  • Barryvdh
  • Registratie: Juni 2003
  • Laatst online: 21:50
Je kan kijken hoe andere ontwikkelaars het doen (of gewoon een standaard library gebruiken natuurlijk)
https://packagist.org/search/?q=router

  • wackmaniac
  • Registratie: Februari 2004
  • Laatst online: 20-11 09:10
Hoewel ik misschien een digitaal pak slaag riskeer van Matis; KISS. Wat ik zelf gebruik is een standaard reguliere expressie en de matches geef ik door als parameters. Voordeel is dat je geen aparte syntax hebt, zoals bij Zend) en dat je een basale invoercontrole kan uitvoeren. Waar zou je het moeilijker maken dan het is:

PHP:
1
2
3
$app->route('|^users/view/(\d+)/?$|s', function($id) use ($app) { 
    // Uit te voeren code 
});

[ Voor 0% gewijzigd door wackmaniac op 23-03-2013 12:17 . Reden: typo ]

Read the code, write the code, be the code!


Verwijderd

De routing zoals dat in het Yii framework gebeurd vind ik ook wel een nette oplossing, deze gebruikt een RouteRule class die eigenlijk zijn eigen logica heeft om een route te parsen. De 'rules' worden door de router doorlopen en bij een match wordt de loop onderbroken, soort van Chain of Command pattern.

Dit zorgt ervoor dat je in principe meerdere soorten logica qua routes kunt hanteren en ook makkelijk kunt switchen door middel van subclassing. Ook kun je zo een makkelijke fallback routing inbouwen die de standaard routing <module>/<controller>/<action> hanteert .

edit:

wat me trouwens wel dwars zit bij het gebruik van routes als /search/*/* is dat je weinig controle hebt over het type van de het argument en ook geen enkele naam aan je arguments kunt koppelen, een simpele oplossing zou zijn om bijvoorbeeld een route te hebben als :

/search/<product:d>/<brand:s> ... waarbij <product:d> het argument met naam 'product' is en de 'd' aangeeft dat het een decimaal/getal moet zijn ... deze route zal dan vervolgens herschreven moeten worden naar de regular expression :

/^search\/(d+)\/(\w+)\//u

[ Voor 32% gewijzigd door Verwijderd op 23-03-2013 14:01 ]


  • Vinze
  • Registratie: Augustus 2006
  • Laatst online: 20-11 10:29
Bedankt voor de reacties! Ik heb recent ook Silex en Slim gevonden, beide zijn micro frameworks gebaseerd op Sinatra. Ga even door hun code pluizen om uit te zoeken hoe zij dat oplossen.

  • wackmaniac
  • Registratie: Februari 2004
  • Laatst online: 20-11 09:10
Verwijderd schreef op zaterdag 23 maart 2013 @ 13:02:
De routing zoals dat in het Yii framework gebeurd vind ik ook wel een nette oplossing, deze gebruikt een RouteRule class die eigenlijk zijn eigen logica heeft om een route te parsen. De 'rules' worden door de router doorlopen en bij een match wordt de loop onderbroken, soort van Chain of Command pattern.

Dit zorgt ervoor dat je in principe meerdere soorten logica qua routes kunt hanteren en ook makkelijk kunt switchen door middel van subclassing. Ook kun je zo een makkelijke fallback routing inbouwen die de standaard routing <module>/<controller>/<action>
Wat ik dan vaak zie gebeuren zijn twee dingen: Een echte enthousiasteling doe voor elke route een subklasse maakt (pfffff) of voor elke regel wordt een regex-achtige regel gebruikt.
Verwijderd schreef op zaterdag 23 maart 2013 @ 13:02:wat me trouwens wel dwars zit bij het gebruik van routes als /search/*/* is dat je weinig controle hebt over het type van de het argument en ook geen enkele naam aan je arguments kunt koppelen, een simpele oplossing zou zijn om bijvoorbeeld een route te hebben als :

/search/<product:d>/<brand:s> ... waarbij <product:d> het argument met naam 'product' is en de 'd' aangeeft dat het een decimaal/getal moet zijn ... deze route zal dan vervolgens herschreven moeten worden naar de regular expression :

/^search\/(d+)\/(\w+)\//u
Je kan in de regex een match een naam geven, dan kan je ook keurig met namen werken :)

Read the code, write the code, be the code!


Verwijderd

@Wackmaniac

Het is natuurlijk niet de bedoeling om voor iedere route een subclass te maken, dat zou wel erg lomp zijn .. het idee is meer om eventueel meerdere methodes te hebben om een route te parsen of een url te genereren vanuit een route.

Vooral handig als je bijvoorbeeld wil dat je 'page module' dynamische urls genereert met pagina titels uit de database.
Pagina: 1