A very very basic routing class.

PHP programming forum. Ask questions or help people concerning PHP code. Don't understand a function? Need help implementing a class? Don't understand a class? Here is where to ask. Remember to do your homework!

Moderator: General Moderators

Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

A very very basic routing class.

Post by Chris30 »

I want this feature for my framework.

Route::get('/account/(int)', function() {
Forward('account, array($1))
}

So in reality; ?page=account&memberID=5 would become; /account/5

I would like to see a very small example of how it is done. Manually get URI, explode it by / symbols, handle it accordingly?

I want to see a very basic example of this task. Checked the routing classes in frameworks on github but they are pretty huge. I don't need that. I just want a singlepage class if possible, so I can create mine by referencing to it.

Thanks!
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

Having done this kind of thing a couple times before, might I suggest this?

Code: Select all

Route::add('/account/(\d+)', function($dispatch, $matches) {
    $dispatch->Forward('account', array($matches[1]));
});
1. The first argument is a regular expression you run through preg_match()

Code: Select all

// example data
$url = '/account/5';
$test = '/account/(\d+)';

// here's how you use them
if (preg_match("#^{$test}$#", $url, $matches)) {
    // matched. now send $matches to the callback function
} else {
    // did not match. try something else
}
2. $dispatch and $matches are so that the callback has access to the things it needs: namely the object that it calls ->Forward() on and the matched data from the regex it gave


---


But you can go even further.

Code: Select all

Route::add('/account/(?P<memberID>\d+)', 'account');
with the default action of the "forward" thing - assuming, of course, that there are other things it could have done. Then 'account' (whatever that is?) gets the array of matches from the regex but now the ID in there is named "memberID". It does require you to know a little bit more about regular expressions but it saves you from writing a lot of interstitial code.
Last edited by requinix on Sat Mar 30, 2013 6:08 pm, edited 1 time in total.
Reason: changed regex delimiters
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

How do we get the $url and $test values, $_SERVER['REQUEST_URI']? Also, does the URI depends on platform (like directory differences (/ and \\) between unix and win) or always returns the correct URI?
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

Look in $_SERVER. You can use the REQUEST_URI but that will also have the query string. And no, the URI will always use forward slashes.
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

I made something like this.

Code: Select all

//in Route class
function get($uri)
{
   if($uri == '/account')
   {
      return true;
   }
   else
   {
      return false;
   }
}

function Boo()
{
  echo "Boo";
}

//in index.php
$route = new Route();

$route->get('/account', function() use ($route) { 
   return $route->Boo();
});
It does not call $route->Boo().

A guy on stackoverflow said I should pass a second parameter to get function, (e.g $uri, Closure $closure) but it will be pretty bad looking.

I want to be able to write PHP codes inside my closure, like;

Code: Select all

$route->get('/account', function() use ($route) { 
   //Do something what you can do without closure, like loading a view file (e.g $view->create('account.tpl');
});
How can I do this?
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

Of course it doesn't call that method: all get() does is return true or false. Seriously. Read the code.

Try more like

Code: Select all

//in Route class
function get($uri, $callback)
{
   if($uri == '/account')
   {
      $callback();
      return true;
   }
   else
   {
      return false;
   }
}
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

Okay but, handling callback inside the get function isn't something I prefer doing. The route class doesn't connect other classes.
For example, if there is a wrong URI given, I would like to print load 404 view, hence use the template model.

index.php

Code: Select all

if($route->get('account') == true)
      $controller->Run();
else
      $template->loadView('404.tpl');
Index.php has access to $template, but Route class is not. (and should not, it is not his purpose to handle template)

For that reason, I want to handle callback in a different scope. Like the scope in index.php itself so I can use $template->loadView('404.tpl'); inside the closure.

Is that not possible with PHP?
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

I'm sure it is possible, whatever you're trying to say. What's not possible is for the script to behave in a certain way when you specifically do not want to write the code to allow it to behave in the certain way.

How about this angle. Inside index.php what kind of code do you want to write? What should it look like?
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

I don't think I can explain myself any clearer than this.

Let's have a look at your example.

Code: Select all

//in Route class
function get($uri, $callback)
{
   if($uri == '/account')
   {
      $callback();
      return true;
   }
   else
   {
      return false;
   }
}
We defined a second paramter to get() function to handle callbacks. However;

1. I don't want to define a secondary parameter in every function to handle callbacks. I want to handle them somewhere else. Not inside a function inside a basic routing class.
2. The routing class has no access to other controllers. So get() function cannot take "$this->database->mysql->query" as a callback because Route class has no access to database object. It is out of it's scope.

In short, I want to do something like this:

Code: Select all

index.php
$route->get('/account', function() use ($route) //Like in javascript, get('/account') returns true so we can do something inside the closure.
{
      $this->database->mysql->query('BOOM BOOM <span style='color:blue' title='I&#39;m naughty, are you naughty?'>smurf</span>');
      or
      return Template::load('error.tpl');
});
But without as a secondary parameter to get function. I may make some other functions than get and I have to re-define a callback eachtime.

How else, without using a second parameter named "Closure $closure" in get() function, you can solve the code above?
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

1. Then don't use callbacks.
2. I don't know why you're saying it needs to pass mysql->query as a callback. That's not what's happening at all.

As for your sample code, unless you're on 5.4+ you can't use $this directly inside a closure. The Template stuff will work. So is that a solution? I still can't tell what the problem is besides you not wanting to pass along a "dispatcher" object from the Route class (something which, if it exists, the router would most certainly have available) to the callback, and that you've changed the desired behavior of this code a few times in this thread.

So as for my earlier question of what you want your index.php code to look like, is

Code: Select all

$route->get('/account', function() use ($route)
{
      return Template::load('error.tpl');
});
this it? Where is the result of that ::load() going? You've already said the router has no access to dispatchers, controllers, or databases.

FYI in this pattern, which it seems like you're trying very hard to get right, the router class is in charge of figuring out where a request should be handled. That's all. It shouldn't actually be executing the actions - that's the job of a front controller or dispatcher.
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

Well. How does Laravel handle it then?

http://laravel.com/docs/routing

Code: Select all

Route::get('/', function()
{
    return "Hello World!";
});
or

Code: Select all

Event::listen('404', function()
{
    return Response::error('404');
});
Route class only looks for an $action parameter. (In sources of Laravel Route class)
User avatar
requinix
Spammer :|
Posts: 6617
Joined: Wed Oct 15, 2008 2:35 am
Location: WA, USA

Re: A very very basic routing class.

Post by requinix »

Because that there encapsulates routing and controllers. Those functions are the controllers.
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: A very very basic routing class.

Post by Christopher »

You could do something like this:

Code: Select all

class Route
{
	protected static $routes = array();
	
	public static function get($route, $function)
	{
		self::$routes[$route] = $function;
	}
	
	public static function run($route)
	{
		if (isset(self::$routes[$route]) && is_callable(self::$routes[$route])) {
			call_user_func(self::$routes[$route]);
		}
	}
}
Route::get('/', function()
{
    echo "Hello World!";
});
Route::run('/');
(#10850)
Chris30
Forum Newbie
Posts: 15
Joined: Sat Jan 19, 2013 4:52 pm

Re: A very very basic routing class.

Post by Chris30 »

Christopher wrote:You could do something like this:

Code: Select all

class Route
{
	protected static $routes = array();
	
	public static function get($route, $function)
	{
		self::$routes[$route] = $function;
	}
	
	public static function run($route)
	{
		if (isset(self::$routes[$route]) && is_callable(self::$routes[$route])) {
			call_user_func(self::$routes[$route]);
		}
	}
}
Route::get('/', function()
{
    echo "Hello World!";
});
Route::run('/');
Yeah, things are alot clearer now! Laravel also does something like this, right?
User avatar
Christopher
Site Administrator
Posts: 13596
Joined: Wed Aug 25, 2004 7:54 pm
Location: New York, NY, US

Re: A very very basic routing class.

Post by Christopher »

Chris30 wrote:Laravel also does something like this, right?
I haven't looked at the code, so don't know. This is the style of microframeworks. They are for sites that don't have a lot of pages and the pages are fairly simple. For larger sites you want to use Action Controller classes that are loaded on request.
(#10850)
Post Reply