J3.x

Difference between revisions of "Developing an MVC Component/Upgrading to Joomla4"

From Joomla! Documentation

< J3.x:Developing an MVC Component
m (fix typo)
m (fix typos)
Line 106: Line 106:
 
On the other hand class names which aren't fully qualified don't have to be unique, eg <tt>MVCFactory</tt>
 
On the other hand class names which aren't fully qualified don't have to be unique, eg <tt>MVCFactory</tt>
  
* occurs as <tt>Joomla\CMS\Extension\Service\Provider\MVCFactory</tt> in <tt>libraries/src/Extension/Service/Provider\MVCFactory.php</tt>
+
* occurs as <tt>Joomla\CMS\Extension\Service\Provider\MVCFactory</tt> in <tt>libraries/src/Extension/Service/Provider/MVCFactory.php</tt>
  
 
* occurs as <tt>Joomla\CMS\MVC\Factory\MVCFactory</tt> in <tt>libraries/src/MVC/Factory/MVCFactory.php</tt>
 
* occurs as <tt>Joomla\CMS\MVC\Factory\MVCFactory</tt> in <tt>libraries/src/MVC/Factory/MVCFactory.php</tt>
Line 116: Line 116:
 
The API documentation gives the methods available in each of the [https://api.joomla.org/cms-4/namespaces/joomla.html Joomla CMS] and [https://api.joomla.org/framework-2/namespaces/joomla.html Framework] classes, but just be aware that a Joomla CMS class may extend a Framework class, and in this case some methods may not be visible under the Joomla CMS documentation.
 
The API documentation gives the methods available in each of the [https://api.joomla.org/cms-4/namespaces/joomla.html Joomla CMS] and [https://api.joomla.org/framework-2/namespaces/joomla.html Framework] classes, but just be aware that a Joomla CMS class may extend a Framework class, and in this case some methods may not be visible under the Joomla CMS documentation.
  
For example, <tt>$app->setHeader()</tt> which is used to HTTP response code is not listed under the [https://api.joomla.org/cms-4/classes/Joomla-CMS-Application-CMSApplication.html CMS Application class API], but is available as a method because it inherits from the Framework class [https://api.joomla.org/framework-2/classes/Joomla-Application-AbstractWebApplication.html AbstractWebApplication].  
+
For example, <tt>$app->setHeader()</tt> which is used to set the HTTP response code is not listed under the [https://api.joomla.org/cms-4/classes/Joomla-CMS-Application-CMSApplication.html CMS Application class API], but is available as a method because it inherits from the Framework class [https://api.joomla.org/framework-2/classes/Joomla-Application-AbstractWebApplication.html AbstractWebApplication].  
  
 
===Other File Rearrangements===
 
===Other File Rearrangements===

Revision as of 09:29, 21 December 2022

Joomla! 
3.x
Tutorial
Developing an MVC Component



This is a multiple-article series of tutorials on how to develop a Model-View-Controller Component for Joomla! VersionJoomla 3.x.

Begin with the Introduction, and navigate the articles in this series by using the navigation button at the bottom or the box to the right (the Articles in this series).



This tutorial is part of the Developing an MVC Component for Joomla! 3.2 tutorial.

This step covers what's involved in upgrading the tutorial code to align with Joomla version 4.

Page Under Construction![edit]

Introduction[edit]

This page describes the main aspects related to component development which have been introduced in Joomla 4, and details what has been changed in the tutorial code to align with v4.

It's not practical to detail all the code changes on the webpage as has been done for previous tutorial steps. Instead you can find the code as a github repository at Joomla 4 Tutorial code, and can compare this with the code of the previous tutorial step (Adding Custom Fields) at Joomla 3 MVC code. The Joomla 4 code replicates all the functionality of the Joomla 3 code, with the exception of implementing the little penguin icon introduced in Adding decorations to the backend (which isn't so relevant given the change of UI in Joomla 4 Administrator) and the use of a Captcha plugin (Adding a front-end form). You'll probably find it helpful to have this Joomla 4 code at hand to refer to as you read through this page.

The code for this step has been developed by going through each of the previous tutorial steps and trying to get each step working under Joomla 4.2.5. As Joomla 4 is still evolving quite fast it may be that certain aspects cited here get modified during the lifeteim of Joomla 4. Please feel free to correct aspects on this page and create pull requests on the Joomla 4 github repository.

If you're installing the Joomla 4 tutorial code on a new Joomla instance then you will need to configure your instance to make it multilingual and add in the Ajax hidden menuitems as described in the step Using the language filter facility. Also ensure that every helloworld record has a valid category.

Further Reading[edit]

You can find here a tutorial for Joomla 4 component development, similar to this Joomla 3 one, and some excellent Joomla 4 documentation is also available here. Both of these references include descriptions of Joomla 4 impacts on development of other types of Joomla extensions (plugins, modules and templates), which aren't covered here.

This step doesn't cover everything required to update a component from Joomla 3 to Joomla 4, just the aspects covered by the tutorial code. If you're upgrading your own Joomla 3 component then you may find it helpful to look at Potential backward compatibility issues in Joomla 4.

Namespacing[edit]

Component Namespacing[edit]

Namespacing of classes using the PSR-4 recommendation was gradually introduced during Joomla 3 releases, and with Joomla 4 it is pretty much all namespaced.

It is now expected that Joomla extensions use namespacing also, with the base namespace for the extension defined in its manifest XML file. Joomla components use a namespace of the form Joomla\Component\<Component Name> eg Joomla\Component\Content, so following that scheme I've given the tutorial code a base namespace of Robbie\Component\Helloworld. Here's the line in the manifest helloworld.xml file:

   <namespace path="src">Robbie\Component\Helloworld</namespace>

(The path="src" attribute means that it will expect the classes to be under the src subfolder.)

This actually creates 2 namespace prefixes, pointing to specific folders in our helloworld development structure and in our live joomla instance:

Robbie\Component\Helloworld\Administrator – will point to administrator/components/com_helloworld/src on the live instance and is associated with admin/src in our development area.

Robbie\Component\Helloworld\Site – will point to components/com_helloworld/src on the live instance, and is associated with site/src in our development area.

So Joomla now expects all classes names will be found under those base directories, with filename matching the class name, and the fully qualified classname matching the path eg

Robbie\Component\Helloworld\Administrator\Controller\DisplayController will be found in administrator/components/com_helloworld/src/Controller/DisplayController.php

Robbie\Component\Helloworld\Site\Model\CategoryModel will be found in components/com_helloworld/src/Model/CategoryModel.php

When Joomla 3 wanted to call functions in your component it looked for certain filenames in particular folders, and checked that the name of the class in that file matched what it expected.

In Joomla 4 it generates the fully qualified name of the class it wants to find, and then looks to find it in the location where the PSR-4 recommendation would expect it.

Hence the namespace you specify in your class files must align with the file path to your PHP file, below the base src directory at your namespace prefix.

Note in particular how the View class files are now organised.

Also if you use one of the PHP predefined classes (eg Exception) in your code, then add a backslash before it (eg \Exception), otherwise PHP will try and find it in your namespace.

If you use custom Field or Rule definitions in your form xml files then you have to indicate to PHP which namespace prefix to use to look for them, eg in site/forms/add-form.xml:

   addruleprefix="Robbie\Component\Helloworld\Administrator\Rule"
   addfieldprefix="Robbie\Component\Helloworld\Administrator\Field"

and also in admin/src/Model/HelloworldModel::preprocessForm where the associations are added into the form dynamically to form the custom modal_helloworld type field:

   $fieldset->addAttribute('addfieldprefix', 'Robbie\Component\Helloworld\Administrator\Field');

Class Autoloading[edit]

Joomla uses the PHP mechanism for Autoloading Classes and on startup registers a number of autoloaders – functions which get called when PHP can't find a given class. In particular the Joomla PSR-4 autoloader function (in libraries/loader.php) is responsible for finding classes of Joomla extensions whose code is in source files which are organised according to the PSR-4 recommendation. (Joomla library classes are autoloaded using a different autoloader – see libraries/vendor/composer/autoload_static.php).

You can see the namespace prefixes of extensions on the Joomla instance by looking at administrator/cache/autoload_psr4.php. This is generated by Joomla looking through the component, module and plugin directories to find each extension's manifest file, and then processing these xml files to extract the <namespace> element – quite a lot of work, which is why it's cached I guess!

Let's consider what happens when Joomla wants our admin HelloworldController class. It might have something like

   $classname = 'Robbie\Component\Helloworld\Administrator\Controller\HelloworldController'
   if (!class_exists($classname)) { …

Assuming PHP doesn't already know about this class, the class_exists function will result in the classname being passed to the registered autoload function(s). The PSR-4 autoloader will then:

  1. go through its array of namespaces to try and match $classname from the start of the string.
  2. find 'Robbie\Component\Helloworld\Administrator' as a match
  3. see that this maps to "administrator/components/com_helloworld/src"
  4. extract the remainder of the classname: 'Controller\HelloworldController'
  5. treat this as the path down to the class file
  6. and so look for "administrator/components/com_helloworld/src/Controller/HelloworldController.php"
  7. check that in this file there's actually a class with that namespace + class name.

So in this way Joomla can go from a fully qualified class name to find the source code for that class. This makes it easier for us too when the core Joomla code is looking for one of our classes. We still need to know what class it's looking for, but once we know that, we know what we should call our class and where we should save the source code for it.

Joomla Library classes[edit]

Originally Joomla's core classes (beginning with a capital J) were global classes. Over the Joomla 3 versions and into Joomla 4 these migrated to their namespaced equivalents, but the original global classnames were still available as Joomla registered them as aliases (in the libraries/classmap.php file).

In a Joomla 4 extension you can still use these global classnames, except that in a namespaced file you'd have to prefix the name with a backslash to indicate a global class rather than one in your namespace (eg \JFactory instead of JFactory). However it's pretty definite that the aliases will disappear at some stage, so the upgrade to Joomla 4 seems an appropriate opportunity to replace all your component's calls to the global JSomething classes to the namespaced equivalents. This involves adding the appropriate use statements, and all this has been done in the tutorial code. A useful mapping between the two can be found here, where the new qualified classname can be copied and pasted into a use statement in your PHP file.

With reference to the Joomla API Documentation there are 2 Joomla library namespaces:

  • Joomla CMS classes – have namespaces Joomla\CMS\Something – are found under libraries/src/Something
  • Joomla Framework classes – have namespaces Joomla\Something – are found under libraries/vendor/joomla/something/src

So a big advantage of namespacing is that you can immediately determine from the fully qualified class name in a PHP use statement where the source for that class exists in the libraries directories.

On the other hand class names which aren't fully qualified don't have to be unique, eg MVCFactory

  • occurs as Joomla\CMS\Extension\Service\Provider\MVCFactory in libraries/src/Extension/Service/Provider/MVCFactory.php
  • occurs as Joomla\CMS\MVC\Factory\MVCFactory in libraries/src/MVC/Factory/MVCFactory.php

so sometimes you have to be careful to examine the use statement to identify the right class.

Similarly our site/src/Model/FormModel class can have the same FormModel classname as the Joomla FormModel in libraries/src/MVC/Model/FormModel.php.

The API documentation gives the methods available in each of the Joomla CMS and Framework classes, but just be aware that a Joomla CMS class may extend a Framework class, and in this case some methods may not be visible under the Joomla CMS documentation.

For example, $app->setHeader() which is used to set the HTTP response code is not listed under the CMS Application class API, but is available as a method because it inherits from the Framework class AbstractWebApplication.

Other File Rearrangements[edit]

With all of the namespaced classes moving under the src directories, the tmpl layout files and form xml files have been moved out into their own directories.

Extension and Dispatcher classes[edit]

Extension[edit]

In Joomla 3 the point where the Joomla core handed over to your component code was when it ran your helloworld.php code; if it was the frontend it ran your site helloworld.php code, or if was the backend it ran your admin helloworld.php code.

In Joomla 4 the entry point into your component code is your Extension class, both for site and admin, found in admin/src/Extension/HelloworldComponent.php.

When the Joomla core application wants to start executing the component code it creates an instance of HelloworldComponent, just to get a handle on the component code.

Similarly (as described below) other Joomla classes such as Router or Categories need to call certain Helloworld methods (eg custom router functions). In Joomla 3 these methods were written in certain Helper files etc. In Joomla 4 the principle is that the component creates an instance of the HelloworldComponent Extension class, and calls the required methods on that instance.

This Extension class isn't anything like a big container class for the component, it's more like a glue class, providing a way in to the Helloworld component, enabling the Joomla core and other components call the methods which they want to call.

When Joomla core wants to execute the component code, the key method it is looking for in your Extension instance is getDispatcher, and it expects to get back a Dispatcher instance. The HelloworldComponent extends MVCComponent, which in turn extends Component, and it is this class which defines getDispatcher for us.

Dispatcher[edit]

When Joomla wants to run the Hello World component, it gets the Dispatcher instance as described above and then calls the dispatch method on it. It is this dispatch function which replaces the functionality in the admin and site helloworld.php files.

Let's look at the main lines of code in our admin helloworld.php file:

if (!JFactory::getUser()->authorise('core.manage', 'com_helloworld'))
{
	throw new Exception(JText::_('JERROR_ALERTNOAUTHOR'));
}

// Get an instance of the controller
$controller = JControllerLegacy::getInstance('HelloWorld');

// Execute the Request task method
$input = JFactory::getApplication()->input;
$controller->execute($input->getCmd('task'));

// Redirect if set by the controller
$controller->redirect();

The code

  • performs some permissions checks
  • instantiates the appropriate Helloworld controller
  • executes the appropriate method in that controller
  • implements any redirect set by the controller

See Model-View-Controller for further details of how this works in Joomla 3. The second and third steps used the task parameter within the URL query to determine the correct controller class to instantiate and method to call. If no task parameter was present then it instantiated the controller in controller.php and called the display method. Otherwise it instantiated one of the controllers in the controllers subdirectory (eg controllers/helloworld.php) and called the appropriate method on it.

The ComponentDispatcher::dispatch method (in libraries/src/Dispatcher/ComponentDispatcher.php)) now provides this functionality, but in a nicer way. After checking the permissions it uses the task by considering its parts as <controller type>.<method> (taking 'display' as default if either part is missing). It will then pass control to

  • an instance of the class <Controller type>Controller
  • calling the function <method>
  • using the admin or site controller, as appropriate

In this way the naming of the controllers is consistent - we have DisplayController, HelloworldController and HelloworldsController, all in the same directory.

The Dispatcher class can thus be considered as providing some 'routing - execution' functionality, enabling us to find the right controller and function within that controller, and it is cleaner to have this in a separate class rather than in the JControllerLegacy class as it was in Joomla 3.

Since the Hello World tutorial uses the standard Joomla pattern for selecting the class and method based on the task and has the same permissions checks, we can use the dispatch code as it stands in ComponentDispatcher, and don't need to define a special class of our own.