? Success

User tests: Successful: Unsuccessful:

avatar Hackwar
Hackwar
16 Dec 2014

This PR implements a new base class for component routers, which can be used to create rules based routers that are dynamically extendable.

The idea

Components that stick to the Joomla MVC style should be able to create a fully working router with as little work as possible. At the same time, component routers should only handle views, since those are the only ones that you should be able to link to. If you have something that is not a view, it should execute and then redirect to a view again. This means that we only have to create SEF URLs for views. Views are pretty well defined, too.

How it works

A router extends from this class and in its constructor, it registers all its views with a bunch of its data and the hierarchy between them. The idea is, that views form a tree hierarchy, where traversing along the tree towards the leafs gets you higher and higher in the hierarchy. In case of com_content, the articles and its article view would represent the leafs of the tree, the categories with their category view represent the nodes of the tree and the categories view, which displays all categories, would represent the root of the tree.
So we already have a kind of hierarchy in our views and we register them accordingly in our router. We start with the categories view. That has no id, has no parent view and is not nestable. Then we have the category view, which has the ID "id" in the query, has the categories view as a parent view and that view is nestable. This means that while traversing along the above described tree, we can have a node that is a category view and its child-nodes are also category views.
Based on this, we can then find the right Itemid for example (by starting from the node given by our query, for example the article view and its ID and traversing along the tree to its root, all the while comparing the menu items that we have with the current node that we are in) and/or add the category hierarchy from that menu item to the URL and last but not least add the article slug to it.
Since we are not registering "we have categories and content items" but a flexible hierarchy, you could in theory go as far as having categories that contain tags which again contain content items and those contain something else again. So the hierarchy is not limited to the category/content-item system that we have in Joomla right now, but allows for a lot more.

A specific example could look like this:

    public function __construct()
    {
        $categories = new JComponentRouterViewconfiguration('categories');
        $this->registerView($categories);
        $category = new JComponentRouterViewconfiguration('category');
        $category->setKey('id')->setParent($categories)->setNestable()->addLayout('blog');
        $this->registerView($category);
        $article = new JComponentRouterViewconfiguration('article');
        $article->setKey('id')->setParent($category, 'catid');
        $this->registerView($article);
        $this->registerView(new JComponentRouterViewconfiguration('archive'));
        $this->registerView(new JComponentRouterViewconfiguration('featured'));
        $this->registerView(new JComponentRouterViewconfiguration('form'));

        /** Insert adding rules to process the URLs here **/
    }

The rules

Based on the hierarchy that you have to define above, we can then write rules that process the input query to transform it to the SEF URL that we want. You can see a rule that finds the right Itemid for a query in #5446. This means, that we don't need a component specific code for this in the ContentHelperRoute classes.
Similarly the current routers behavior can be combined into a generalized rule that works of of the hierarchy that was described above. That means, that we can summarize all the code in our core routers in those rules and instead of having 11 component routers, we would only have one rule that implements this specific logic. You can see this in #5533.
I have to admit, those rules get pretty complex and difficult to maintain, but it would mean that no component developer needs to write his own router anymore, but can use the core rules instead.

The component router

A specific component router could simply consist of the code that I posted above. Adding a plugin event to it and some further logic, and we would specifically only have this:

/**
 * Content Router
 */
class ContentRouter extends JComponentRouterAdvanced
{
    function __construct()
    {
        $categories = new JComponentRouterViewconfiguration('categories');
        $this->registerView($categories);
        $category = new JComponentRouterViewconfiguration('category');
        $category->setKey('id')->setParent($categories)->setNestable()->addLayout('blog');
        $this->registerView($category);
        $article = new JComponentRouterViewconfiguration('article');
        $article->setKey('id')->setParent($category, 'catid');
        $this->registerView($article);
        $this->registerView(new JComponentRouterViewconfiguration('archive'));
        $this->registerView(new JComponentRouterViewconfiguration('featured'));
        $this->registerView(new JComponentRouterViewconfiguration('form'));
        parent::__construct();
    }
}

This would be all that is needed for our core routers. There are however some things that I would propose for the future and that would allow to make the system more robust and a bit more flexible.
In order for the system to be able to retrieve the right data, I think it would be good to introduce router specific getters for things like the article ID/slug. So a com_content router would have a method getArticle() that either returns the ID of the article that the slug and the category ID belongs to or that returns the ID with the slug attached, based on the input. I'm not sure about the correct interface here yet. The getCategory() method is a generalized version for all category related views. The system that I have in mind expects an interface like that from JCategoryNode for all nestable views. Since I don't like the JCategoryNode implementation very much anymore, since its too category specific, I started a discussion on a node interface in #5227. Feel free to join in there for this part.

getPath()

The getPath() method in this class returns a path of views from the node/leaf that is given by the query to the root of the tree-hierarchy. This allows us to simply check if each entry for example corresponds to a menu item and thus set the right Itemid or to add a segment to our url from that matching menu-path item to our original node/leaf defined by the query. It's a helper method that I think a lot of rules will make use of.

Current issues

Since there is no code yet in this PR to test this with, I have to point you to #5446 and #5533. I'd also like to ask for a code-review by the maintainers and merging this based on that code review.

This was made possible through the generous donation of the people mentioned in the following link via an Indiegogo campaign: http://joomlager.de/crowdfunding/5-contributors

avatar Hackwar Hackwar - open - 16 Dec 2014
avatar jissues-bot jissues-bot - change - 16 Dec 2014
Labels Added: ?
avatar brianteeman brianteeman - change - 16 Dec 2014
Category Router / SEF
avatar johanjanssens
johanjanssens - comment - 16 Dec 2014

@Hackwar Thanks, read the proposal and added a first set of comments on the code as proposed. One first question though.Why did you choose the proposed implementation using a path based solution for routing over a regex based routing approach ?

avatar Hackwar
Hackwar - comment - 17 Dec 2014

First of all, I want this advanced router to be optional. If a component does not want to use this router or does not fit into the schema proposed here, they can still use their own implementation. This also means that the behavior up until the component router is called, has to be the same as now. Always remember the backwards compatibility that we have to adhere to in 3.x :wink: So we already have to find the right menu item when parsing the URL.

Then the goal also is to keep the same URLs for the system right now, so I can't start with a clean slate here and drop the menu path or category path completely.

Last but not least, I honestly wouldn't know how to write a regex for the system that we have. As far as I can see, you would need a different regex depending on the menu item that is associated with this query and that would make it rather ... difficult. Or we could have a regex that represents the hierarchy that you have to set up in your component controllers constructor, but that would only be a different representation of the same thing and considering MY issues with regex, I would opt for the simpler code solution in PHP for our developers.
To clarify: I looked at implementations like in AngularJS and Play and those are good for a complete application, but we are talking about components and not the whole application and I think that is where a regex solution would fall flat.

Last but not least, this approach with registerView allows us to extend a router dynamically. You could add your own view to a component (there are different ways to do that) and simply add that view into the component router via a plugin and the router would automatically adapt and take that new view into account.

avatar johanjanssens
johanjanssens - comment - 24 Dec 2014

Hannes, thanks for the feedback. I agree with you that for Joomla a none regex based routing approach is better. Just wanted to hear if you did research the regex and what you decided.

avatar Hackwar
Hackwar - comment - 29 Dec 2014

I've added a JComponentRouterViewconfiguration class in regards to @johanjanssens and @ercanozkaya comments. I'm looking for some feedback from you guys, before I finalise this with proper comments and codestyle.

avatar Hackwar
Hackwar - comment - 31 Dec 2014

I've written unittests for all classes, rewritten a few of them again and removed the ability to have one view under several names in the system. I was hoping that I would be able to make this work, but it is logically impossible to solve this. It also has the benefit that this makes the whole implementation a bit simpler and removes a layer of indirection.

With that said, I'd ask you to go over this one more time, @johanjanssens, @mbabker, @wilsonge

I will update the PRs depending on this one asap.

avatar Hackwar Hackwar - change - 3 Jan 2015
The description was changed
avatar wilsonge
wilsonge - comment - 3 Jan 2015

Hannes why have you decided to choose a configuration based on views rather than say tasks. Generally we will be wanting routes to point to a controller task rather than to a view. Suddenly you need a different view for add and edit for example.

I get how this will make things easier for smaller components with a maybe like 4 views like in the CMS. But when you move to larger components which might have like 20 views or so (think e-commerce, subscriptions as an example) then suddenly things become super complex with this.

avatar Hackwar
Hackwar - comment - 3 Jan 2015

I chose views because those are the things that we are creating URLs for. Whenever you have a task, the user will click the URL or the form will be send to that URL and after that the user will be redirected to a view. You will never see a task URL in your adress bar in the browser.

avatar Hackwar
Hackwar - comment - 3 Jan 2015

To extend my answer a bit: We are currently only creating SEF URLs for views in Joomla. There are no SEF URLs for tasks. And especially for large components with 20+ views, its gets actually a lot easier. They only have to create the constructor where they register those 20 views and that is basically it. This makes their routers a lot shorter and they especially don't have to pay attention to the logic.

avatar Bakual
Bakual - comment - 3 Jan 2015

I actually have SEF URLs for tasks in my extension. So only because core doesn't use it, doesn't mean it isn't used anywhere. Just saying :smile:

avatar wilsonge wilsonge - close - 3 Jan 2015
avatar wilsonge wilsonge - close - 3 Jan 2015
avatar wilsonge wilsonge - reopen - 3 Jan 2015
avatar wilsonge wilsonge - change - 3 Jan 2015
Status Pending Closed
Closed_Date 0000-00-00 00:00:00 2015-01-03 23:49:03
avatar wilsonge wilsonge - reopen - 3 Jan 2015
avatar wilsonge wilsonge - change - 3 Jan 2015
Status Closed New
avatar Hackwar
Hackwar - comment - 4 Jan 2015

@Bakual sure. But this effort also can't solve all routing for ever and always. This is aimed at most likely 90% of the extensions out there. The other 10% can still write/use the same router as they did the last 10 years. :wink:

avatar HermanPeeren
HermanPeeren - comment - 4 Jan 2015

SEF = Search Engine Friendly. A search engine should only index URLs with GET-requests and never do any tasks. A GET-request should be safe and never alter any data. Ergo: no use to implement tasks in a search engine friendly way.

avatar Bakual
Bakual - comment - 4 Jan 2015

SEF = Search Engine Friendly. A search engine should only index URLs with GET-requests and never do any tasks. A GET-request should be safe and never alter any data. Ergo: no use to implement tasks in a search engine friendly way.

Just an example: I use a task to download a file. There is no reason to use a view there. However the link is fine to be indexed and thus SEF makes sense.

avatar HermanPeeren
HermanPeeren - comment - 4 Jan 2015

An URL to download a file = a Uniform Resource Locator to GET the file. The task is already in the GET, no need to specify an extra task. That is how HTTP is intended.

avatar Bakual
Bakual - comment - 4 Jan 2015

There are reasons why I don't directly link to the file :)

avatar ercanozkaya
ercanozkaya - comment - 4 Jan 2015

@Hackwar JComponentRouterViewconfiguration is looking very good. One remark: setParent method sets the child_key in the parent class. This might get out of sync when one view has multiple children with different keys.

avatar ercanozkaya
ercanozkaya - comment - 4 Jan 2015

@wilsonge @Bakual A task is merely an action performed on a resource and it should be sent to the resource URL itself. So a router should work on resources, not tasks.

About your example; a file download is a resource and should have its own view and URL. The task is GET here as Herman explained.

avatar Bakual
Bakual - comment - 4 Jan 2015

In Joomla, we use the task parameter to specify a controller method to be executed. For example &task=file.download would execute the download method of the file controller. By default this has the value display which will usually load a view.
This has nothing to do with the HTTP request methods (GET, POST, PUT, ...).

avatar Hackwar
Hackwar - comment - 4 Jan 2015

@ercanozkaya JComponentRouterViewconfiguration saves the child keys in an array. Yes, child views might have different parent keys, but they simply put this into the array and that is it.

avatar Hackwar
Hackwar - comment - 4 Jan 2015

@Bakual I disagree here. Just because Joomla is confusing a lot of things in its architecture and controls most of the stuff via the task URL parameter, that doesn't mean that the REST methods are not the theoretical background of it. There is a lot of scientific research regarding this and it shows that all actions with a content item can be reduced to the REST methods. That said, everything that displays something or retrieves something without changing the data, is a GET. And those URLs we should SEF. The rest not.

avatar HermanPeeren
HermanPeeren - comment - 4 Jan 2015

@Bakual That is a nice summary of what is basicly wrong in Joomla (esp. since 1.6) and an explanation why we still have no RESTful interface in core. ;-) But as that is not a very productive comment of me in this discussion I'll stop about this.

Your download-task is not a task in the same way as a create, update or delete. It doesn't change anything to the resource. It merely just gives some output. Not a HTML-output, but a file, that is another representation. In MVC you can still call that a view. And if it is safe to have it indexed by a search engine, you should use a GET for it, not a POST; otherwise it is of no use to make a SEF URL.

Oh, Hannes was just faster than I... ;-)

avatar Bakual
Bakual - comment - 4 Jan 2015

I think it gets a bit offtopic now.
I just wanted to show that there are cases where extensions will have SEF URLs for other things than views. If that is architectural correct or not doesn't matter much. It just has to be possible with the new routers as well.

avatar Hackwar
Hackwar - comment - 4 Jan 2015

As I've written before, this advanced router is an offering to developers. It should make their lives a lot easier, but they also will have to stick to the implicit structure that is required by this router. This means, that you have to use the views and nothing else. If you want to route by something else, you either have to write your own router like those that we have right now or write your own router rules. But that is the reason why it is called "advanced router". If that is not your style, you can still write a simple router, as long as it adheres to JComponentRouterInterface or as long as it is the 1.5 to 3.2-style function based routers.

avatar Bakual
Bakual - comment - 4 Jan 2015

I don't think I like this restriction myself, but I will likely need to do my own router anyway.

What about having multiple trees? Like for example I have in my component three different types of content types (sermons, series and speakers) where each of them has categories. But there is no shared root for them. So I would need to build three trees. I guess that's not possible with that router and I have to extend it or build an own myself?

avatar Hackwar
Hackwar - comment - 4 Jan 2015

You can have as many trees as you want. As long as the category view is not named the same for all 3, its okay.

avatar Bakual
Bakual - comment - 4 Jan 2015

Thanks, that's great!

avatar johanjanssens
johanjanssens - comment - 12 Jan 2015

@Bakual @wilsonge Routing in Joomla has always been 'view' centric, this is by design and for very good reasons. The views represents a resource.

The routing in Joomla is called resource routing, in this routing scheme the router matches the url to a resource. Ideally only the HTTP methods are used. Joomla 1.5 didn't do this yet, however it was build to move towards this.

None resource routing is possible with Joomla, but not implemented in the core. In this routing scheme a url would be matched to an action on a controller. None resource routing should be avoided as much as possible.

For more info on both also read the Rails routing documentation which covers both forms of routing in details. In Rails like in Joomla resource routing is the default.

@Bakual An additional note about &task=file.download you mentioned.

This is one of the bigger architectural mistakes that have crept in Joomla after 1.5. This change devaluated Joomla back to the swamp of POX, or level 0 following the Richardson Rest maturity model

The task variable includes not only the action to be performed it also includes the controller to call the action on. You cannot go much worse then that.

Whoever did this, obviously had no clue how the web works and set a very bad example for extension developers. I see many extensions to day who implement tasks like this. As @HermanPeeren correctly points out the change is one of the main reasons why Joomla still doesn't have a proper REST interface.

In short

Joomla 1.5 was build specifically to be able to work at level 1 of the RMM model; being the level of resources. A 'view' represents a resource, and the router is responsible to find the correct resource/component pair. Routers are 'view' centric, not controller centric, from the view the controller is found, not the other way around.

In future to move towards level 2 and 3 of RMM and to become fully Hypermedia aware Joomla should get rid of the 'task' URL query parameter and adopt the HTTP methods as the default actions across all of it's components. This will allow components to become RESTful out of the box.

In your example of &task=file.download the correct request would be GET /downloads/myfile.doc HTTP/1.1which is level 2 in RMM.

Example

I often see developers claim that using only the HTTP methods doesn't work for their extension. I disagree.

In Nooku we have adopted a BREAD (browse, read, edit, add, delete) paradigm for all controller actions. Nooku's http dispatcher maps HTTP methods to controller actions on the fly. We have found that we can cover any scenario's with only those 5 actions.

Notes

New methods are also being proposed and added to HTTP

In those very few scenario's where the default HTTP methods are insufficient WebDav could offer a solution.

@Hannes Good work on the routing changes.

avatar Hackwar
Hackwar - comment - 21 Jan 2015

I've implemented the feedback from @johanjanssens and @mbabker into this last commit. Please review this. If this is accepted, I'm going to update the other PRs.

avatar johanjanssens
johanjanssens - comment - 21 Jan 2015

@Hackwar Good work. I appreciate the name change to JComponentRouterView. No immediate and urgent remarks from me on the PR. Will do one final pass tonight.

avatar wilsonge wilsonge - change - 21 Jan 2015
Milestone Added:
avatar johanjanssens
johanjanssens - comment - 22 Jan 2015

@Hackwar Added 2 more points of feedback. Things are shaping up very nicely.

avatar Hackwar
Hackwar - comment - 22 Mar 2015

@phproberto I updated the branch to latest staging and added the "covers" notation to the unittests. Can we merge this now? :smile:

avatar phproberto phproberto - reference | - 26 Mar 15
avatar phproberto
phproberto - comment - 26 Mar 2015

Merged into 3.5-dev branch. Thanks!

avatar phproberto phproberto - change - 26 Mar 2015
Status New Closed
Closed_Date 2015-01-03 23:49:03 2015-03-26 00:04:31
avatar phproberto phproberto - close - 26 Mar 2015
avatar phproberto phproberto - close - 26 Mar 2015
avatar Hackwar
Hackwar - comment - 26 Mar 2015

Thank you!!

avatar roland-d roland-d - reference | b1fa512 - 6 Aug 15
avatar Hackwar Hackwar - head_ref_deleted - 6 Jan 2016

Add a Comment

Login with GitHub to post a comment