mecha-cms / mecha

Minimalist content management system.
https://mecha-cms.com
GNU General Public License v3.0
178 stars 23 forks source link

Remove `Route` Class #152

Closed taufik-nurrohman closed 3 years ago

taufik-nurrohman commented 3 years ago

From the very beginning, class Route is actually just a syntactic sugar for conditional on the $url->path value.

Both of the commands below can be considered identical:

Route::set("", function() { … });
Route::set('*', function($path) { … });
Route::set('product/:id', function($id) { … });

Route::start();
$route = trim($url->path, '/');
$fn = function() {};
$lot = [];

if ("" === $route) {
    $fn = function() { … };
}

if ("" !== $route) {
    $fn = function($path) { … };
    $lot = [$route];
}

if (preg_match('/^product\/([^\/]+)$/', $route, $m)) {
    $fn = function($id) { … };
    $lot = [$m[1]];
}

call_user_func($fn, $lot);

Just because the route is executed at the end and has a function stack feature, it becomes a safer option for extensions to add custom pages since extensions can control the route function execution priority by adding a stack value to the route definition so that the core system can execute those functions in order.

Do you know what else has a function stack feature? Hooks!

Yes, we can replace Route class completely with Hook! Hook name is likely to last longer since even WordPress still using the term to this day. But I need a good name to associate route-related hooks on the system as a built-in feature. The best hook name candidate for now is probably route. This name will certainly be harmonious when paired with content.

Hook::set('route', function($route) { … }, 10);

Now I need a way to parse the $route component as in Route. It must be neat and can auto-parse numeric part into a real number.

Creating namespaces for each route might make it a bit easier to develop. Each extension can then add their own route hooks into the main route hook. This will automatically create the impression of having a group feature on the route mechanism without making it complicated:

<?php

// `.\lot\x\page\index.php`
Hook::set('route', function($route) {
    Hook::fire('route.page', [$route], $this);
}, 10);
<?php

// `.\lot\x\user\index.php`
Hook::set('route', function($route) {
    if ("" === $route) {
        return;
    }
    $chops = explode('/', $route);
    if ('user' === array_shift($chops)) {
        if (1 === count($chops)) {
            // `http://127.0.0.1/user/taufik-nurrohman`
            Hook::fire('route.user', [$chops[0]], $this);
        } else {
            // `http://127.0.0.1/user`
            Hook::fire('route.user', [null], $this);
        }
    } else {
        // Default to the page route
        Hook::fire('route.page', [$route], $this);
    }
}, 9);
<?php

// `.\lot\x\tag\index.php`
Hook::set('route', function($route) {
    if ("" === $route || 0 === substr_count($route, '/')) {
        return;
    }
    $chops = explode('/', $path);
    $tag = array_pop($chops);
    $path = array_pop($chops);
    if ('tag' === $path && $tag) {
        Hook::fire('route.tag', [$tag], $this);
    } else {
        // Default to the page route
        Hook::fire('route.page', [$route], $this);
    }
}, 9);
taufik-nurrohman commented 3 years ago

Route::hit() method can be performed simply by pushing more hooks with a higher stack:

Hook::set('route.page', function($route) { … }, 1); // This should run on top of the default `route.page` hook