Router
At this point, you have a general understanding of how Jet MVC works, you know what a base is, what a page is, and controller. One of the last pieces missing to complete the puzzle is the router.
And right at the beginning, one important piece of information. The router mainly evaluates. Based on the URL, it decides what the request is (what base, locale, page and how any leftover URL is handled) and sets the overall status. But the router no longer does anything like sending http headers, generating output and so on. No, that's not a job for the router. To be precise, the router has to do the things that are part of the resolving process - i.e. call the base initializer, set up the localization and resolver. This is a necessary part of the evaluation process. But it no longer sends the generated page to the output. That is no longer the responsibility of the router.
In practice, what happens is that the system creates a singleton of class Jet\MVC_Router (or other - your implementation, if you specify it using factory) and then evaluates the URL it receives as a parameter (it can be any URL, not just the one from the current http request).
The router will parse/evaluate this URL (find out what base, locale and page it is) and set its internal and system state (for example, the current locale).
Only then follows the process, when based on the detected information (the internal state of the router) the action is performed, which effectively means sending a response to the http request.
And even though the activity performed based on the evaluation is not an integral part of the router, it is an activity directly dependent on the router and it demonstrates well what the router evaluates. This last step can be found in the Jet\Application class, runMVC() method.
Method Application::runMVC()
You may have wondered what the Jet\Application class is doing here, since we are talking about MVC. As mentioned above, the router just evaluates the URL and sets the necessary state.
Let's take an example. The router detects if it is a redirect. That is, it finds out that the given URL is valid, but it should be redirected elsewhere (let's not go into the reason why). But how will the redirect be done in the end? That is, who, what and how will send the necessary http headers? And will the http headers actually be sent? This is no longer a matter of MVC, but purely a matter of your application. Yes, in most cases you just send the headers and you're done. But what if you don't care about redirects, but want a preview page for your CMS, for example? Or what if the redirect needs to perform some operation that Jet itself can't do?
And another example: how about a page that requires login and authentication. Sure, there's a standard way to handle this in Jet, and we'll show that next. But again, nowhere is it written that this has to be handled in your application in exactly the way Jet does it by default. That would limit you and take away your freedom and leeway.
It is for these reasons that the activity performed by the router discovery is completely separate from the MVC and actually belongs to the application space. In Jet there is a default implementation for handling the entire processing sequence that should be sufficient for most situations - it is the Application::runMVC() method that is called from ~/application/bootstrap.php. However, the layout makes it clear that you can implement your own sequence and final request processing logic.
And for the sake of illustration, it is best to look at this simple method directly:
public static function runMVC( ?string $URL = null ): void
{
Debug_Profiler::blockStart( 'MVC router - Init and resolve' );
$router = MVC::getRouter();
$router->resolve( $URL );
Debug_Profiler::blockEnd( 'MVC router - Init and resolve' );
if( $router->getIsRedirect() ) {
Http_Headers::redirect(
$router->getRedirectType(),
$router->getRedirectTargetURL()
);
}
if( $router->getHasUnusedUrlPath() ) {
Http_Headers::movedPermanently( $router->getValidUrl() );
}
if( $router->getIs404() ) {
ErrorPages::handleNotFound( false );
return;
}
$base = $router->getBase();
$locale = $router->getLocale()
$page = $router->getPage();
if( !$base->getIsActive() ) {
ErrorPages::handleServiceUnavailable( false );
return;
}
if( !$base->getLocalizedData( $locale )->getIsActive() ) {
ErrorPages::handleNotFound( false );
return;
}
if( !$page->getIsActive() ) {
ErrorPages::handleNotFound( false );
return;
}
if(
$page->getSSLRequired() &&
!Http_Request::isHttps()
) {
Http_Headers::movedPermanently( Http_Request::URL(
include_query_string: true,
force_SSL: true
) );
}
if( $router->getLoginRequired() ) {
Auth::handleLogin();
return;
}
if( $router->getAccessNotAllowed() ) {
ErrorPages::handleUnauthorized( false );
return;
}
$result = $page->render();
$page->handleHttpHeaders();
echo $result;
}
The code should show how the final reporting is done by default. But a few important things need to be pointed out.
- A router is a singleton whose instance is held by the Jet\MVC class. Thus, the router must not be somewhere in the air, for example as a global variable, and there must always be just one active instance of the router. Jet works with the router in the same way your application does - it uses it. So this is key.
- You can see that a block is created for the profiler on the decision process (resolv). The decision process must be fast. So that block is very important.
- And let me remind you that you can do all this in the application space as you want. For example, does the default strategy not suit you in a situation where, for example, a language is inactive? There is nothing stopping you from making your own sequence and your own runMVC method.
Before we get into the router itself, it's important to talk about the Jet\MVC class. And then you can take a closer look at the Jet\MVC_Router class.