Implementing a dynamic custom route

I have a database with table that contains pages with their slug names. If I have urls like this:

/page/child_page_a/child_page_b/child_page_c/

I’m wondering how best to setup a route for pages that can be any number deep. I don’t want to match just the last url segment since this could be named the same in another url. Is the best solution to pull all db rows that match the last field and then match parents all the way back in each of those till I find a perfect match?

I’m trying to find the most efficient way of doing this as I don’t want to hit the database a bunch or pull all the pages from the db for comparison. Just not sure how to best do that. I also don’t want to store the whole url as the page url for a bunch of reasons (one of which is that if, for instance, ‘child_page_a’ got renamed then it’d break all the other links).

Thoughts?

I once had the same requirement for nested slugs in the URL in an online shop and I did just what you don’t want to do - stored the whole URL path in the category table. Then resolving the route is just a matter of a simple query involving an indexed column - it’s very fast, and if you utilize mysql query cache then it’s lightning fast! I don’t know if anything can be faster than that (maybe apart from some cache solutions in memory).

As to breaking other links when renaming I simply implemented appropriate logic in the code that updates the category table and if the slug name has changed then the application scans and updates all other related slugs (actually, I think it updates all slugs because the code is simpler and still fast enough). Usually changing the name is a rare occurrence so spending a fraction of a second more in these cases doesn’t hurt anything. Of course, it depends on your use case and category table size - if updates are more frequent then reads then a different approach would be suitable.

Same here.

Typical pattern for this is to have an alias table that maps an “alias” to the actual route/controller/method.

I had to build something similar for a project recently and this is what I did.

One table:

CREATE TABLE `cms_router` (
  `id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `entity_type` varchar(128) NOT NULL COMMENT 'Context of entity_id for polymorphic relationship.',
  `entity_id` bigint(20) unsigned NOT NULL COMMENT 'Polymorphic reference to page, category, or whatever else primary key.',
  `rule_path` varchar(255) DEFAULT NULL COMMENT 'Application path associated with this route.',
  `created_at` timestamp NULL DEFAULT NULL,
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `rule_path` (`rule_path`),
  KEY `entity_type` (`entity_type`,`entity_id`)
) ENGINE=InnoDB AUTO_INCREMENT=111 DEFAULT CHARSET=utf8

Example data
1 category 1 /download 2016-04-05 13:46:52 2016-04-05 13:46:52
5 page 3 /download/claim_form 2016-04-05 13:46:52 2016-04-05 13:46:52
6 category 6 /download/images 2016-04-05 13:46:52 2016-04-05 13:46:52
7 page 4 /download/all_images/Parts 2016-04-05 13:46:52 2016-04-05 13:46:52
8 page 5 /download/all_images/Service 2016-04-05 13:46:52 2016-04-05 13:46:52
9 category 9 /download/accessories 2016-04-05 13:46:52 2016-04-05 13:46:52
10 page 6 /download/accessories/A4 2016-04-05 13:46:52 2016-04-05 13:46:52
11 page 7 /download/accessories/A5 2016-04-05 13:46:52 2016-04-05 13:46:52
12 page 8 /download/accessories/A6 2016-04-05 13:46:52 2016-04-05 13:46:52
13 page 9 /download/accessories/A7 2016-04-05 13:46:52 2016-04-05 13:46:52

Then it is just a mater of matching the current request URI to a row in the router table.

I just came across a different method. With this example URL:

/Page1/SubPage1/SubSubPage1

You can access it via one query like so:

SELECT p1.id
FROM pages AS p1, pages AS p2, pages AS p3
WHERE p1.PageName = ‘SubSubPage1’ AND p1.parentID = p2.ID AND
p2.PageName = ‘SubPage1’ AND p2.parentID = p3.ID AND
p3.PageName = ‘Page1’

So here is the code I ended up using (where $url is equal to the url segment after the domain name):

	// Get segments in reverse order
		$parts = array_reverse( explode('/', $url) );

		$from = $where = '';
		for($i=1; $i <= count($parts); $i++){
			if( $i!=1 ){
				$from.= ', ';
				$where.= ' AND ';
			}
			$from.= 'pages as p'.$i;
			$where.= 'p'.$i.'.slug = ?';
			if( $i < count($parts) ) $where.= ' AND p'.$i.'.parent_id = p'.($i+1).'.id';
			else $where.= ' AND p'.$i.'.parent_id = 0';

		}


		// GET PAGE FROM DB
		$q = $this->db->query('SELECT p1.* FROM '.$from.' WHERE '.$where, $parts);

I’m using CodeIgniter so in the query the $parts array values replace all the ‘?’ symbols (for those that were wondering).

This seems to be efficient enough to get it all in one query. I like that. Just a note but by doing it this why, you’d want to put an index on that slug field. That’s an area that could be slowed down in that query.

This topic was automatically closed 91 days after the last reply. New replies are no longer allowed.