19

I know that you can add rules in htaccess, but I see that PHP frameworks don't do that and somehow you still have pretty URLs. How do they do that if the server is not aware of the URL rules?

I've been looking Yii's url manager class but I don't understand how it does it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Alex
  • 66,732
  • 177
  • 439
  • 641
  • 5
    See my answer [How to change appearance of URL from within a PHP script](http://stackoverflow.com/questions/8392965/how-to-change-appearance-of-url-from-within-a-php-script/8392997#8392997) What most framework do is redirect all request to one file that handles everything. – Ibu Dec 09 '11 at 03:02
  • The link is (effectively) broken (the source code is absent). [Google Code shut down in 2016](https://en.wikipedia.org/wiki/Google_Developers#Google_Code). Is it [now at GitHub](https://github.com/yiisoft/yii2)? – Peter Mortensen Nov 30 '19 at 20:37

1 Answers1

17

This is usually done by routing all requests to a single entry point (a file that executes different code based on the request) with a rule like:

# Redirect everything that doesn't match a directory or file to index.php
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule .* index.php [L]

This file then compares the request ($_SERVER["REQUEST_URI"]) against a list of routes - a mapping of a pattern matching the request to a controller action (in MVC applications) or another path of execution. Frameworks often include a route that can infer the controller and action from the request itself, as a backup route.

A small, simplified example:

<?php

// Define a couple of simple actions
class Home {
    public function GET() { return 'Homepage'; }
}

class About {
    public function GET() { return 'About page'; }
}

// Mapping of request pattern (URL) to action classes (above)
$routes = array(
    '/' => 'Home',
    '/about' => 'About'
);

// Match the request to a route (find the first matching URL in routes)
$request = '/' . trim($_SERVER['REQUEST_URI'], '/');
$route = null;
foreach ($routes as $pattern => $class) {
    if ($pattern == $request) {
        $route = $class;
        break;
    }
}

// If no route matched, or class for route not found (404)
if (is_null($route) || !class_exists($route)) {
    header('HTTP/1.1 404 Not Found');
    echo 'Page not found';
    exit(1);
}

// If method not found in action class, send a 405 (e.g. Home::POST())
if (!method_exists($route, $_SERVER["REQUEST_METHOD"])) {
    header('HTTP/1.1 405 Method not allowed');
    echo 'Method not allowed';
    exit(1);
}

// Otherwise, return the result of the action
$action = new $route;
$result = call_user_func(array($action, $_SERVER["REQUEST_METHOD"]));
echo $result;

Combined with the first configuration, this is a simple script that will allow you to use URLs like domain.com/about. Hope this helps you make sense of what's going on here.

Ross
  • 46,186
  • 39
  • 120
  • 173
  • 1
    in your code you forget: to add the url in the GET params: `RewriteRule (.*) index.php?url=$1 [QSA,L]` – Olivier Pons Dec 09 '11 at 06:56
  • 1
    Hi Olivier, it's not really necessary to pass the url as a parameter as it's available in `$_SERVER['REQUEST_URI']`. – Ross Dec 09 '11 at 07:49
  • Are you *sure* that it won't be the final rewritten URL (i.e. `$_SERVER['REQUEST_URI']`==`index.php`)? – Olivier Pons Dec 09 '11 at 14:40