Beste Developers,

Ik zie vaak allemaal ingewikkelde routers tegen komen op internet.

Na even te zoeken kwam ik een vrij simpele router tegen hier op PHPHulp.

Wat is de reden van een uitgebreide moeilijke router? Na mijn mening is dit toch prima?

Enige probleem dat ik nu nog heb, is dat wanneer ik nu naar /artikel/bewerk/234/ dat hij de route /artikel/ pakt, hoe kan ik dit op de beste manier oplossen? Alles een prioriteit meegeven, sorteren op langste route? Wat is de beste manier hiervoor? Of gewoon een kwestie van de routes aanmaken in de goede volgorde?


<?php
class Router
{
    private $_routes;

    public function __construct()
    {
        $this->_routes = array();
    }

    public function setRoute( Route $route )
    {
        $this->_routes[] = $route;
    }

    public function match( $query )
    {
        $routes = $this->_routes;
        if( $routes )
        {
            foreach( $routes as $route )
            {
                $match = $route->match( $query );
                if( $match )
                {
                    return $match;
                }
            }
        }
        return false;
    }
}

class Route
{
    private $_regex;
    private $_controller;

    public function __construct( $pattern, $controller )
    {
        $this->_regex = preg_replace( '#:([a-z])+#','(?P<$1>[^/]+)', $pattern );
        $this->_controller = $controller;
    }

    public function match( $query )
    {
        if( !preg_match( '#'.$this->_regex.'#', $query, $matches ) )
        {
            return false;
        }

        $controller = new Controller();
        $controller->setName( $this->_controller );
        return $controller;
    }
}

class Controller
{
    private $_name;

    public function setName( $name )
    {
        $this->_name = $name;
    }
    public function getName()
    {
        return $this->_name;
    }
}

$router = new Router();
$router->setRoute( new Route( '/artikel/', 'article.view.module') );
$router->setRoute( new Route( '/artikel/bewerk/:id', 'article.edit.module') );
$router->setRoute( new Route( '/artikel/verwijder/:id', 'article.delete.module') );

$controller = $router->match( $request->server( 'REDIRECT_URL' ) );
?>


Oke... duurt altijd zo lang :)
Ondertussen heb ik alvast wat wijzigingen aangebracht in mijn router.

Route.class.php

<?php
/**
 * @author Tom Swinkels
 * @version v1.0 last edit on 08-09-2013
 */
class Route
{
    private $_pattern;
    private $_data;

    public function __construct( $pattern, $data )
    {
        $this->_pattern = $pattern;
        $this->_data = $data;
    }

    public function match( $query )
    {
        // ADD EXPESSION FOR MATCHING.
        $route = '#^' . $query . '$#';
        
        // CHECK FOR NUMERIC ROUTE
        $route = preg_replace( '/\<\#(.*?)\>/', '(?P<\1>[0-9]+)', $route );

        // CHECK FOR ALPHA NUMERIC ROUTE
        $route = preg_replace( '/\<\:(.*?)\>/', '(?P<\1>[A-Za-z0-9\-\_]+)', $route );

        if( preg_match( $route, $query, $matches ) )
        {
            return $this->_data;
        }
        return false;
    }
}
?>


Router.class.php

<?php
/**
 * @author Tom Swinkels
 * @version v1.0 last edit on 08-09-2013
 */
class Router
{
    private $_routes;

    public function __construct()
    {
        $this->_routes = array();
    }

    public function setRoute( Route $route )
    {
        $this->_routes[] = $route;
    }

    public function getRoute( $query )
    {
        $routes = $this->_routes;
        if( $routes )
        {
            foreach( $routes as $route )
            {
                $match = $route->match( $query );
                if( $match )
                {
                    $controllerName = $match['controller'] . 'Controller';
                    
                    $controller = new $controllerName();
                    $controller->$match['action']();
                    return $controller;
                }
            }
        }
        return false;
    }
}
?>


Dit stukje zou dan uiteindelijk in de FrontController moeten komen??

<?php
$router = new Router();
$router->setRoute( new Route( '/artikel/bewerk/<#id>/', array( 'controller' => 'Article', 'module' => 'edit', 'action' => 'index' ) ) );
$controller = $router->getRoute( $request->server( 'REDIRECT_URL' ) );
?>
De routes toevoegen wordt natuurlijk in een configuratie bestand gezet. Andere punten:
- de router heeft niks van doen met het koppelen van een route aan een controller, dit moet de front controller of een controller resolver doen.
- regel 22 van de router is nodeloos geheugen verspilling
- gebruik liever count om te kijken of er routes zijn
- een method addRoute beschrijft de functie beter
- gebruik in het geval van ids natuurlijk wel <#id>
- geen routes om in te zoeken zou ik opvatten als een fout
Ik heb de Route.class.php nog wat aangepast.

Bedankt voor je antwoord, hier kan ik wat mee!

Antwoorden op jou vragen:

- Ik return enkel de data, en in de FrontController roep ik de nieuwe controllers aan bedoel je?
- Regel 22?
- Waarom zou je dat met count kijken?
- Dit had ik nog niet aangepast.
- Wat bedoel je met geen routes om in te zoeken?

Hoe zou jij een default route afhandelen / toevoegen?

Verder geeft het bovenstaande stuk matches op alles.
- Nee, je retourneert gewoon de hele route en laat de front controller lekker uitzoeken wat hij daarna met die route wil doen, dat gaat de router niks aan.
- Ja, regel 22:
[code s=19]<?php
public function getRoute( $query )
{
$routes = $this->_routes;
?>[/code]
- Omdat je een array hebt en geen boolean value. Gewoon een kwestie van netjes scripten. Zoals ik altijd zeg "PHP is awesome because it's noobproof, a PHP developer is awesome if he doesn't use those noobproof-features".
- Nou, stel $this->_routes is leeg, dan zou ik een exceptiontje gooien

En default routes, wat bedoel je daarmee?
- Ok, maar wie roept de router aan? Aangezien de routes uit de config komen?
- Welke array bedoel je? Het is toch logies om alle routes in een array te gooien?

Ik bedoel wanneer er geen route is opgegeven, bijvoorbeeld www.site.nl hoe bepaal ik welke route er dan standaard wordt uitgevoerd?
- De frontcontroller roept de router aan. Routes uit config kun je erg simpel doen met:
[code file=routes.php]<?php
$router->addRoute(new Route(...));
// ...
?>[/code]
[code file=frontcontroller.php]<?php
class FrontController
{
public function terminate(...)
{
// ...

$router = $this->getRouter();
$route = $router->match(...);

// ... do something nice with the matched route
}

protected function getRouter()
{
$router = new Router();
require_once 'path/to/config/routes.php'; // laad routes

return $router;
}
}
?>[/code]

- Ja, maar deze array kan ook leeg zijn. Daar heb je nu een ifje voor gebruikt, zodra die leeg is zou ik dus een error gooien.

De route site.nl/ is de '/' route.
Ik ga nu slapen na een indrukwekkende reportage bij SBS6 te hebben gezien.

Morgen probeer ik het een en ander uit te werken.

Een quote uit je eerste bericht in dit topic "- Je mist default values, ik kan niet zeggen dat /:slug standaard de waarde 'home' krijgt.".

[size=xsmall]Toevoeging op 09/09/2013 10:10:37:[/size]

Ik heb ondertussen wat wijzigen doorgevoerd.

FrontController

<?php
/**
 * @author Tom Swinkels
 * @version v1.0 last edit on 09-09-2013
 */
class FrontController
{
    public function __construct( $requestUri ) 
    { 
        $router = $this->getRouter();
        
        $route = $router->getRoute( $requestUri );
        if( $route )
        {
            $data = $route->getData();
            
            $controllerName = $data['controller'] . 'Controller';

            $controller = new $controllerName();
            $controller->$data['action']();
            return $controller;
        }
    }
    
    protected function getRouter()
    {
        $router = new Router();
        
        require_once('../include/routes.php'); // laad routes

        return $router;
    }
}
?>


Route

<?php
/**
 * @author Tom Swinkels
 * @version v1.0 last edit on 08-09-2013
 */
class Route
{
    private $_pattern;
    private $_data;

    public function __construct( $pattern, $data )
    {
        $this->_pattern = $pattern;
        $this->_data = $data;
    }
    
    public function getData()
    {
        return $this->_data;
    }

    public function match( $query )
    {
        // ADD EXPESSION FOR MATCHING.
        $route = '#^' . $query . '$#';
        
        // CHECK FOR NUMERIC ROUTE
        $route = preg_replace( '/\<\#(.*?)\>/', '(?P<\1>[0-9]+)', $route );

        // CHECK FOR ALPHA NUMERIC ROUTE
        $route = preg_replace( '/\<\:(.*?)\>/', '(?P<\1>[A-Za-z0-9\-\_]+)', $route );

        if( preg_match( $route, $query, $matches ) )
        {
            return $route;
        }
        return false;
    }
}
?>


Router

<?php
/**
 * @author Tom Swinkels
 * @version v1.0 last edit on 08-09-2013
 */
class Router
{
    private $_routes;

    public function __construct()
    {
        $this->_routes = array();
    }

    public function setRoute( Route $route )
    {
        $this->_routes[] = $route;
    }

    public function getRoute( $query )
    {
        $routes = $this->_routes;
        if( $routes )
        {
            foreach( $routes as $route )
            {
                $match = $route->match( $query );
                if( $match )
                {
                    return $route;
                }
            }
        }
        return false;
    }
}
?>
Returnen in een constructor doet niets. Die instantie van de juiste controller zul je moeten opslaan in de FrontController class.
Zelf gebruik ik een aparte compiler voor het maken van de reguliere expressie en een matcher die de goede route zoekt bij de url

Naar mijn mening is zo'n route class die je als voorbeeld stelt genoeg voor kleine applicaties. Echter als je zoals Symfony ook vette dingen wilt doen als url's genereren per route bijvoorbeeld, dan moet je wat extra code toevoegen

Reageren