what is the exact purpose of Router->constructUrl ? does it use any caching or performance helpers i should ?

pn-pm
Member | 13
+
0
-

I wonder basically, having custom router, why is there any redirect? Can i keep the original request and just change the presenter and action instead redirection from returned value from constructUrl()?

Example:I have a custom router class, testing as first, in router factory (looking for post in db):

// pass db to router so it can be used in DbRouter
public static function createRouter(\Nette\Database\Explorer $database): RouteList
{	$router = new RouteList;
	$router->add(new DbRouter($database)); // <<-- search url in db
	$router->addRoute('/', 'Home:default');
	// .. some more ..
	$router->addRoute('<presenter>/<action>', '<presenter>:<action>');
	return $router;
}

then i have class DbRouter with construct receiving the db reference, and testing url ike this:

public function match(HttpRequest $httpRequest): ?array
{	$slug = Strings::trim($httpRequest->getUrl()->getPath(), '/');
	$ret = $this->database->table('posts')->where(['url'=>$slug]);
	if($ret->count() == 1){
			return ['presenter' => 'Home','action' => 'viewPage','slug' => $slug,'queryString' => $httpRequest->getQuery()];
	}
	return null; // if not found
}
public function constructUrl(array $params, UrlScript $refUrl): ?string
{	if (!array_key_exists('slug', $params)) { return null;
	return $refUrl->getHostUrl() . '/' . $params['slug'];
}

if found article with matching slug, for example abc/def, it redirects to return value of constructorUrl:

// trying this URL
'/abc/def?foo=bar'
// redirects to
'/abc/def' // (strips query string).
// if in constructUrl return null, then redirection is without "nice urls", adding presenter and action + the params array as queryString
'/home/view-page?slug=abc%2Fdef&queryString%5Bfoo%5D=bar'

the return value works as redirect, why does it need to redirect and not just point to proper presenter without redirection? Is there such way to keep original request? Maybe including post variables and any query parameters? As matched by match() function? How can i keep orifinal url instead redirection?

public function constructUrl(array $params, UrlScript $refUrl): ?string
{	if (!array_key_exists('slug', $params)) { return null; }
	$paramsUrl = ''; // adding here query params to url to be returned
	if(!empty($params['queryString']) && is_array($params['queryString'])){
		$paramsUrl = '?'; $c = 0;
		foreach($params['queryString'] as $k=>$v){ $c++; $paramsUrl.=($c==1?'':'&amp;').$k.'='.$v; }
	}
	// return null; // would redirect to /<presenter>/<action>?<params>
	return $refUrl->getHostUrl() . '/' . $params['slug'] . $paramsUrl; // Redirects to this URL
}

Last edited by pn-pm (2023-07-24 11:51)

Marek Bartoš
Nette Blogger | 1184
+
+1
-

Don't complicate your life and just add a route that matches format of the slug in database.
You can handle the database connection and errors in presenter

This route would match anything, including slashes

$router->addRoute('<slug .+>', 'Post:default');

Last edited by Marek Bartoš (2023-07-24 12:02)

jeremy
Member | 53
+
0
-

@pn-pm
I don't think the router itself should be dealing with database matching slugs at all.
You just simply create a router:

$defaults = [
	'presenter' => 'Home',
	'action' => 'default',
	'slug' => ''
];

$router->addRoute("? slug=<slug.+>", $defaults, Route::ONE_WAY);
$router->addRoute('<slug .+>/', $defaults);

And in HomePresenter.php:

public function actionDefault(string $slug): void {
	$article = $this->articles->getByUrl($slug);

	// If no article was found send 404 response ($this->error())
	...
}

The value of the “slug” parameter in the url is automatically passed to “actionDefault” and there is where you can check and find an article. I believe I wrote pretty much the same thing in your other question “custom url router working also in test mode”.
The router this way will take care of pretty urls, and it will keep additional query parameters, if any.
For example /?slug=abd/def&anotherParam=value will become /abc/def/?anotherParam=value.

Last edited by jeremy (2023-07-24 12:20)

m.brecher
Generous Backer | 774
+
0
-

@pn-pm

I agree with @Jeremy, that it is not good way if router deals with slugs in database. The best way how to use router is use it only for matching presenter and action from and extract parameters including slug from url. The rest should be done in presenter – query database, search slug in db table, etc…

Last edited by m.brecher (2023-07-24 12:49)

m.brecher
Generous Backer | 774
+
0
-

@pn-pm

I wonder basically, having custom router, why is there any redirect?

Nette redirects duplicite urls to canonical url – this is desirable default behaviour. Redirect is not done by router itself, but the process od canonization is like this:

1) router matches first route and calls corresponding presenter
2) presenter check on duplicity – calls linkGenerator and this creates url from current request
3) if the current URL is different from url from linkGenerator, makes redirection

So the redirection is not done in router, but in presenter, regardless of whether you have your own router or a Nette router

pn-pm
Member | 13
+
0
-

@Jeremy thanks, this seems to do what i need but please help me understand

$router->addRoute('<slug .+>', 'Home:viewPage'); // works ok, matches also slash-having urls, like /abc/def
// must comment out route <presenter>/<action> bc it would have catched it

this behaves as expected, but i have to first add all real presenters manually before it, like

$router->addRoute('/admin/[/<action>][/<slug>]', 'Admin:<action>');
$router->addRoute('/auth/[<action>][/<slug>]', 'Auth:<action>');
$router->addRoute('<slug .+>', 'Home:viewPage');

that one BAD thing i see on nette, there is no way on the router to force match only if presenter exists? example, /abc/def matches as presenter Abc, altough no presenter of such name is defined, is there any operator to match ONLY EXISTING PRESENTERS in the general <presenter>/<action> route and skip to next rule if not found?

what exactly means this other line ?

$router->addRoute("? slug=<slug.+>", $defaults, Nette\Application\Routers\Route::ONE_WAY);

i still fail to understand the ONE_WAY mening here – i read its to mtch old urls i want to work but not use, but why use it here, and what is the first argument “? slug=<slug.+>” doing

Last edited by pn-pm (2023-07-24 13:22)

jeremy
Member | 53
+
+1
-

@pn-pm

that one BAD thing i see on nette, there is no way on the router to force match only if presenter exists?

That is not the purpose of the router, it's only supposed to get the url parameters, it doesn't really know what the parameters are for.
Even <presenter> and <action> are just url paramaters, nette then processes them and loads the presenter and action if they exist, otherwise it throws error 404.

If you want something like this:

$router->addRoute('<presenter>/<action>/', ...);
$router->addRoute('<slug .+>/', ...);

Then I don't think that's possible because url like /abc/def will always match as presenter Abc and action def.

In my own application I have something like this:

$router->addRoute('admin/<presenter>/<action>/', $adminDefaults);
$router->addRoute('<slug .+>/', $frontendDefaults);

what exactly means this other line ?

$router->addRoute("? slug=<slug.+>", $defaults, Nette\Application\Routers\Route::ONE_WAY);

All it does is it redirects ?slug=abc/def to /abc/def/. It prevents having 2 different urls point to the same content.

Last edited by jeremy (2023-07-24 13:53)

Marek Bartoš
Nette Blogger | 1184
+
0
-

that one BAD thing i see on nette, there is no way on the router to force match only if presenter exists?

It may seem like a good idea to implement it and it is possible, but it has the same problem like your database router. You can't rely on the routes order. The second you add a new post or a new presenter, route matches something that was previously matched by a route bellow.

All it does is it redirects ?slug=abc/def to /abc/def/. It prevents having 2 different urls point to the same content.

It's a bit more complicated. One way route matches the incoming url but never generates it. Presenter, when it has canonization enabled, generates link to itself via router. Router skips the one way route, generates url via the second one. Presenter recognizes the current and generated urls do not match and then redirects.

Last edited by Marek Bartoš (2023-07-24 13:54)