1

I am having a simple component, basically a list view and a single view. I want to nice URLs so I copy pasted and modified the router.php from com_content like this:

class BookstoreRouter extends JComponentRouterView
{
    public function __construct($app = null, $menu = null)
    {
        $books = new JComponentRouterViewconfiguration('books');
        $books->setNestable();
        $this->registerView($books);

        $book = new JComponentRouterViewconfiguration('book');
        $book->setKey('id')->setParent($books);
        $this->registerView($book);

        parent::__construct($app, $menu);

        $this->attachRule(new JComponentRouterRulesMenu($this));
        $this->attachRule(new JComponentRouterRulesStandard($this));
        $this->attachRule(new JComponentRouterRulesNomenu($this));
    }

    public function getBookSegment($id, $query)
    {
        if (!strpos($id, ':'))
        {
            $db = JFactory::getDbo();
            $dbquery = $db->getQuery(true);
            $dbquery->select($dbquery->qn('alias'))
                ->from($dbquery->qn('#__bookstore_books'))
                ->where('id = ' . $dbquery->q($id));
            $db->setQuery($dbquery);

            $id .= ':' . $db->loadResult();
        }

        return array((int) $id => $id);
    }

    public function getBookId($segment, $query)
    {
       return (int) $segment;
    }
}

There seem to be a couple of problems with this and I can not figure out what it is. The links generated in the list view seems fine but I get these errors:

  1. Warning: array_reverse() expects parameter 1 to be array, boolean given in /var/www/public/libraries/src/Component/Router/Rules/StandardRules.php on line 279

I am not setting a key to the parent view but there is no key to use as far as I understand it. When setting some random key 'foo' the Warning disappears but that solution seems odd.

  1. When calling host\{booklist-menu-alias}\{item-title} the list view is displayed instead of the single view.

Any helpful advice is much appreciated, thanks.

Update

Problem seems to be my parent has no Id of some kind so I am getting this Warning. I can route to the menu item, which is my list view. From there I have a dilemma. I can register the books view, resulting in my totally correct item links will not work. Or I can have only the book view registered and not have the links build in my books view at all.

I did build a legacyrouter.php like the one com_content uses which works fine. But since it is marked as deprecate I really want to make it work with the JComponentRouterView.

  • Have you tried the "A more complex Example" from the Joomla documentation? docs.joomla.org/… – pe7er Nov 11 at 20:06
  • I did see that, I did not try it. Using build and parse seems to be "the old way" of doing this and in my understanding everything should work fine without it. – Tom Nov 12 at 8:12
  • @Tom please take the tour to better acquaint yourself with this community and earn your informed badge. Stack Exchange sites enjoy a very structured page design. All question details belong in the question (nothing else belongs in the question). When you want to post question-resolving information, post a new answer to your question (it is perfectly okay to answer your own question). After posting an educational and complete answer that will benefit future researchers, you may award your own answer with the big green tick to signify that your question is resolved. – mickmackusa Nov 13 at 22:02
  • 1
    From what I can see (diffchecker.com/eByW0skr), you have added $books->setNestable();, altered strpos() line(input variable and the comparison), and fixed the quoting method typos. Please roll back your question edit and transfer your findings to an answer. Thank you for taking the time to share your insights and help other Joomla users. – mickmackusa Nov 13 at 22:12
  • @mickmackusa will do. Didn't know I can Answer my own questions. – Tom Nov 14 at 0:26
1

I won't pretend to believe that this answer will completely resolve your problem, but I do see a couple things to be fixed up.

  1. if (!strpos($id, ':')) is not advisable practice even if it does what you intend to do. To check if a substring exists within a string you should be performing a strict boolean check on false (either !== or ===)

    if (strpos($id, ':') === false)
    
  2. You have typos in your query block. You have written $dbquery->qn() but qn() must be chained to your database object, not your query object.

    $db = JFactory::getDbo();
    $dbquery = $db->getQuery(true)
        ->select($db->qn('alias'))
        ->from($db->qn('#__bookstore_books'))
        ->where('id = ' . $db->q($id));
    $db->setQuery($dbquery);
    
    $id .= ':' . $db->loadResult();
    
  3. I think it is odd that you are casting the string key of your return array as an integer. I don't know what is expected, but it seems like a strange choice. (return array((int) $id => $id);) Based on Pe7er's note to look at https://docs.joomla.org/J3.x:Supporting_SEF_URLs_in_your_component#A_more_complex_Example, I think you mean to return a value (not a key) which is converted to an integer.

    return array('id' => (int)$id);
    

    ...while the technique of casting the $id as an integer will sufficiently trim the non-digital characters from your string, perhaps a more intuitive function call (to other developers who understand the function) might be something like (https://3v4l.org/1ljNP):

    return array(ltrim($id, 'a..z:') => $id);
    

    or a more stable version that explicitly hunts for the colon:

    return array(explode(':', $id)[1] => $id);
    

    Yes, these techniques will have more overhead (but not in a noticeable way; and yes there are other ways of doing the same, but I won't type them all out), but your script will be more informative about what it intends to do.

Please understand that this is a superficial code review and that I haven't had the time to research your issue in depth.

  • 1
    As mentioned in the Question this code mostly is copied from com_content/router.php where routing works just fine. (1) in my opinion something like is_numeric would be a more suitable approach but I don't want to mess with it since it is working fine in the core components. (2) See (1). (3) I assume you want the key to be an integer and (int) will truncate the string part so "3:someslug" will result in {3 => "3:someslug"}. – Tom Nov 12 at 8:24
  • Okay, well, #1 is forgivable if it is intentional (I'll give it the benefit of the doubt), but if #2 is in the Joomla core, then I am embarrassed for the core. #2 will be generating errors and not making queries secure if it can even run. – mickmackusa Nov 12 at 8:28
  • I've been working through com_content/helper/legacyrouter.php where they did #1 and #2 exactly the way you suggested. Don't know why they changed it in the router.php the way they did. – Tom Nov 12 at 14:25
0

After trying every possible way I could imagine I finally found the solution and I've been pretty close with the version I mentioned in this post.

class BookstoreRouter extends JComponentRouterView
{
    public function __construct($app = null, $menu = null)
    {
        $books = new JComponentRouterViewconfiguration('books');
        $this->registerView($books);

        $book = new JComponentRouterViewconfiguration('book');
        $book->setKey('id')->setParent($books);
        $this->registerView($book);

        parent::__construct($app, $menu);

        $this->attachRule(new JComponentRouterRulesMenu($this));
        $this->attachRule(new JComponentRouterRulesStandard($this));
        $this->attachRule(new JComponentRouterRulesNomenu($this));
    }

    public function getBookSegment($id, $query)
    {
        if (strpos($query['id'], ':') === false) {
            $db = JFactory::getDbo();
            $dbquery = $db->getQuery(true);
            $dbquery->select($db->quoteName('alias'))
                ->from($db->quoteName('#__bookstore_books'))
                ->where('id = ' . $db->quote($id));
            $db->setQuery($dbquery);

            $id .= ':' . $db->loadResult();
        }

        return array((int)$id => $id);
    }

    public function getBookId($segment, $query)
    {
        return (int)$segment;
    }
}

In my understanding Nestable was necessary for Parent. Seems I got it all wrong. Works like a charm now.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.