Vaste routes
ik zag dat Zend en Symfony gebruik maken van vaste routes. Ik bedoel hiermee dat je ergens een path moet definiëren en er een controller aan moet toewijzen.
Wat is het voordeel hiervan? Waarom kijkt de dispatcher niet gewoon of de controller bestaat idpv te gaan kijken of de route bestaat.
Ik begrijp dat je op die manier routes kan genereren die een andere naam hebben dan de controller. Voor de rest is het toch helemaal niet handig of flexibel? Voor elke controller een nieuwe router toevoegen?
Kan iemand mij uitleggen waarom de grote frameworks dit gebruiken en wat het voordeel hier van is?
Bedankt!
De homepage van mijn pagina is dus controller home met action index. "/" gaat mijn dispatcher daar automatisch naar doorsturen.
Quote:
"/" gaat mijn dispatcher daar automatisch naar doorsturen.
Je merkt toch zelf ook wel dat dit onpraktisch is? In je volgende project heb je een WelcomeController met een homeAction, ga je dan je hele framework dispatcher aanpassen?
Het is dan toch veel logischer om een configuratie bestand te maken waarin je aangeeft bij welke route je welke controller + action je aanroept, met welke parameters, met de juiste requirements en de juiste default waardes?
kunnen jullie me ook helpen met onderstaande vraag?
PHP Jasper op 08/02/2013 18:34:22:
dat brengt mij meteen bij het volgende punt. Wanneer een nieuwe controller en wanneer een nieuwe actie binnen een controller en wanneer een bundle? Want uiteindelijk noem je je controller en je action toch hoe je zelf wilt?
Gewijzigd op 08/02/2013 18:44:56 door Jasper DS
Quote:
Dus voor elke mogelijke view moet een route gedefinieerd zijn?
Voor elke view: Nee. voor elke action: Ja
Quote:
En wat met paden zoals bijvoorbeeld host/controller/action/param1/param2/param3 waarvan alleen param1 required is en param2 en param3 niet?
Kijk anders eens naar hoe we dat in Symfony2 doen: http://symfony.com/doc/current/book/routing.html
Edit:
Ja, maar nu even niet.
Quote:
kunnen jullie me ook helpen met onderstaande vraag?
Ja, maar nu even niet.
Gewijzigd op 08/02/2013 18:46:33 door Wouter J
Quote:
Wanneer een nieuwe controller en wanneer een nieuwe actie binnen een controller en wanneer een bundle? Want uiteindelijk noem je je controller en je action toch hoe je zelf wilt?
Een action is iets specifieks voor 1 request. Zo heb je een showAction en een editAction. Maar ook een listAction ect. Of een loginAction en een logoutAction, registerAction en registerSuccesAction, ect.
Sommige van deze actions horen bij elkaar. Zo zie je duidelijk dat de login- en logout actions bij elkaar horen en de register- en registerSuccess actions ook. Deze zet je dus in een aparte controller, SecurityController en RegisterController.
Vervolgens kun je sommige controllers ook weer groeperen in bundles. Zo heb je bijv. een UserBundle voor de controller hierboven. Dan zou je een BlogBundle hebben die controllers en models voor een blogpost heeft. Je hebt dan misschien ook wel een CommentsBundle, deze zal de UserBundle gebruiken om aan de users te komen die mogen reageren.
Bundles moet je altijd zo veel mogelijk los scripten. Onze CommentBundle moet niet afhankelijk worden van die ene UserBundle, hij moet ook met andere users kunnen werken.
Terug komend op de routes. Symfony zet de routes op deze manier in een .yml bestand:
Code (php)
1
2
3
4
5
6
7
8
2
3
4
5
6
7
8
# routes.yml
route1:
pattern: /foo
defaults: { _controller: 'MyController::fooAction' }
route2:
pattern: /foo/bar
defaults: { _controller: 'MyController::foobarAction' }
route1:
pattern: /foo
defaults: { _controller: 'MyController::fooAction' }
route2:
pattern: /foo/bar
defaults: { _controller: 'MyController::foobarAction' }
Pattern zegt welke path het is. Zo zal route1 regageren op host/foo en route2 op host/foo/bar.
Defaults zegt welke controller en actie er moet aangeroepen worden maar wat doet de "naam" -> "route1" en "route2". Is dat enkel voor in de .yml of doet symfony er echt iets mee?
ik zag in YamlFileLoader de volgende opties: 'type', 'resource', 'prefix', 'pattern', 'options', 'defaults', 'requirements'. Is het één van die of is het nog iets anders?
Gewijzigd op 12/02/2013 01:05:49 door Jasper DS
Quote:
Pattern zegt welke path het is. Zo zal route1 regageren op host/foo en route2 op host/foo/bar.
Merk op dat vanaf Symfony2.2 pattern vervangen is door path.
Quote:
Is dat enkel voor in de .yml of doet symfony er echt iets mee?
Dat is de naam van de route, hartstikke handig. Zo kun je de path's flexibel houden. Je kunt dan een url opvragen met `url('route1')`, hij zal dan de url opbouwen. Mocht je nog parameters in de url hebben dan doe je `url('route1', { 'foo' => 'bar' })`.
Oke mooi. Ik heb al een hele dag zitten zoeken hoe symfony de routes binnenhaalt... Hoe word aan het path gekoppeld aan de controller? Want je krijgt een path binnen en dan moet je programma toch niet heel de .yml doorlopen tot hij de route met overeenkomstige path heeft gevonden?
Allereerst heb je de RouteCollection welke uit allemaal Routes (en weer aparte RouteCollections) bestaat. Deze houdt gewoon alle routes vast. Je kan deze zelf met PHP maken:
Code (php)
1
2
3
4
5
6
7
8
9
10
11
2
3
4
5
6
7
8
9
10
11
<?php
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$route = new Route('/about');
$route1 = new Route('/post/{slug}', array(), array('slug' => '[A-Za-z0-9-]+'));
$routeCollection = new RouteCollection();
$routeCollection->add('about_page', $route);
$routeCollection->add('show_post', $route1);
?>
use Symfony\Component\Routing\Route;
use Symfony\Component\Routing\RouteCollection;
$route = new Route('/about');
$route1 = new Route('/post/{slug}', array(), array('slug' => '[A-Za-z0-9-]+'));
$routeCollection = new RouteCollection();
$routeCollection->add('about_page', $route);
$routeCollection->add('show_post', $route1);
?>
Je kan dit ook via een Yml of XML file doen en dan de YmlRouteLoader gebruiken.
Vervolgens hebben we al deze routes, maar nu moeten we ook nog iets hebben waarmee we deze routes kunnen vergelijken: De huidige url. Hiervoor hebben we een RequestContext class nodig, die alle informatie over de huidige request verzameld die handig kan zijn voor het matchen van een route:
Code (php)
1
2
3
4
5
2
3
4
5
<?php
use Symfony\Component\Routing\RequestContext;
$context = new RequestContext($_SERVER['REQUEST_URI']);
?>
use Symfony\Component\Routing\RequestContext;
$context = new RequestContext($_SERVER['REQUEST_URI']);
?>
Als laatst hebben we nog een klasse nodig die de routes met deze RequestContext gaat matchen en dan de juiste route eruit pikt. Hiervoor hebben we een UrlMatcher klasse:
Code (php)
1
2
3
4
5
2
3
4
5
<?php
user Symfony\Component\Routing\Matcher\UrlMatcher;
$matcher = new UrlMatcher($routeCollection, $context);
?>
user Symfony\Component\Routing\Matcher\UrlMatcher;
$matcher = new UrlMatcher($routeCollection, $context);
?>
En nu kunnen we gaan matchen:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
<?php
$result = $matcher->match('/about');
// $result = array('_route' => 'about_page')
$result = $matcher->match('/post/hello-world');
// $result = array('slug' => 'hello-world', '_route' => 'show_post');
?>
$result = $matcher->match('/about');
// $result = array('_route' => 'about_page')
$result = $matcher->match('/post/hello-world');
// $result = array('slug' => 'hello-world', '_route' => 'show_post');
?>
We krijgen nu dus een array terug met daarin de waardes van de slug en de naam van de route die matched. Nu hebben we hier niet heel veel aan, we hebben een naam van een Controller nodig.
De manier hoe Symfony2 dit oplost is door een _controller parameter mee te geven aan de routes, deze stellen we dan in bij de default values, zodat we de controller naam niet in de URL zien:
Code (php)
1
2
3
2
3
<?php
$route = new Route('/about', array('_controller' => 'PageController::showAbout'));
?>
$route = new Route('/about', array('_controller' => 'PageController::showAbout'));
?>
Als we hem nu matchen zullen we deze controller terug krijgen:
Vervolgens kunnen we deze string omzetten in een call naar de controller met functies als call_user_func_array.
In Symfony2 gebruiken we een logical name om aan te geven welke controller er wordt getoond. Zo zal een string als WjPageBundle:Page:showPage verwijzen naar de method showPageAction in de Wj\PageBundle\Controller\PageController class.
Nu komt dit waarschijnlijk gigantisch groot en moeilijk bij je over, het valt best mee, maar ik zal snappen als je denkt: Dit kan ik nooit. Geen nood, je kan altijd de Routing Component gebruiken, maar je kan er ook eentje zelf schrijven. Zo moeilijk is dit namelijk niet, lees anders maar eens de code die Pim ooit eens gaf door: http://www.phphulp.nl/php/forum/topic/router/83492/#594215
Nu loop ik wel te knoeien met de params / args. De array die ik momenteel terug krijg ziet er als ik Pim zijn voorbeeld routers gebruik zo uit:
Code (php)
1
2
3
4
5
2
3
4
5
array (size=4)
0 => string '/peer/123' (length=9)
'd' => string '123' (length=3)
1 => string '123' (length=3)
'_controller' => string 'Jds::Welcome::helloWorld' (length=24)
0 => string '/peer/123' (length=9)
'd' => string '123' (length=3)
1 => string '123' (length=3)
'_controller' => string 'Jds::Welcome::helloWorld' (length=24)
Nu wil ik eigenlijk dat die array alleen de "slug bevat" dus stel dat we zoiets hebben: host/controller/action/:id/:name:/:month dan wil ik een array terug krijgen met id, name en month als key en de waarde van de url als value. Is dat mogelijk / handig? Zouden jullie het ook zo doen? Want ik kom er niet uit wat Pim met de args doet.. :s
Pim returnt ergens $matches, en die $matches, dat zijn dan de slugs.
Quote:
Is dat mogelijk / handig? Zouden jullie het ook zo doen? Want ik kom er niet uit wat Pim met de args doet.. :s
Ja, het is mogelijk, maar nee ik zou het niet zo doen. Je moet de controller meegeven aan de args, anders kun je nooit de controller achterhalen.
Wat je doet is de controller ophalen en dan de controller eruit toveren. Vervolgens kun je deze controller uit de array halen, samen met de numeric keys en dan hou je alleen maar je placeholders over.
Wat je nog beter kan doen is wat magic uithalen met de ingebouwde Reflection* klassen. Zie daarvoor: http://www.phphulp.nl/php/forum/topic/routing-hoe-parameters-meegeven/88894/#639085
Code (php)
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
function match($query)
{
foreach($this->routes as $r)
{
if(($matches = $r->match($query)) !== false)
{
return $matches;
}
}
}
{
foreach($this->routes as $r)
{
if(($matches = $r->match($query)) !== false)
{
return $matches;
}
}
}
Gewijzigd op 14/02/2013 14:08:01 door Jasper DS
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
25
26
27
28
29
30
31
32
33
34
35
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
32
33
34
35
array (size=3)
'home' =>
object(Jframe\Router\Route)[4]
public 'name' => string 'home' (length=4)
public 'path' => string '/' (length=1)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'index' (length=5)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=0)
empty
'helloWorld' =>
object(Jframe\Router\Route)[5]
public 'name' => string 'helloWorld' (length=10)
public 'path' => string '/hallo' (length=6)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'helloWorld' (length=10)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=0)
empty
'blah' =>
object(Jframe\Router\Route)[6]
public 'name' => string 'blah' (length=4)
public 'path' => string '/page' (length=5)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'helloWorld' (length=10)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=2)
1 => string 'slug' (length=4)
2 => string 'id' (length=2)
'home' =>
object(Jframe\Router\Route)[4]
public 'name' => string 'home' (length=4)
public 'path' => string '/' (length=1)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'index' (length=5)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=0)
empty
'helloWorld' =>
object(Jframe\Router\Route)[5]
public 'name' => string 'helloWorld' (length=10)
public 'path' => string '/hallo' (length=6)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'helloWorld' (length=10)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=0)
empty
'blah' =>
object(Jframe\Router\Route)[6]
public 'name' => string 'blah' (length=4)
public 'path' => string '/page' (length=5)
public 'bundle' => string 'Jds' (length=3)
public 'controller' => string 'Welcome' (length=7)
public 'action' => string 'helloWorld' (length=10)
public 'rgx' => string '' (length=0)
public 'params' =>
array (size=2)
1 => string 'slug' (length=4)
2 => string 'id' (length=2)
Dat ziet er naar mijn mening goed uit alleen kan ik niet controleren op params. Ik heb alle topics meermaals doorgespit maar ik geraak er niet uit.
routers.yml
Route.php
Router.php
Frontcontroller
App.php
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
25
26
27
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
$yaml = new Parser();
//$parser = new ConfigParser();
$values = $yaml->parse(file_get_contents('../app/config/routes.yml'));
$router = new Router();
foreach($values as $key=>$value)
{
$router->addRoute(new Route($key, $value['path'], $value['controller']));
}
var_dump($router->routes);
$result = $router->match(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
if(false === $result)
{
echo 'Route not found';
}
else
{
$fc = new FrontController();
$fc->handle($result);
}
//$parser = new ConfigParser();
$values = $yaml->parse(file_get_contents('../app/config/routes.yml'));
$router = new Router();
foreach($values as $key=>$value)
{
$router->addRoute(new Route($key, $value['path'], $value['controller']));
}
var_dump($router->routes);
$result = $router->match(parse_url($_SERVER["REQUEST_URI"], PHP_URL_PATH));
if(false === $result)
{
echo 'Route not found';
}
else
{
$fc = new FrontController();
$fc->handle($result);
}
Gewijzigd op 15/02/2013 14:15:10 door Jasper DS
- host/blog/dit/is/param1/param2 -> host/blog/:param1/:param2
- host/blog/dit/is/een/url -> host/blog/dit/is/een/url
in principe voldoet url 2 aan het path van url 1 als je "een" en "url" beziet als param. Hoe lost men dit dan op?
Ik gebruik volgende regex van Pim uit het toppic van Ozzie:
preg_replace('#:([a-z])+#','(?P<$1>[^/]+)',$path);
Ik heb nog twee vragen, volgende routes werken:
- /index
- /hallo
- /page/:id/:slug
- Hoe zorg ik ervoor dat / ook aanvaard word? Momenteel vangt het path / alle routes op? Ik heb al geprobeert de "+" op het einde weg te halen maar dan werken de andere url's niet meer?
- Hoe zie je het verschil tussen /page/show/:id/:name en /page/show/:author/:id? Of ben je dan slechte routes aan het maken?
Gewijzigd op 18/02/2013 17:00:27 door Jasper DS
Quote:
- Hoe zie je het verschil tussen /page/show/:id/:name en /page/show/:author/:id? Of ben je dan slechte routes aan het maken?
Dat doe je met requirements:
Code (php)
1
2
3
4
5
6
7
2
3
4
5
6
7
blog:
pattern: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
show_post:
pattern: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Post:show }
pattern: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
show_post:
pattern: /blog/{slug}
defaults: { _controller: AcmeBlogBundle:Post:show }
In geval 1 wil je routes als /blog/2 en /blog/3 matchen en in geval 2 wil je routes als /blog/hello-world matchen, in dat geval voeg je requirements toe:
Code (php)
1
2
3
4
5
2
3
4
5
blog:
pattern: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
requirements:
page: \d+
pattern: /blog/{page}
defaults: { _controller: AcmeBlogBundle:Blog:index, page: 1 }
requirements:
page: \d+
Quote:
- Hoe zorg ik ervoor dat / ook aanvaard word? Momenteel vangt het path / alle routes op? Ik heb al geprobeert de "+" op het einde weg te halen maar dan werken de andere url's niet meer?
Dan moet je niet deze regex aanpassen, maar die in Route#match. Dat moet worden: