router
Ik ga binnenkort beginnen met misschien wel het belangrijkste onderdeel van mijn CMS, namelijk de router.
De route vertaalt een route naar een controller (class) en een action.
(Voor de mensen die niet precies weten wat ik bedoel met een route, een route is het deel dat achter de domein root staat. In www.mijnsite.nl/berichten/toon/1 is 'berichten/toon/1' de route.
Graag wil ik jullie advies/ tips over hoe ik het beste mijn routes kan opbouwen.
Grofweg zijn er 2 mogelijkheden:
1) iedere route heeft een vaste structuur, waarbij deel 1 bijvoorbeeld de controller is en deel 2 de actie. Bijvoorbeeld: www.mijnsite.nl/berichten/toon/1 zou de controller (class) 'berichten' aanroepen en de action (functie) 'toon' en parameter 1.
2) alles is mogelijk, een route heeft geen vaste structuur. Bijvoorbeeld: www.mijnsite.nl/een/hele/ingewikkelde/route/toont/nummer/1/bericht
MIJN PERSOONLIJKE MENING:
- optie 1 is makkelijker te maken, maar minder flexibel
- optie 2 is (veel?) moeilijker te maken, maar je kunt er veel meer mee.
Ik neig er dus naar om voor de 2e optie te kiezen. Maar ik heb geen idee wat ik me op de hals haal. Is zoiets moeilijk om te maken? En is het niet een al te zwaar proces om de juiste controller en action te achterhalen wat ten nadele zal zijn van de performance van de website?
Ik hoop dat iemand (uit ervaring wellicht) advies kan geven. Ook zou ik enorm blij zijn als iemand toevallig een codevoorbeeld / link heeft van optie 2.
Alvast hartelijk dank!
Mijn codevoorbeeld:
- Route object
- De FrontController waarin bij de dispatch method wordt gekeken welke route matched
- Routes object (deze wordt gebruikt door de frontcontroller)
- Een voorbeeld van mijn routes.ini files
Edit:
Of een voorbeeld uit het framework wat Pim ooit eens heeft gemaakt: https://github.com/drumstok/Small-but-Beautiful/blob/master/SBB.php#L326
Gewijzigd op 20/03/2012 15:21:47 door Wouter J
Hmmm.... ik wacht nog even wat meer reacties af.
Als ik zelf iets dergelijks zou willen maken, zou ik het denk ik als volgt doen:
Je hebt een node-structuur, zoals een bestandsmap, zeg: / of /blog
Hierbinnen kan je gewoon pagina's plaatsen, zoals /blog/about
Je kan ook met 'engines' werken, die zelf een dynamische url mappen naar dynamische pagina's, zoals: /:year/:month/:slug (a la wordpress).
Deze engines kan je dan in de node structuur plaatsen, waardoor je krijgt:
/blog/:year/:month/:slug
Hierdoor heb je een mooi modulair systeem: de blog-engine is onafhankelijk van de rest, maar kunnen de engines zelf wel volledig flexibel zijn.
Je kan dan ook nog aparte elementen in een engine (zeg de frontpage van je blog-engine) aparte paden geven, bijv. /. Zo kan je de frontpage van je blog als frontpage van je site nemen.
Misschien wat lastig te implementeren (denk bijv. aan 2 blogs op 2 locaties), maar wel leuk en ik denk dat het wel te doen is.
Succes ;)
Toevoeging op 20/03/2012 17:51:44:
Je moet dan ook proberen de url-mapping enigszins overeen te laten komen met de menu-structuur, dat is misschien wel wat ingewikkeld, al kan je natuurlijk ook die systemen los van elkaar houden.
Hou er trouwens rekening mee dat je naast een url-matcher ook een url-generator moet maken, begin daar niet achteraf aan.
Toevoeging op 20/03/2012 17:54:54:
Het is dan natuurlijk helemaal cool wanneer je de node structuur die aan het hoofd van je routing staat ook de structuur geeft van een engine, dan heb je een kleine core en veel code in plugins, wat sowieso leuk is :)
Pim, ik waardeer je enthousiasme maar ik als niet-(opgeleid)programmeur snap maar weinig van wat je zegt. Node structuur, enginee... pfff....
Wat ik feitelijk gewoon wil is een action en controller aan een route kunnen koppelen. Stel je hebt een webshop dan zouden dit wat routes kunnen zijn:
- /winkelmandje (controller: shoppingbasket, action index)
-/klant/particulier/inschrijven (controller: register action: consumer)
Die routes wil ik koppelen aan een action en controller. Hoe precies weet ik nog niet, maar bijvoorbeeld:
Code (php)
So far, so good...
Maar wat ik dus wil weten is... is hoe je deze url:
www.mijnwebshopje.nl/winkelmandje
vertaalt naar de controller 'shoppingbasket' en de action 'index'.
Hoe maak je zo'n router? En zoals ik in de beginpost al zei, je kunt een eenvoudige router maken waarbij het 1e deel van je route overenkomt met de controller en het 2e deel met de action. Maar ik wil het graag wat flexibeler... mits dit niet al te ingewikkeld is...
Eerst doe je het heel simpel en maak je niks dynamisch:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?php
// routes.php
$routes = array();
$routes[] = array('route' => 'winkelmandje', 'controller' => 'shoppingbasket', 'action' => 'index');
$routes[] = array('route' => 'klant/particulier/inschrijven', 'controller' => 'register', 'action' => 'consumer');
// front controller
foreach( $routes as $route )
{
if( $route['route'] == 'winkelmandje' )
{
echo ucfirst($route['controller']).'::'.strtolower($route['action']).'()';
break;
}
}
?>
// routes.php
$routes = array();
$routes[] = array('route' => 'winkelmandje', 'controller' => 'shoppingbasket', 'action' => 'index');
$routes[] = array('route' => 'klant/particulier/inschrijven', 'controller' => 'register', 'action' => 'consumer');
// front controller
foreach( $routes as $route )
{
if( $route['route'] == 'winkelmandje' )
{
echo ucfirst($route['controller']).'::'.strtolower($route['action']).'()';
break;
}
}
?>
Vervolgens ga je het uitbreiden en voeg je er een dynamische url aan toe, ong. zoiets:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?php
// routes.php
$routes = array();
$routes[] = array('route' => 'winkelmandje', 'controller' => 'shoppingbasket', 'action' => 'index');
$routes[] = array('route' => 'klant/particulier/inschrijven', 'controller' => 'register', 'action' => 'consumer');
// front controller
$url = $_SERVER['REQUEST_URI'];
$url = end(preg_split('/ozzie-router\.php\//', $url));
foreach( $routes as $route )
{
if( $route['route'] == $url )
{
// i.p.v. een echo moet je natuurlijk wat doen met de controller en action gegevens
echo ucfirst($route['controller']).'::'.strtolower($route['action']).'()';
break;
}
}
?>
// routes.php
$routes = array();
$routes[] = array('route' => 'winkelmandje', 'controller' => 'shoppingbasket', 'action' => 'index');
$routes[] = array('route' => 'klant/particulier/inschrijven', 'controller' => 'register', 'action' => 'consumer');
// front controller
$url = $_SERVER['REQUEST_URI'];
$url = end(preg_split('/ozzie-router\.php\//', $url));
foreach( $routes as $route )
{
if( $route['route'] == $url )
{
// i.p.v. een echo moet je natuurlijk wat doen met de controller en action gegevens
echo ucfirst($route['controller']).'::'.strtolower($route['action']).'()';
break;
}
}
?>
Dit is nog relatief simpel. Nu hoef je alleen nog het probleem van parameters die in de url zitten en in de action parameters thuis horen voor elkaar te krijgen en dan zul je REGEX nodig hebben.
Doe het wel zo dat je in de routes.php moet aangeven waar je de parameters wilt, en misschien zelfs wel met voorwaardes of het cijfers of letters moet bevatten.
Dankzij jouw codevoorbeeldje wordt het overigens wel wat duidelijker :) Ik had het idee dat ik de complete route zou moeten opsplitsen in deeltjes en die deeltjes dan met elkaar zou moeten vergelijken, maar nu ik het zo zie is dat wellicht helemaal niet nodig, maar moet ik inderdaad gaan preg_matchen... waar ik waars. tzt wel weer wat hulp bij nodig heb :)
P.S. ligt het aan mij of ontvang ik ineens geen mailtjes meer als er iemand gereageerd heeft in een van mijn topics?
Quote:
P.S. ligt het aan mij of ontvang ik ineens geen mailtjes meer als er iemand gereageerd heeft in een van mijn topics?
Nee, dat ligt niet aan jou
Opzich is het conceptueel niet zo lastig hoor. Een node structuur is niets anders dan een boom of 'composite' pattern. Op internet zijn genoeg manieren te vinden hoe je dat met een db implementeerd.
Dat wat ik een engine noemde (term komt uit eoa cms waar ik ooit naar gekeken heb) is gewoon een soort module, een eigen mvc systeempje met routes, views en een of meerdere tabellen.
Hoe je dit daadwerkelijk uitschrijft, tsja... ;-)
Code (php)
Maar mijn vraag is, hoe herleid ik de onderstaande url dan weer naar de juiste action en controller:
www.mijnwebshop.nl/winkelmandje
Of anders gezegd, hoe match ik de url "winkelmandje" met de route "winkelmandje". Oké... in dit geval is het eenvoudig, maar wat als we het een beetje gecompliceerder maken. Stel we hebben 2 routes:
Code (php)
De 1e route bevat een variabele en zoals je ziet beginnen ze allebei met "product/".
Stel nu je hebt deze url:
www.mijnwebshop.nl/product/123
Hoe match je dan de url "product/123" met de juiste route? Dat is mijn vraag. Heeft iemand daar een handige tip voor?
Ik doe het vaak zo:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
$route = new Route('/abc/:id');
class Route
{
public function __c($pattern) {
$this->regex =
preg_replace(
'#:([a-z])+#',
'(?P<$1>[^/]+)',
$pattern
)
;
}
function match($query)
{
if(!preg_match('#'.$this->regex.'#', $query, $matches))
return false;
return $matches;
}
}
?>
$route = new Route('/abc/:id');
class Route
{
public function __c($pattern) {
$this->regex =
preg_replace(
'#:([a-z])+#',
'(?P<$1>[^/]+)',
$pattern
)
;
}
function match($query)
{
if(!preg_match('#'.$this->regex.'#', $query, $matches))
return false;
return $matches;
}
}
?>
Even 'quick and dirty'. Kijk eens naar http://www.php.net/manual/en/function.preg-match.php voor named subpatterns.
Toevoeging op 21/03/2012 10:47:52:
De patterns matchen nu alles voor de eerste slash, je kan dat evt nog wijzigen.
Ik zie dat je aan de $match functie een $query meegeeft. Moet dat niet een route zijn? En je returnt $matches? Moet ik daaruit concluderen dat een route meerdere matches kan hebben? Waar staat "__c" trouwens voor?
Je gebruikt het als volgt:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<?php
class Router
{
function addRoute(Route $r)
{
$this->routes[] = $r;
}
function match($query)
{
foreach($this->routes as $r)
if(($matches = $r->match($query)) !== false)
return $matches;
return false;
}
}
$router = new Router();
$router->addRoute(new Route('/appeltje'));
$router->addRoute(new Route('/peer/:id'));
$route->match('/peer/123');
?>
class Router
{
function addRoute(Route $r)
{
$this->routes[] = $r;
}
function match($query)
{
foreach($this->routes as $r)
if(($matches = $r->match($query)) !== false)
return $matches;
return false;
}
}
$router = new Router();
$router->addRoute(new Route('/appeltje'));
$router->addRoute(new Route('/peer/:id'));
$route->match('/peer/123');
?>
Toevoeging op 21/03/2012 11:02:36:
Zoals je in het PHP.net bestand ziet, geeft matches de regex matches terug. De named subpatterns zitten daarbij, zodat $matches['id'] het id is wat in de query zat.
Gewijzigd op 21/03/2012 11:03:14 door Pim -
Maar hoe krijg je in jouw voorbeeld nou de controller en action? Want dat zie ik nu nergens terug.
Je helpt me hier overigens enorm mee, thanks!!
De makkelijkste manier is om eoa string voorstelling te maken van de controller en action, bijv. Controller::Action. Je kan dan van route maken:
Code (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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<?php
class Route
{
public function __construct($pattern, $controller)
{
$this->regex = //...
$this->controller = $controller;
}
public function match($query)
{
if(!preg_match('#'.$this->regex.'#', $query, $matches))
return false;
return $matches+array('_controller'=>$this->controller);
}
}
$router = new Router();
$router->addRoute(new Route('/appeltje', 'AppelController::index'));
$router->addRoute(new Route('/peer/:id', 'PeerController::peertje'));
$result = $route->match('/peer/123');
$result['_controller'] // is PeerController::peertje
$result['id'] // is 123
?>
class Route
{
public function __construct($pattern, $controller)
{
$this->regex = //...
$this->controller = $controller;
}
public function match($query)
{
if(!preg_match('#'.$this->regex.'#', $query, $matches))
return false;
return $matches+array('_controller'=>$this->controller);
}
}
$router = new Router();
$router->addRoute(new Route('/appeltje', 'AppelController::index'));
$router->addRoute(new Route('/peer/:id', 'PeerController::peertje'));
$result = $route->match('/peer/123');
$result['_controller'] // is PeerController::peertje
$result['id'] // is 123
?>
Die data kan je dan aan eoa 'frontcontroller' geven, die de controller voor je zoekt en de parameters eraan geeft.
Gewijzigd op 21/03/2012 11:25:24 door Pim -
Thanks voor de hulp!!! Hoop er nog deze week mee aan de slag te gaan. Heb nu in ieder geval een mooie basis :-)))
Code (php)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<?php
class PeerController
{
public function peertjeAction($args)
{
print (int)$args['id'].' is het ID';
}
}
class FrontController
{
public function handle($controllerString, $args)
{
list($controller, $action) = explode('::', $controllerString);
$controller = new $controller;
// Weet niet of dit werkt
$controller->$action($args);
// Anders
call_user_func(array($controller, $action), $args);
}
}
?>
class PeerController
{
public function peertjeAction($args)
{
print (int)$args['id'].' is het ID';
}
}
class FrontController
{
public function handle($controllerString, $args)
{
list($controller, $action) = explode('::', $controllerString);
$controller = new $controller;
// Weet niet of dit werkt
$controller->$action($args);
// Anders
call_user_func(array($controller, $action), $args);
}
}
?>
Toevoeging op 21/03/2012 11:31:57:
Dus dan krijgen we:
Code (php)
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
<?php
$router = new Router();
$router->addRoute(new Route('/appeltje', 'AppelController::index'));
$router->addRoute(new Route('/peer/:id', 'PeerController::peertje'));
$result = $route->match('/peer/123');
$fc = new FrontController();
$fc->handle($result['_controller'], $result);
// Geeft: 123 is het ID
?>
$router = new Router();
$router->addRoute(new Route('/appeltje', 'AppelController::index'));
$router->addRoute(new Route('/peer/:id', 'PeerController::peertje'));
$result = $route->match('/peer/123');
$fc = new FrontController();
$fc->handle($result['_controller'], $result);
// Geeft: 123 is het ID
?>
Gewijzigd op 21/03/2012 11:32:31 door Pim -
Je bent de held van de dag :-)
Graag gedaan ;)