Joomla
Joomla 3 naar 5: 17 valkuilen die de upgrader oversloeg
Vorig kwartaal migreerden we een Antwerpse uitgever met 14.000 artikelen van Joomla 3.10 naar Joomla 5.2. Hier zijn de zeventien valkuilen die de officiële tool oversloeg, op volgorde van schade.

Het is dinsdagochtend in Berchem. De hoofdredacteur opent de staging-site. De artikellijst laadt. Het artikel zelf laadt. De byline niet. De publicatiedatum staat op 01-01-1970. De module voor gerelateerde artikelen gooit een fatal in het Nederlands. We zitten zes weken in een migratie van Joomla 3.10 naar Joomla 5.2 bij een Belgische uitgever met 14.000 artikelen, elf custom componenten en een printworkflow die ze allemaal raakt.
De officiële Joomla-migratiegids zegt: draai de pre-update check, installeer de database fixer en vertrouw je extensies. De post-install meldingen vlaggen het makkelijke werk. Hieronder de rest, op volgorde van hoe luid de bug schreeuwde toen we 'm vonden. Sta je op het punt om een component te herschrijven voor Joomla 4 of 5, plak deze lijst dan boven je editor.
1. De dispatcher is nu verplicht
In Joomla 3 was het entry point van een component components/com_foo/foo.php, met JControllerLegacy::getInstance(). In Joomla 5 heb je een Dispatcher-klasse nodig én een service provider die hem registreert. Het oude entry point laadt nog wel, maar elke moderne API-call gaat ervan uit dat de container is bedraad. De migratietool maakt geen van beide files voor je aan.
// services/provider.php
defined('_JEXEC') or die;
use Joomla\CMS\Extension\Service\Provider\ComponentDispatcherFactory;
use Joomla\CMS\Extension\Service\Provider\MVCFactory;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
return new class implements ServiceProviderInterface {
public function register(Container $c): void
{
$c->registerServiceProvider(new MVCFactory('\\Acme\\Component\\News'));
$c->registerServiceProvider(new ComponentDispatcherFactory('\\Acme\\Component\\News'));
}
};
2. PSR-4 layout, anders laadt er niks
Joomla 4 introduceerde een strikte namespaced layout: administrator/components/com_news/src/Controller/, src/Model/, src/View/. Je oude controllers/article.php is onzichtbaar voor de nieuwe container. De compatibility-plugin laadt klassieke JModelLegacy-childs nog een tijdje, maar alles wat $app->bootComponent('com_news') aanroept krijgt een stub terug. De migratietool herstructureert de core. Jouw code niet.
3. JFactory werkt, tot het niet meer werkt
De backwards-compatibility shim houdt JFactory::getApplication() in leven in 4.x. In 5.0 verdwijnt-ie. Search-and-replace is rechttoe rechtaan, op één patroon na: JFactory::getDbo() binnen een model dat al BaseDatabaseModel extend, moet $this->getDatabase() worden. De globale DBO kan namelijk naar een andere connectie wijzen tijdens CLI-runs. We hebben een middag verspeeld aan die cron voor de print-export.
// before
$db = JFactory::getDbo();
// after, in a model
$db = $this->getDatabase();
// after, anywhere else
$db = Factory::getContainer()->get('DatabaseDriver');
4. HTMLHelper herschrijft het asset-register
Elke JHtml::_('behavior.formvalidator') wordt HTMLHelper::_('behavior.formvalidator'), prima. De valkuil is HTMLHelper::_('jquery.framework'). Joomla 5 levert jQuery niet meer mee op de front-end. De call resolved nog wel, maar het script wordt niet daadwerkelijk geladen, tenzij je template ervoor opt-int. Twee van onze modules verwachtten $.fn.colorbox op DOMContentLoaded en faalden stilletjes in productie.
5. Bootstrap 2 markup is dode markup
De core ging van Bootstrap 2 naar Bootstrap 5. Dat zijn twee complete rewrites van klassenamen, zie de officiële Bootstrap 5 migratiegids. .span6 heet nu .col-md-6, .well is weg, data-attributen zijn nu prefixed met data-bs-. Niets hiervan wordt automatisch omgezet in je componentviews. We hebben een eenmalige regex-pass geschreven, met het oog gereviewed, en daarna nogmaals samen met de designer, vóór er een redactie-template naar staging ging.
6. jQuery is opt-in
Heeft een module jQuery nodig in Joomla 5, dan laad je hem zelf:
$wa = $this->getApplication()->getDocument()->getWebAssetManager();
$wa->useScript('jquery');
De Web Asset Manager dedupliceert over de pagina, dus hem op twee plekken laden is goedkoop. Hem op één plek vergeten is een console-fout en een kapotte module.
7. Text-class, en de constantenfiles zijn verhuisd
JText::_ wordt Text::_, en de taalbestanden staan nu in language/en-GB/com_news.ini in plaats van language/en-GB/en-GB.com_news.ini. De migratie-installer houdt het oude pad leesbaar, maar nieuwe strings die je daarin zet worden genegeerd. We hebben de keyboard-shortcut overlay van onze editor drie keer herschreven voor we doorhadden dat we steeds een file aan het editen waren die niet meer gelezen werd.
8. addfieldprefix kreeg een namespace
Custom field types in form-XML zijn twee keer verschoven. Joomla 3 gebruikte addfieldprefix="JFormFieldFoo". Joomla 4 introduceerde addfieldpath en een namespaced veldlocatie. Joomla 5 verwacht de volledig gekwalificeerde namespace. Oude XML-bestanden laden zonder waarschuwing, en renderen stilletjes text-inputs op de plek waar je een category-picker had.
9. ACL asset rules op geneste categorieën
Asset rules waren vroeger gekoppeld aan com_news.category.42. Nadat de database fixer de asset-tabel had herbouwd, was onze ACL voor de eindredactie op een vier niveaus diepe categorie verdwenen. Geef de rol opnieuw uit op de parent en de regels cascaden weer. De fix is één klik. De oorzaak vinden kostte een ochtend.
10. com_content::getItem is van vorm veranderd
Hergebruikt je component ArticleModel::getItem($pk) uit core com_content, dan lazy-loadt het teruggegeven object nu de custom fields. $item->jcfields aanroepen vóór de article-view draait geeft null terug. Forceer het laden:
PluginHelper::importPlugin('content');
Factory::getApplication()->triggerEvent(
'onContentPrepare',
['com_content.article', &$item, &$params, 0]
);
11. TinyMCE 6 heeft de helft van je plugins gedumpt
TinyMCE 4 naar 6 is een aparte migratie binnen de Joomla-migratie. paste, contextmenu, textcolor en spellchecker zijn weg of samengevoegd. De Joomla editor-profiel UI laat de oude vinkjes nog één release zien, maar uitgevinkte plugins verdwijnen stilletjes. Redacteuren klaagden dat copy-paste vanuit Word de opmaak overnight liet vallen. De fix was de nieuwe paste-instellingen expliciet aanzetten in het profiel.
12. Routing vereist een RouterView
SEF URL-parsing in Joomla 3 was een procedurele router.php. Joomla 5 verwacht een Site\Service\Router die RouterView extend, met expliciete RouterViewConfiguration per view en per geneste view. Oude router.php files draaien nog voor inkomende requests, maar de nieuwe menu-builder negeert ze. Elke nieuwe menu-item wijst dus naar de rauwe querystring. De router opnieuw opbouwen kostte ons een dag. Vanuit het legacy-bestand starten zou er drie hebben gekost.
13. MySQL 8 strict mode vindt je losse quotes
De installer van Joomla 5 zet sql_mode=STRICT_TRANS_TABLES aan op nieuwe installaties. Migreren naar een verse MySQL 8 box legde elke WHERE id = '12' op een integer-kolom bloot. De query builder is prima. Rauwe SQL via $db->setQuery() niet. Draai je slow-query log een week na release en grep op truncated incorrect.
14. CacheController gaat alleen nog via de factory
new JCache(...) en JCache::getInstance() zijn weg. De vervanger:
use Joomla\CMS\Cache\CacheControllerFactoryInterface;
$cache = Factory::getContainer()
->get(CacheControllerFactoryInterface::class)
->createCacheController('output', ['defaultgroup' => 'com_news']);
Plugins die hun eigen output cachten via de oude API, cachten gewoon niets meer. Wij merkten het toen de front-page TTFB met 380 ms steeg na release.
15. Toolbar-buttons zijn nu objecten
JToolBarHelper::save('article.save') wordt $toolbar->standardButton('save', 'JTOOLBAR_SAVE', 'article.save'). De statische facade ToolbarHelper werkt nog voor de gebruikelijke buttons. Alles wat custom is, heeft de nieuwe fluent-API nodig én een custom button-class die Joomla\CMS\Toolbar\Button extend. De compatibility-plugin rendert niets voor onbekende calls.
16. Custom veldtypes hebben een class-attribuut nodig
Form-XML wil nu een expliciete class op custom field-elementen, anders valt de renderer terug op text:
<field
name="region"
type="region"
class="Acme\Component\News\Administrator\Field\RegionField"
label="COM_NEWS_FIELD_REGION_LABEL" />
17. Tag-plugin events zijn hernoemd
Heb je een tag-plugin geschreven tegen onContentPrepare met context com_tags.tag, dan vuurt die context nog. Het nieuwe onTagsBeforeDisplay event kwam erbij in 4.2, en meerdere core-plugins stapten over. Mixed-context plugins renderden tags dubbel in onze module voor gerelateerde artikelen, tot we elke $context-string in elke plugin die wij beheerden langs hadden gelopen.
De post-install compatibility check is een rookmelder, geen sprinklerinstallatie. Hij vlagt deprecated calls in de core. Hij vlagt geen ontbrekende service provider, geen Bootstrap 2 view, en geen TinyMCE-plugin die stil is gevallen. Reken op een volledige QA-ronde per component.
Wat we anders zouden doen
Drie dingen. Eén: bouw de service provider en de dispatcher op dag één, nog voor je één view port. De vorm van de container dwingt elke andere beslissing daarna af. Twee: schrijf de Bootstrap 2 naar 5 regex-pass, draai 'm op een kopie, en lees daarna elke diff hardop door, samen met een designer. We vingen twee layout-regressies die de regex vrolijk naar staging had geschoven. Drie: migreer de editor niet in dezelfde release als de component-rewrite. Geef de redactie een week om aan de nieuwe TinyMCE-toolbar te wennen voor de URL's en templates onder ze veranderen.
Toen we deze legacy-migratie deden voor de Antwerpse uitgever, liepen we vooral aan tegen de officiële compatibility-plugin, die groen licht gaf op het databaseschema en stil bleef over de zeventien issues hierboven. We hebben dat opgelost door precies de eenpaginalijst te schrijven die je net hebt gelezen, en elk custom component erdoorheen te halen voor we naar staging gingen.
Begin je vandaag aan deze migratie, neem dan tien minuten en grep je codebase op JFactory::, JHtml::, JText:: en JToolBarHelper::. Die telling is je huiswerk.
Kern
De post-install compatibility check van Joomla is een rookmelder, geen sprinklerinstallatie. Hij vlagt de core, niet jouw code. Loop elk custom component handmatig na.
FAQ
Vangt de ingebouwde compatibility check van Joomla kapotte componentcode?
Hij vlagt deprecated core-API calls en bekende issues in het databaseschema. Hij parsed jouw custom controllers, views en modules niet. Reken op een handmatige QA-ronde per component.
Wordt jQuery standaard nog geladen in Joomla 5?
Nee. De front-end komt zonder jQuery. Heeft een module het nodig, roep dan $wa->useScript('jquery') aan op de Web Asset Manager, anders falen je scripts stilletjes in productie.
Kan ik mijn oude router.php gewoon meenemen naar Joomla 5?
Hij draait nog voor inkomende requests, maar de nieuwe menu-builder negeert hem. Nieuwe menu-items wijzen naar rauwe querystrings tot je migreert naar een RouterView-klasse.
Wat is de veiligste volgorde om een Joomla 3 component te migreren?
Bouw eerst de service provider en de dispatcher, daarna de namespaced PSR-4 layout, daarna de views, en als laatste de router. Routing leunt op alle andere stukken die op hun plek staan.