Introduction

Dans une première phase, vous avez rendu votre extension développée sous Joomla 3 compatible ave Joomla 4 en effectuant les modification minimales nécessaires.
Voir l'article correspondant... à cette étape.

Votre extension fonctionne donc bien sous Joomla4 (ou Joomla 5), mais utilise le mode de compatibilité et reste donc dans le MVC de Joomla 3.

Sous Joomla 6 ceci ne fonctionnera plus.

Dans ce nouvel article, nous alors donc voir comment modifier votre composant afin qu'il fonctionne sous le nouveau MVC de Joomla 4 et +, et donc à terme, assurer le portage sous Joomla 6.

Cette liste fait des propositions de correction du code uniquement.
On ne parlera pas de boostrap par exemple.

Pour la partie structurelle, on se reportera à l’excellent ouvrage en ligne, le "book" deNikolaos Dionysopoulos ....le "book" deNikolaos Dionysopoulos ....

On aura bien sûr déjà rendu son extension compatible Joomla 4 et 5.
A défaut on se reportera à l'article correspondant...

Après la paragraphe recensant quelques liens vers des articles utiles, vous trouverez un petit récapitulatif de ce que j'ai rencontré comme correction pour passer une extension de Joomla3 à Joomla4.

Cette liste n'est pas exhaustive bien sûr, et se complètera au fur et à mesure des mes découvertes faites lors de la migration de mes extensions.

Ceci étant la première phase, on va modifier la structure des dossiers afin de s’approcher un max de celle demandé par Joomla MVC4 et 5, mais on restera toujours en mode MVC Joomla3.

Un article suivant parlera alors du passage en MVC Joomla5

Article en cours de rédaction...

 

Remarques importantes

namespace

Dans les exemples ci-dessus, on aura déjà commencé à mettre en place la nouvelle structure, donc en placant les fichiers de code dans
/administrator/components/com_moncomposant/src ou dans /components/com_moncomposant/src

On aura donc aussi défini dans le manifest du composant (dans /administrator/components/com_moncomposant/moncomposant.xml :

<namespace path="src">MaSoociete\Component\Moncomposant</namespace>

Ce sera bien plus simple ainsi, toutefois, si vous n'utilisez pas le sous dossier src, il suffira de retirer la path="src" de la balise namespace du manifest.

Attention, après avoir ajouté ou changé la balise namespace, il faudra réinstaller votre composant.
Pour rappel, il faudra que l'on retrouve bien cette définition dans /administrator/cache/autoload_psr4.php de la sorte :

'Masociete\\Component\\Moncomposant\\Administrator\\' => [JPATH_ADMINISTRATOR . '/components/com_moncomposant/src'],
Masociete\\Component\\Moncomposant\\Site\\' => [JPATH_SITE . '/components/com_moncomposant/src'],

Prefixes

Les classes sous Joomla3 sont généralement préfixées (tables, model).

Tant que l'on reste en mode legacy (MVC Joomla3), il faura maintenir ces prefixes (methodes getModel des controleurs, ou appel des classes de Table (anciennes métodes Table::getInstance),

Par contre quand on passera en mode natif MVC Joomla4 et +, lors des appels vu que l'on travaillera dans un conteneur identifiant déjà l'extession, et que les prefixe des noms de classes ne sont plus nécessaire (et ont été supprimés), la notion de prefixe identifiera l'application (Administrator ou Site par exemple).

Remplacement du chargeur de classe (appels à JLoader)

Cas simple

remplacement d'un JLoader::register('MonfichierHelper', JPATH_COMPONENT_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'helper1.php');

Déplacer le fichier /administrator/components/com_moncomposant/helpers/helper1.php
dans /administrator/components/com_moncomposant/src/Helper/MonfichierHelper.php

Lui affecter (en tout debut du code son namespace :

namespace MaSociete\Component\Moncomposant\Administrator\Helper;

Nommer la classe du même nom que le fichier (sans le .php)
donc ici : class MonfichierHelper

Remplacer le Jloader ci-dessus par au début du fichier, après l'eventuel namespace par

use MaSociete\Component\Moncomposant\Administrator\Helper\MonfichierHelper;

Avec renommage de la classe

La méthode est très poche de la précédente.

 remplacement d'un JLoader::register('MonfichierHelper', JPATH_COMPONENT_ADMINISTRATOR . DIRECTORY_SEPARATOR . 'helpers' . DIRECTORY_SEPARATOR . 'helper1.php');

Déplacer le fichier /administrator/components/com_moncomposant/helpers/helper1.php
dans /administrator/components/com_moncomposant/src/Helper/Monhelper1Helper.php

Lui affecter (en tout début du code son namespace :

namespace MaSociete\Component\Moncomposant\Administrator\Helper;

Nommer la classe du même nom que le fichier (sans le .php)
donc ici : class Monhelper1Helper

Remplacer le Jloader ci-dessus par au début du fichier, après l'eventuel namespace par

use MaSociete\Component\Moncomposant\Administrator\Helper\Monhelper1Helper as MonfichierHelper;

 JLoader dynamique

Remplacer un JLoader::register($className, $helperfile);

Ceci pouvant arriver n'importe où dans le code.
Donc pas question d'utiliser un use, qui doit être placé en début du fichier php, et qui est donc codé en dur.

Dans mon exemple suivant, la méthode courante est définie dans une librairie (/librairies/Malib/src/Helper/LibHelper.php
et elle doit pouvoir appeler une classe statique KeywordHelper qui est définie dans le composant utilisant cette librairie commune.

On remplacera donc :

// chercher si le helper existe pour le composant et l'appeler $component contient le nom du composant utilisant la librairie sans le com_
$helperfile = rtrim(JPATH_SITE,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'components'.DIRECTORY_SEPARATOR.'com_'.$component.DIRECTORY_SEPARATOR.'helpers'.DIRECTORY_SEPARATOR.'keyword.php';
if (!file_exists($helperfile)) {
  return false;
}
$className = 'KeyWordHelper';
JLoader::register($className, $helperfile);
if(method_exists($className, 'getKeyWords')){
  $groups = $className::getKeyWords($fieldname);
 // appel de la méthode getKeyWords de la classe statique KeyWordHelper définie dans le dossier /helpers/keyword.php de la partie site du composant $component
}
....

Par

// chercher si le helper existe pour le composant et l'appeler
$helperfile = rtrim(JPATH_SITE,DIRECTORY_SEPARATOR).DIRECTORY_SEPARATOR.'components'.DIRECTORY_SEPARATOR.$com_component.DIRECTORY_SEPARATOR.'src'.DIRECTORY_SEPARATOR.'Helper'.DIRECTORY_SEPARATOR.'KeywordHelper.php';
if (!file_exists($helperfile)) {
  return false;
}
$className = 'Masociete\Component\\' . ucfirst($component) . '\Site\Helper\KeywordHelper';
if(method_exists($className, 'getKeyWords')){
  $groups = $className::getKeyWords($fieldname);
  // appel de la méthode getKeyWords de la classe statique KeywordHelper définie dans le dossier /src/Helper/Keyword.php de la partie site du composant $component
}
....

Ce code étant à adapter selon les besoins bien sûr

Helpers du framework

CategoriesHelper

Supprimer :

JLoader::register('CategoriesHelper', JPATH_ADMINISTRATOR . '/components/com_categories/helpers/categories.php');

 

et mettre en debut :

use \Joomla\Component\Categories\Administrator\Helper\CategoriesHelper;

 

FieldsHelper

Supprimer

JLoader::register('FieldsHelper', JPATH_ADMINISTRATOR . '/components/com_fields/helpers/fields.php');

et mettre au début

use \Joomla\Component\Fields\Administrator\Helper\FieldsHelper;

MVCFactory

Remarque : dans une classe Controller ou Model, le MVCFactory du composant courant est obtenu simplement par

$this->getMVCFactory()

au lieu de
Factory::getApplication()->bootComponent('com_componentname')->getMVCFactory()

Exemple obtenir MVCFactory de com_content :

$comContentMVCFactory = \Joomla\CMS\Factory::getApplication()->bootComponent('com_content')->getMVCFactory();

Voir : https://www.dionysopoulos.me/book/com-mvcfactory.html

Remplacements simples

Remplacer Par
Factory::getUser() $currentUser = Factory::getApplication()->getIdentity();
Factory::getUser($id); $user = Factory::getContainer()->get(UserFactoryInterface::class)->loadUserById($id);
Factory::getDBO()

use Joomla\Database\DatabaseInterface;
...
Factory::getContainer()->get(DatabaseInterface::class);

Factory::getConfig() Factory::getApplication->getConfig()
Factory::getLanguage() Factory::getApplication()->getLanguage()
Table::getInstance($name, $prefix, $prefix); Factory::getApplication()->bootComponent('com_componentname')->getMVCFactory()->createTable($name, $prefix, $config);
en remplaçant com_componentname par le nom du composant où est défini la table.
Attention aux prefixes : en mode Legacy (MVC Joomla3 ce sera le prefixe de vos classes de Table, mais en mode MVC Joomla4 et +, ce prefixe disparait, et il fautre y mettre le type d'application avec la 1ere lettre en majuscules donc 'Administrator).
Voir paragraphe Remarques au début de cet article.
$session = Factory::getSession(); $session = Factory::getApplication()->getSession();
config = Factory::getConfig(); $config = Factory::getApplication()->getConfig();
 Factory::getLanguage();  $language = Factory::getContainer()->get(LanguageFactoryInterface::class)->createLanguage(
Factory::getApplication()->get('language'),
Factory::getApplication()->get('debug_lang'),
);

Les tables

Déplacer les fichiers php de description de vos tables de /administrator/components/com_moncomposant/tables dans .../src/Table

On renommera les fichiers et leurs noms de classe en TotoTable

On leur attribuera le namespace correspondant donc  :

  • Masociete\Component\Moncomposanr\Administrator\Table

Pour rester dans l'ancien mode de fonctionnement, le temps de terminer les modification, j'ai gardé provisoirement les fichiers des tables dans le dossier .../table et gardant leur ancien nom de classe, et en les faisant hériter de la nouvelle classe.

Par exemple : class MonPrefixTableToto extends Masocite\Component\Moncomposant\Administrator\Table\TotoTable

et en supprimant tout son contenu, car le but est juste que Joomla arrive à acceder à la nouvelle structure, tout en restant à la sauve Joomla3 MVC.

Remarques

Dans la classe d'une table (héritant de Table), on peut duplique l'instance juste en faisant :

$table = new static($this->_db);

Les Models

La logique sera la même que pour les tables.

Déplacer les fichiers php de description de vos tables de /administrator/components/com_moncomposant/models dans .../src/Model

On renommera les fichiers et leurs noms de classe en TotoModel

On leur attribuera le namespace correspondant donc  :

  • Masociete\Component\Moncomposanr\Administrator\Model

Pour rester dans l'ancien mode de fonctionnement, le temps de terminer les modification, j'ai gardé provisoirement les fichiers des models dans le dossier .../models et gardant leur ancien nom de classe, et en les faisant hériter de la nouvelle classe.

Par exemple : class MonprefixModelToto extends Masociete\Component\Moncomposant\Administrator\Model\TotoModel

et en supprimant tout son contenu, car le but est juste que Joomla arrive à acceder à la nouvelle structure, tout en restant à la sauve Joomla3 MVC.

Remarque

Attention aux methodes getTable de vos models.

Sous Joomla3 (donc en mode Legacy (MVC Joomla3), on gardera les prefixes de vos Models, mais en mode MVC Joomla4 il faudra y mettre le type d'application concernée (donc Administrator ou Site).

Si vous n'avez pas besoins de definir cette méthode, ne le faites pas la calsse mère Joomla\CMS\MVC\Controller\BaseController le fera corretement en detectant si vous êtes en mode LEgacy ou pas

Propriétés préremplies des models

  • ...

Les Vues (dossier views)

 Les opérations sont similaires à celles concernant les Models.

Il faut recopier le dossier ../views dans ../src/View, puis mettre la première lettre ede chaque sous-dossier de vues en majuscule.

Ensuite il faudra séparer ensuite les templates (sous dossier views/mavue/tmpl).
Il faudra les placer à la racine du composant en admin ou en front, dans un dossier tmpl (attention tout en minuscules); et dans un sous-dossier du nom de la vue tout en mminuscules également.

Dans le dossier View/Mavue, on renommera les fichiers :

  • view.html.php en HtmlView.php
  • view.raw.php en RawView.php
  • view.feed.php en FeedView.php

Idem pour le nom de la classe qui deviendra aussi HtmlView

On leur attribuera le namespace correspondant donc  :

  • Masociete\Component\Moncomposanr\Administrator\View\Mavue (en admin)
  • Masociete\Component\Moncomposanr\Site\View\Mavue (en front)

Pour rester dans l'ancien mode de fonctionnement, le temps de terminer les modification, j'ai gardé provisoirement les fichiers des vues dans le dossier .../views et gardant leur ancien nom de classe, et en les faisant hériter de la nouvelle classe.

Par exemple : class MonprefixViewMaVue extends Masociete\Component\Moncomposant\Administrator\View\Mavue\HtmlView

et en supprimant tout son contenu, car le but est juste que Joomla arrive à acceder à la nouvelle structure, tout en restant à la sauve Joomla3 MVC.

On supprimera aussi le sous dossier /tmpl dans l'ancien dossier .../views/mavue, en effet le template utilisé sera dans le nouvel emplacement donc en ../tmpl/mavue.

Propriétés préremplies des vues

  • $this->getCurrentUser()
  • $this->getDispatcher()

Les controleurs

Les opérations sont similaires à celles concernant les Models.

Il faut recopier le dossier ../controllers dans ../src/Controller.

Dans le dossier ...src/Controller, on renommera les fichiers :

  • moncontroller.php en MoncontrollerController.php

On renomera la nouvelle classe également en MoncontrollerController

On leur attribuera le namespace correspondant donc  :

  • Masociete\Component\Moncomposanr\Administrator\Controller (en admin)
  • Masociete\Component\Moncomposanr\Site\Controller (en front)

Pour rester dans l'ancien mode de fonctionnement, le temps de terminer les modification, j'ai gardé provisoirement les fichiers des controlleurs dans le dossier .../controllers et gardant leur ancien nom de classe, et en les faisant hériter de la nouvelle classe.

Par exemple : class MonprefixControllerMoncontroller extends Masociete\Component\Moncomposant\Administrator\Controller\MoncontrollerController

et en supprimant tout son contenu, car le but est juste que Joomla arrive à acceder à la nouvelle structure, tout en restant à la "sauce" Joomla3 MVC.

Remarque

Attention aux methodes getModel de vos controlleurs.

Sous Joomla3 (donc en mode Legacy (MVC Joomla3), on gardera les prefixes de vos Models, mais en mode MVC Joomla4 il faudra y mettre le type d'application concernée (donc Administrator ou Site).

Attention aux constructeurs de vos controlleurs

Dans la mesure du possible évitez de déclarer un constructeur.

Si vraiment c'est nécessaire, reprenez bien la syntaxe du constructeur de la calsse mère.

public function construct(
 $config = [],
 MVCFactoryInterface $factory = null,
 ?CMSApplication $app = null,
 ?Input $input = null,
 FormFactoryInterface $formFactory = null
)
{
...
}

 

Il ne faut surtout pas oublier de passer l'argument $factory, car $factory est initialisé avant l'appel de votre controleur.

Si vous ne le faites pas suivre dans la classe mère, la classe Factory sera forcée à Legacy, et vous reviendrez donc au mode MVC de Joomla3.

Propriétés préremplies des contrôleurs

  • $this->factory
  • $this->app (à utiliser d'office. Ne plus utiliser \Joomla\CMS\Factory::getApplication()) dans un controlleur.
  • $this->input

Les Forms

Deplacer le sous-dossier des formulaires xml de /model/forms en /forms directement sous le dossier du composant.

Attention, pour les champs de type subform, pensez à changer le dossier  dans le prpriété formsource de la balise Field du formulaire xml

Les champs (Fields)

Déplacer les fichiers php de description de vos champs de .../models/fields dans .../src/Field

Si vous avez des fichiers en .../models/fiels/modal il faut les placer dans .../src/Field/Modal

On renommera les fichiers et leurs noms de classe en TotoField ou TotoslistField

Il faudra aussi change le protected $type = 'Modal_Article'; avec

  • protected $type = 'Toto'; // pour TotoField.php
  • protected $type = 'Totolist'; // pour TotolistField.php
  • protected $type = 'Modal_Toto'; // pour TotoField.php dans le sous dossier Modal

On leur attribuera le namespace correspondant donc soit :

  • Masociete\Component\Moncomposanr\Administrator\Field
  • Masociete\Component\Moncomposanr\Administrator\Field\Modal

si vos descriptions sont dans la partie admin.

Dans vos formulaires xml, il faudra maintenant supprimer l'emplacement du dossier du php par le namespace

On remplacera donc dans les xml par exemple

  • addfieldpath="/administrator/components/com_gskititres/src/Field"
  • par addfieldprefix="Masociete\Component\Moncomposant\Administrator\Field"

Remarques

  • Les champs de type ModalL:
    Le fait que les champ de type "modal" soient dans un sous dossier de Field ne change rien au niveau des formualires xml
    Dans les xml on defini le même prefixe que pour les champs normaux
    Par contre dans le code php du champ, le namespace terminera bien par ...\Field\Modal

Les regles (Rules)

On fera de même que pour les champs (paragraphe précédent).

Déplacer les fichiers php de description de vos champs de .../models/rule dans .../src/Rule

On renommera les fichiers et leurs noms de classe en TotoRule

On leur attribuera le namespace correspondant donc soit :

  • Masociete\Component\Moncomposanr\Administrator\Rule

si vos regles sont dans la partie site.

Dans vos formulaires xml, il faudra maintenant supprimer l'emplacement du dossier du php par le namespace

On remplacera donc dans les xml dans les balises form, fieldset ou field par exemple

  • addrulepath="/components/com_moncomposant/models/rules"
  • par addruleprefix="Masociete\Component\Moncomposant\Site\Rule"

Et si on a renommé la classe de la gregle on vérifiera qu'il y a le bon nom dans la propriéte validate des champs en question exemple :

<field
name="xyz"
type="textarea"
label="COM_MONCOMPOSANT_MESSAGE_LABEL"
cols="50"
rows="10"
id="xyz"
filter="safehtml"
validate="Toto"
required="true"
/>

 

 Router

 Si vous ne souhaitez pas utiliser un router il faut retirer les lignes correspondantes dans /administrator/components/com_moncomposant/src/Extension/MoncomponsantComponent.php :

dans la rubrique "implements" de la classe, retirer :

  • RouterServiceInterface
  • et le use RouterServiceTrain

Les categories

On copiera juste le fichier /components/com_moncomposant/helpers/category.php

dans /components/com_moncomposant/src/Service en le renommant en Category.php

Si vous avez differentes catégories dans votre extension, copierale fichier /components/com_moncomposant/helpers/category_masection.php

dans /components/com_moncomposant/src/Service en le renommant en MasectionCategory.php

 On lui attribuera le namespace correspondant donc  :

  • Masociete\Component\Moncomposanr\Site\Service

Pour rester dans l'ancien mode de fonctionnement, le temps de terminer les modification, j'ai gardé provisoirement les fichiers de le dossier .../helpers,
en gardant leur ancien nom de classe, et en les faisant hériter de la nouvelle classe.

Par exemple : class MonComposantMacategorieCategories extends Masociete\Component\Moncomposant\Site\Service\Category

et en supprimant tout son contenu, car le but est juste que Joomla arrive à acceder à la nouvelle structure, tout en restant à la "sauce" Joomla3 MVC.