2

Context: I have a component that works fine in J3 and I want to migrate it to J4.

Problem: When I create a menu (Joomla main menu) on the frontend view of the component, in J4 only, the URL is not SEF => http://mysite.com/alias-menu?view=view-name instead of http://mysite.com/alias-menu (without parameter by default)

The URL works if I manually remove the parameter, but I want for the main menu of Joomla to generate a link properly (SEF) without any parameter by default.

Based on documentation and other Joomla components, I have added a Router.php, but no change and it is never called. Here is the code of the router.php

/com_componentname/src/Service/Router.php

<?php
namespace Componentname\Component\Componentname\Site\Service;
defined('_JEXEC') or die;

use Joomla\CMS\Component\Router\RouterViewConfiguration;
use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\Rules\StandardRules;
use Joomla\CMS\Component\Router\Rules\MenuRules;
//use Joomla\CMS\Component\Router\Rules\NomenuRules;
use Componentname\Component\Componentname\Site\Service\ComponentnameNomenuRules as NomenuRules;
use Joomla\CMS\Factory;
use Joomla\CMS\Categories\Categories;
use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Categories\CategoryFactoryInterface;
use Joomla\CMS\Categories\CategoryInterface;
use Joomla\Database\DatabaseInterface;
use Joomla\CMS\Menu\AbstractMenu;

class Router extends RouterView
{
    protected $noIDs = false;

    private $categoryFactory;
    private $categoryCache = [];
    private $db;

    public function __construct(SiteApplication $app, AbstractMenu $menu, CategoryFactoryInterface $categoryFactory, DatabaseInterface $db)
    {
        $params = Factory::getApplication()->getParams('com_componentname');
        $this->noIDs = (bool) $params->get('sef_ids');
        $this->categoryFactory = $categoryFactory;
        $this->db = $db;

        $viewname = new RouterViewConfiguration('viewname');
        $this->registerView($viewname);
        
die('here router construct'); //--> never called

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

        $this->attachRule(new MenuRules($this));
        $this->attachRule(new StandardRules($this));
        $this->attachRule(new NomenuRules($this));
    }

    /**
     * @param   array   A named array
     * @return  array
     */
    function ViewnameBuildRoute(&$query) {

die('here router ViewnameBuildRoute'); //--> never called

        $segments = array();

        if (isset($query['task'])) {
            $segments[] = implode('/', explode('.', $query['task']));
            unset($query['task']);
        }
        if (isset($query['view'])) {
            $segments[] = $query['view'];
            unset($query['view']);
        }
        if (isset($query['id'])) {
            $segments[] = $query['id'];
            unset($query['id']);
        }

        return $segments;
    }

    
    function ViewnameParseRoute($segments) {

die('here router ViewnameParseRoute'); //--> never called

        $vars = array();
        // view is always the first element of the array
        $vars['view'] = array_shift($segments);

        while(!empty($segments)) {

            $segment = array_pop($segments);
            if (is_numeric($segment)) {
                $vars['id'] = $segment;
            } else {
                $vars['task'] = $vars['view'] . '.' . $segment;
            }
        }

        return $vars;
    }

}

/com_componentname/src/Service/ComponentnameNomenuRules.php

<?php
namespace Componentname\Component\Componentname\Site\Service;
defined('_JEXEC') or die;

use Joomla\CMS\Component\Router\RouterView;
use Joomla\CMS\Component\Router\Rules\RulesInterface;

/**
 * Rule to process URLs without a menu item
 *
 * @since  3.4
 */
class ComponentnameNomenuRules implements RulesInterface
{

    protected $router;

    public function __construct(RouterView $router)
    {
        $this->router = $router;
    }

    public function preprocess(&$query)
    {
        //$test = 'Test';
    }

    /**
     * Parse a menu-less URL
     *
     * @param   array  &$segments  The URL segments to parse
     * @param   array  &$vars      The vars that result from the segments
     *
     * @return  void
     *
     * @since   3.4
     */
    public function parse(&$segments, &$vars)
    {
        
        $vars['view'] = 'viewname';
        $vars['id'] = substr($segments[0], strpos($segments[0], '-') + 1);
        array_shift($segments);
        array_shift($segments);
        return;
    }

    
    public function build(&$query, &$segments)
    {
        
        if (!isset($query['view']) || (isset($query['view']) && $query['view'] !== 'viewname') || isset($query['format']))
        {
            return;
        }
        $segments[] = $query['view'] . '-' . $query['id'];
        // the last part of the url may be missing
        if (isset($query['slug'])) {
            $segments[] = $query['slug'];
            unset($query['slug']);
        }
        unset($query['view']);
        unset($query['id']);

    }
}

/administrator/components/com_componentname/services/provider.php

<?php
// no direct access
defined('_JEXEC') or die;

use Joomla\CMS\Dispatcher\ComponentDispatcherFactoryInterface;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Joomla\CMS\Component\Router\RouterFactoryInterface;
use Joomla\CMS\Extension\Service\Provider\RouterFactory;

return new class implements ServiceProviderInterface
{

    public function register(Container $container)
    {

        $container->registerServiceProvider(new MVCFactory('\\Componentname\\Component\\Componentname'));
        $container->registerServiceProvider(new ComponentDispatcherFactory('\\Componentname\\Component\\Componentname'));
        $container->registerServiceProvider(new RouterFactory('\\Componentname\\Component\\Componentname'));

        $container->set(
            ComponentInterface::class,
            function (Container $container)
            {
                $component = new MVCComponent($container->get(ComponentDispatcherFactoryInterface::class));

                $component->setMVCFactory($container->get(MVCFactoryInterface::class));
                //tried to register Router but Internal error 500
                //$component->setRouterFactory($container->get(RouterFactoryInterface::class));

                return $component;
            }
        );
    }
};

I have an error 500 if I uncomment line "$component->setRouterFactory($container->get(RouterFactoryInterface::class));" in provider.php above. Appache error : Fatal error: Maximum execution time of 30+2 seconds exceeded (terminated) in /website/administrator/components/my_component/services/provider.php on line 35

In general terms, what triggers the router to call ? I guess that is just the call of component view. There is a difference bewteen the Router.php in J3 and J4, except the location of file into the component structure folder?

I think that there is some logic that I missed on this specific operation in J4.

For information, result of my research:

2
  • I'm getting "Class "Joomla\Component\Content\Site\Service\NomenuRules" not found" error. Unless you have declared NomenuRules class in the same namespace as the router (Componentname\Component\Componentname\Site\Service), you should be getting the same error. If you're not getting it, it means the router isn't used at all.
    – Sharky
    Oct 3, 2022 at 5:41
  • @Sharky, Thank you for you comment. I have edited my question to include all last tests (with the class missing) Yes my problem is that the router is never called, even after my last tests.
    – Yann
    Oct 4, 2022 at 15:44

1 Answer 1

2

Solution found, all details on https://forum.joomla.org/viewtopic.php?f=831&t=996848

To create a routing in J4 with same behavior that J3, you need to create 4x files :

/com_componentname/src/Service/Router.php

/com_componentname/src/Service/ComponentnameNomenuRules.php

/administrator/components/com_componentname/services/provider.php

/administrator/components/com_componentname/src/Extension/ComponentnameComponent.php

last file not in my first edit :

<?php
namespace Componentname\Component\Componentname\Administrator\Extension;
defined('_JEXEC') or die;

use Joomla\CMS\Application\SiteApplication;
use Joomla\CMS\Association\AssociationServiceInterface;
use Joomla\CMS\Association\AssociationServiceTrait;
use Joomla\CMS\Categories\CategoryServiceInterface;
use Joomla\CMS\Categories\CategoryServiceTrait;
use Joomla\CMS\Extension\BootableExtensionInterface;
use Joomla\CMS\Extension\MVCComponent;
use Joomla\CMS\HTML\HTMLRegistryAwareTrait;
use FooNamespace\Component\Foos\Administrator\Service\HTML\AdministratorService;
use FooNamespace\Component\Foos\Administrator\Service\HTML\Icon;
use Psr\Container\ContainerInterface;
use Joomla\CMS\Helper\ContentHelper;
use Joomla\CMS\Component\Router\RouterServiceInterface;
use Joomla\CMS\Component\Router\RouterServiceTrait;

/**
 * Component class for com_foos
 *
 * @since  __BUMP_VERSION__
 */
class ComponentnameComponent extends MVCComponent implements BootableExtensionInterface, CategoryServiceInterface, AssociationServiceInterface, RouterServiceInterface
{
    use CategoryServiceTrait;
    use AssociationServiceTrait;
    use HTMLRegistryAwareTrait;
    use RouterServiceTrait;

    /**
     * Booting the extension. This is the function to set up the environment of the extension like
     * registering new class loaders, etc.
     *
     * If required, some initial set up can be done from services of the container, eg.
     * registering HTML services.
     *
     * @param   ContainerInterface  $container  The container
     *
     * @return  void
     *
     * @since   __BUMP_VERSION__
     */
    public function boot(ContainerInterface $container)
    {
        //$this->getRegistry()->register('foosadministrator', new AdministratorService);
        //$this->getRegistry()->register('fooicon', new Icon($container->get(SiteApplication::class)));
    }

    /**
     * Adds Count Items for Category Manager.
     *
     * @param   \stdClass[]  $items    The category objects
     * @param   string       $section  The section
     *
     * @return  void
     *
     * @since   __BUMP_VERSION__
     */
    public function countItems(array $items, string $section)
    {
        /*try {
            $config = (object) [
                'related_tbl'   => $this->getTableNameForSection($section),
                'state_col'     => 'published',
                'group_col'     => 'catid',
                'relation_type' => 'category_or_group',
            ];

            ContentHelper::countRelations($items, $config);
        } catch (\Exception $e) {
            // Ignore it
        }*/
    }

    /**
     * Returns the table for the count items functions for the given section.
     *
     * @param   string  $section  The section
     *
     * @return  string|null
     *
     * @since   __BUMP_VERSION__
     */
    protected function getTableNameForSection(string $section = null)
    {
        //return ($section === 'category' ? 'categories' : 'foos_details');
    }

    /**
     * Returns the state column for the count items functions for the given section.
     *
     * @param   string  $section  The section
     *
     * @return  string|null
     *
     * @since   __BUMP_VERSION__
     */
    protected function getStateColumnForSection(string $section = null)
    {
        //return 'published';
    }
}
3
  • Please edit your answer to include the relevant details from the hyperlink as static text. Link-only answers are not good for Stack Exchange communities because the information is lost if the hyperlink ceases to exist at the same path. Oct 8, 2022 at 0:34
  • 1
    @mickmackusa : the links is to a topics in the official Joomla forum and the topics are not deleted. At worst they are archived. The day this happens, the Joomla community will no longer exist and this topic will be useless anyway. I have edited the answer as requested. But I'm not going to put back all the way and the different help I found in the other forums, just the final solution.
    – Yann
    Oct 10, 2022 at 6:04
  • Thank you for fleshing out the answer. The overarching mission in this respect is for Stack Exchange to be the final destination for researchers -- as opposed to being a web traffic router. Citing where stuff comes from and additional resources to leverage is always good; we just never want link-only answers. This is not to make answering harder, but to make life nicer for researchers. Oct 10, 2022 at 6:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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