---
title: "Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory - WebTolk"
description: "Как без правки ядра Joomla подменять MVC-классы компонентов через системный плагин, DI-контейнер и MVCFactory. Сравниваем старый подход Joomla 2.5/3.x через onAfterRoute с             современной схемой Joomla5/6, показываем extend(), set(), риски декораторов, и порядок загрузки расширений в ядре."
url: "https://web-tolk.ru/blog/pereopredelenie-klassov-yadra-joomla-s-pomoshchyu-plagina-na-primere-mvcfactory"
date: "2026-05-08T04:56:41+00:00"
language: "ru-RU"
---

# Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory

 Автор: Сергей Толкачев Создано: 08 мая 2026 Обновлено: 12 мая 2026 Просмотров: 369    ![Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory](https://web-tolk.ru/blog/images/blog/pereopredelenie-klassov-yadra-joomla-s-pomoshchyu-plagina-na-primere-mvcfactory/header.webp)

Разработчики сайтов, веб-мастера, рассматривая Joomla как CMS, чаще всего используют компоненты ядра такими, какие они есть. Но компоненты ядра, обеспечивающие CRUD-ы в Joomla, следует рассматривать ещё и как примеры использования Joomla в качестве фреймворка. Иногда реалии проекта таковы, что требуется внести изменения именно в логику классов ядра Joomla. Я покажу это на нескольких примерах: как исхитрялись раньше и какие возможности появились в современных версиях Joomla.

Сразу оговорюсь: речь не о том, чтобы править файлы ядра. Это плохая идея почти всегда. При обновлении Joomla такие изменения будут потеряны, а сопровождать их потом придётся вручную. Речь о другом: как изменить точку создания MVC-классов компонента через плагин и DI-контейнер, не залезая в core-файлы.

## А зачем это надо?..

Это бывает нужно в тех случаях, когда на сайте активно используется функционал ядра и нецелесообразно переносить все процессы на работу с другим расширением, которого может и не существовать в природе.

**Простой пример:** мультикатегории, когда материал находится в одной основной категории и нескольких дополнительных. Этого функционала до сих пор нет в составе ядра Joomla, но сама задача востребована. Поэтому сторонние разработчики по-разному решали вопрос: как добавить материалу Joomla несколько категорий, не переписывая весь `com_content` и не создавая отдельный компонент только ради этой логики.

Мне известно несколько плагинов, которые решали эту задачу через подмену стандартной модели MVC на собственную. В целом это нормальное направление мысли: если список материалов должен учитывать дополнительные категории, то логика выборки действительно живёт в модели. Вопрос только в том, **каким способом** эту модель подменять в разных поколениях Joomla. Эта статья написана спустя 1,5 года после выхода [WT Multicategories плагина мультикатегорий Joomla](https://web-tolk.ru/dev/joomla-plugins/wt-multicategories).

## Немного археологии: как раньше работала подмена моделей MVC ядра Joomla 2.5 - Joomla 3.x на `onAfterRoute()`

В старых решениях для Joomla подмена классов часто делалась следующим образом: системным плагином на событие `onAfterRoute` подключали файл со своим классом через `require_once` / `include_once`. Новое подключение класса файлом происходило раньше, чем ядро доходило до подключения своего класса модели.

Это не фокус, а следствие тогдашнего способа загрузки MVC-классов: ядро сначала проверяло, существует ли нужный PHP-класс, и только потом подключало файл модели из компонента. Если плагин успевал объявить класс с тем же именем, Joomla уже не подключала штатный файл.

### Как это работало в Joomla 2.5.x

Для Joomla 2.5 цепочка загрузки моделей MVC была такая:

- `route()` вызывал `onAfterRoute`, а компонент запускался позже в `dispatch()` через `JComponentHelper::renderComponent()`.
- Входной файл `com_content` создавал контроллер.
- Стандартный `display()` создавал модель, а `JModel::getInstance()` сначала делал `class_exists($modelClass)` и только если класса ещё не было, выполнял `require_once` файла модели.

Значит, если системный плагин уже объявил `ContentModelArticle`, ядро файл `components/com_content/models/article.php` уже не подключало. Такой подход был хрупким, но понятным: нужно было выиграть гонку загрузки класса.

### Как это работало в Joomla 3.x

Joomla 3 существовала очень долго, и внутри этой линейки были заметные архитектурные различия. Но общая логика подмены моделей оставалась похожей на Joomla 2.5.

- Событие `onAfterRoute` для системных плагинов происходило до запуска активного компонента.
- Компонент запускался позже через `ComponentHelper::renderComponent()`.
- Для legacy-MVC по умолчанию использовались механизмы, совместимые со старым подходом.
- `BaseDatabaseModel::getInstance()` снова сначала проверял `class_exists`, и лишь потом подключал файл модели.

Поэтому заранее объявленный класс мог перехватить создание модели. На этой технике выросло немало практических решений. Однако в современных версиях Joomla это уже не тот слой, в который стоит вмешиваться.

### Древние изощрённые способы замены классов ядра Joomla от особо творческих личностей

Были также и другие, ещё более изощрённые способы: например, плагином переименовывать файл ядра, а на его место класть свой PHP-класс. И делать это каждый раз, когда класс ядра изменялся после обновления.

Ещё один вариант мне встречался: в состав плагина включались классы оригинальные ядра. На `onAfterInitialise()` переименовывались классы ядра в `$coreClass = $class . 'Core'`, свои классы с изменениями наследовались от созданных `*Core` -классов, а имена новых классов равны классам ядра. И дальше, при проверке наличия оригинальных `*Core`-классов, заменялось *содержимое* файлов классов ядра с помощью `file_put_contents`. Ну как красиво же и как страшно!

```
<?php
// Фрагмент одного плагина

protected function overrideClass($class = null)
{
	$classes = array(
		'FileLayout'     => JPATH_ROOT . '/libraries/src/Layout/FileLayout.php',
		'HTMLHelper'     => JPATH_ROOT . '/libraries/src/HTML/HTMLHelper.php',
		'HtmlView'       => JPATH_ROOT . '/libraries/src/MVC/View/HtmlView.php',
		'ModuleHelper'   => JPATH_ROOT . '/libraries/src/Helper/ModuleHelper.php',
		'BaseController' => JPATH_ROOT . '/libraries/src/MVC/Controller/BaseController.php',
	);

	if (!empty($classes[$class]) && !class_exists($class))
	{
		$coreClass = $class . 'Core';
		if (!class_exists($coreClass))
		{
			$path     = Path::clean($classes[$class]);
			$core     = Path::clean(__DIR__ . '/classes/' . $coreClass . '.php');
			$override = Path::clean(__DIR__ . '/classes/' . $class . '.php');
			if (!file_exists($core))
			{
				file_put_contents($core, '');
			}

			$context = file_get_contents($path);
			$context = str_replace('class ' . $class, 'class ' . $coreClass, $context);
			if (file_get_contents($core) !== $context)
			{
				file_put_contents($core, $context);
			}

			require_once $core;
			require_once $override;
		}
	}
}
```

Так раньше делать было нельзя, а теперь уже и не нужно. Давайте теперь посмотрим...

## Что изменилось в Joomla 5 / 6+

В Joomla 4+ компоненты работают иначе. Поддержка Joomla 4 уже завершилась, поэтому мы не будем специально фокусироваться на старой версии CMS. Событие `onAfterRoute` никуда не исчезло, но современные компоненты создают MVC-объекты через `MVCFactory`. Фабрика использует namespace компонента и собирает полное имя класса: например, `Joomla\Component\Content\Site\Model\ArticleModel`, а не legacy-класс вида `ContentModelArticle`.

Поэтому старая идея "подключим файл с таким же именем класса пораньше" перестаёт быть нормальной точкой расширения. У современного компонента есть service provider, дочерний DI-контейнер, зарегистрированные сервисы и фабрика, которая знает, как создавать контроллеры, модели, представления и таблицы.

В Joomla 6.1 современная часть этой цепочки проверяется по следующим файлам ядра:

- `libraries/src/Extension/ExtensionManagerTrait.php` - загрузка расширения и события `onBeforeExtensionBoot` / `onAfterExtensionBoot`;
- `libraries/src/Extension/Service/Provider/MVCFactory.php` - регистрация `MVCFactoryInterface` в контейнере компонента;
- `libraries/src/MVC/Factory/MVCFactory.php` - создание контроллеров, моделей, view и table;
- `libraries/src/Dispatcher/ComponentDispatcherFactory.php` и `libraries/src/Dispatcher/ComponentDispatcher.php` - передача фабрики в диспетчер компонента;
- `libraries/src/MVC/Controller/BaseController.php` - создание модели через фабрику контроллера;
- `administrator/components/com_content/services/provider.php` - регистрация сервисов компонента ядра на примере материалов Joomla

### Что такое DI-контейнер Joomla?

DI-контейнер Joomla хранит правила создания сервисов. В контейнер кладут данные вида **ключ -> значение**.

 Ключ DI-контейнера JoomlaЧаще всего это интерфейс или имя класса. Например, `MVCFactoryInterface::class`. Но ключом может быть и строка, и alias.Значение DI-контейнера JoomlaЭто может быть готовый объект, но чаще - замыкание, которое создаёт объект в момент вызова `$container->get()`.

Для современного компонента Joomla важнее всего не глобальный контейнер приложения, а **дочерний контейнер конкретного компонента**. Именно туда service provider компонента регистрирует его фабрики и сам объект компонента.

> **Что важно:** в DI-контейнер компонента Joomla не помещается отдельный сервис для каждой модели, view или table. В контейнер помещается сервис `MVCFactoryInterface`, а уже фабрика знает, какой MVC-класс создать. Чисто теоретически компонент может использовать DI-контейнер как угодно и помещать туда и модели, но в стандартной ар
> итектуре MVC Joomla таки
>  примеров нет.

**Отсюда следует главный вывод:** если нужно повлиять на создание модели современного компонента, то подменять надо не файл модели через `include`, а поведение `MVCFactoryInterface` в контейнере этого компонента.

Дальше нашим модельным организмом и подопытным кроликом будет компонент материалов Joomla.

### Как `com_content` регистрирует фабрику?

Все компоненты ядра Joomla (кроме `com_ajax`) работают по этому паттерну. В `administrator/components/com_content/services/provider.php` Joomla регистрирует несколько service provider-ов:

```
$container->registerServiceProvider(new CategoryFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new MVCFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new ComponentDispatcherFactory('\\Joomla\\Component\\Content'));
$container->registerServiceProvider(new RouterFactory('\\Joomla\\Component\\Content'));
```

Затем регистрируется сам компонент:

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

        $component->setRegistry($container->get(Registry::class));
        $component->setMVCFactory($container->get(MVCFactoryInterface::class));
        $component->setCategoryFactory($container->get(CategoryFactoryInterface::class));
        $component->setAssociationExtension($container->get(AssociationExtensionInterface::class));
        $component->setRouterFactory($container->get(RouterFactoryInterface::class));

        return $component;
    }
);
```

Здесь видно две важные вещи. Во-первых, `MVCFactoryInterface` живёт в контейнере компонента. Во-вторых, фабрика нужна не только самому объекту компонента через `setMVCFactory()`, но и `ComponentDispatcherFactory`. А диспетчер компонента потом создаёт контроллер через эту фабрику.

## Где нужно вмешиваться: `ExtensionManagerTrait::loadExtension()`

Современная загрузка расширения идёт через `Joomla\CMS\Extension\ExtensionManagerTrait`, метод `loadExtension()`. Нас интересует порядок действий внутри него:

1. Joomla проверяет, не загружено ли расширение ранее.
2. Создаётся дочерний контейнер расширения: `$this->getContainer()->createChild()`.
3. Диспетчер вызывает событие `onBeforeExtensionBoot`.
4. Подключается `services/provider.php` расширения, и provider регистрирует сервисы в контейнере.
5. Если provider не зарегистрировал объект расширения, Joomla включает fallback для legacy-компонента, модуля или плагина.
6. Диспетчер вызывает событие `onAfterExtensionBoot`.
7. Только после этого Joomla получает объект расширения из контейнера: `$container->get($type)`.
8. Если расширение реализует `BootableExtensionInterface`, вызывается `boot($container)`.
9. Объект расширения кешируется.

Для нашей задачи это почти готовая инструкция. На `onBeforeExtensionBoot` дочерний контейнер уже есть, но service provider компонента ещё не выполнился. Значит, `MVCFactoryInterface` у современного компонента обычно ещё не зарегистрирован. На `onAfterExtensionBoot` provider уже отработал, но объект компонента ещё не создан. Именно в этот момент можно заменить или расширить `MVCFactoryInterface` так, чтобы новая фабрика попала и в компонент, и в dispatcher factory.

> **Как не прома
> нуться?** Для подмены `MVCFactoryInterface` современного компонента из системного плагина нужно на `onAfterExtensionBoot` брать контейнер, который пришёл в событии. Именно его использует компонент потом. Глобальный контейнер приложения здесь не тот уровень.

## Почему не глобальный контейнер и не поздний `setMVCFactory()`

На первый взгляд может показаться, что достаточно сделать что-то вроде этого:

```
$component = $app->bootComponent('com_content');
$component->setMVCFactory(new MyFactory());
```

Иногда это действительно изменит результат прямого вызова:

```
$app->bootComponent('com_content')
    ->getMVCFactory()
    ->createModel('Article', 'Site');
```

Но для обычного рендера компонента этого недостаточно. `ComponentHelper::renderComponent()` вызывает:

```
$app->bootComponent($option)->getDispatcher($app)->dispatch();
```

`getDispatcher()` обращается к `ComponentDispatcherFactory`, а тот уже хранит фабрику, которую получил при создании объекта компонента. Если компонент уже создан, поздняя замена публичной фабрики через `setMVCFactory()` не обязана заменить фабрику внутри dispatcher factory.

Поэтому для стандартной MVC-цепочки важно успеть до создания `ComponentInterface`. В `ExtensionManagerTrait::loadExtension()` это как раз промежуток между регистрацией `services/provider.php` и `$container->get(ComponentInterface::class)`, то есть событие `onAfterExtensionBoot`.

## Способ 1. Расширить сервис через `$container->extend()`

У контейнера Joomla есть метод `extend()`. Он берёт уже зарегистрированный сервис и заворачивает его в новую фабрику. Это похоже на паттерн "декоратор": у нас остаётся исходный объект, но мы возвращаем наружу обёртку с дополнительным поведением.

Пример условного системного плагина:

```
<?php

namespace Webtolk\Plugin\System\MVCFactoryOverride\Extension;

\defined('_JEXEC') or die;

use Joomla\CMS\Event\AfterExtensionBootEvent;
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\CMS\Plugin\CMSPlugin;
use Joomla\DI\Container;
use Joomla\Event\SubscriberInterface;
use Webtolk\Plugin\System\MVCFactoryOverride\MVC\ContentMVCFactoryDecorator;

final class MVCFactoryOverride extends CMSPlugin implements SubscriberInterface
{
    public static function getSubscribedEvents(): array
    {
        return [
            'onAfterExtensionBoot' => 'onAfterExtensionBoot',
        ];
    }

    public function onAfterExtensionBoot(AfterExtensionBootEvent $event): void
    {
        if ($event->getExtensionType() !== ComponentInterface::class) {
            return;
        }

        if (strtolower($event->getExtensionName()) !== 'content') {
            return;
        }

        // Берём контейнер дочерний! Именно он используется компонентом
        $container = $event->getContainer();

        if (!$container->has(MVCFactoryInterface::class) || $container->isProtected(MVCFactoryInterface::class)) {
            return;
        }

        $container->extend(
            MVCFactoryInterface::class,
            static function (MVCFactoryInterface $factory, Container $container): MVCFactoryInterface {
                return new ContentMVCFactoryDecorator($factory);
            }
        );
    }
}
```

Обратите внимание на имя расширения: для `com_content` в событии будет `content`, без префикса `com_`. Так приложение вызывает `bootComponent()` и передаёт имя в `loadExtension()`.

Чтобы современный плагин был создан через service provider, ему нужен `services/provider.php`.

```
<?php

\defined('_JEXEC') or die;

use Joomla\CMS\Extension\PluginInterface;
use Joomla\CMS\Factory;
use Joomla\CMS\Plugin\PluginHelper;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Webtolk\Plugin\System\MVCFactoryOverride\Extension\MVCFactoryOverride;

return new class () implements ServiceProviderInterface {
    public function register(Container $container): void
    {
        $container->set(
            PluginInterface::class,
            // $container->lazy начиная с Joomla 5.4.0.
            // Для Joomla ДО 5.4.0 нужно использовать анонимную функцию-замыкание.
            // Данный пример не будет работать на Joomla 4 и Joomla <5.4.0.
            $container->lazy(MVCFactoryOverride::class, function (Container $container) {
                $plugin = new MVCFactoryOverride(
                    (array) PluginHelper::getPlugin('system', 'mvcfactoryoverride')
                );

                $plugin->setApplication(Factory::getApplication());

                return $plugin;
            })
        );
    }
};
```

### Декоратор фабрики Joomla на минималках

Декоратор реализует тот же `MVCFactoryInterface` и получает исходную фабрику в конструктор. Это позволяет не создавать заново штатную `MVCFactory` и не угадывать, какие зависимости Joomla уже передала в неё через service provider.

```
<?php

namespace Webtolk\Plugin\System\MVCFactoryOverride\MVC;

\defined('_JEXEC') or die;

use Joomla\CMS\Application\CMSApplicationInterface;
use Joomla\CMS\MVC\Factory\MVCFactoryInterface;
use Joomla\Input\Input;

final class ContentMVCFactoryDecorator implements MVCFactoryInterface
{
    public function __construct(
        private readonly MVCFactoryInterface $inner
    ) {
    }

    public function createController($name, $prefix, array $config, CMSApplicationInterface $app, Input $input)
    {
        return $this->inner->createController($name, $prefix, $config, $app, $input);
    }

    public function createModel($name, $prefix = '', array $config = [])
    {
        if ($name === 'Article' && $prefix === 'Site') {
            // Здесь крутим и вертим модель ядра как нам нужно/
            // Или возвращаем собственный объект модели.
        }

        return $this->inner->createModel($name, $prefix, $config);
    }

    public function createView($name, $prefix = '', $type = '', array $config = [])
    {
        return $this->inner->createView($name, $prefix, $type, $config);
    }

    public function createTable($name, $prefix = '', array $config = [])
    {
        return $this->inner->createTable($name, $prefix, $config);
    }
}
```

Такой декоратор хорош как безопасная иллюстрация принципа и как рабочий путь для прямых вызовов `$component->getMVCFactory()->createModel(...)`. Но у него есть важная тонкость, которую легко пропустить.

### Важная ловушка: `createController()` и `$controller->getModel()`

В стандартной цепочке рендера компонента сначала создаётся контроллер. В `ComponentDispatcher::getController()` вызывается:

```
$controller = $this->mvcFactory->createController(
    $name,
    $client,
    $config,
    $this->app,
    $this->input
);
```

Если в контейнере лежит декоратор, этот вызов попадёт в декоратор. Но если декоратор просто делегирует `createController()` во внутреннюю фабрику, дальше вступает в дело `MVCFactory::createController()` ядра:

```
$controller = new $className($config, $this, $app, $input);
```

Здесь `$this` - уже внутренняя фабрика Joomla, а не ваш декоратор. Значит, контроллер сохранит внутри себя исходную фабрику. Когда потом `BaseController::getModel()` вызовет `$this->factory->createModel()`, он может пройти мимо вашего декоратора.

Отсюда практический вывод: **минимальный декоратор `createModel()` не всегда достаточен для подмены модели в обычном рендере компонента**. Он достаточен для прямых вызовов фабрики, но для контроллерной MVC-цепочки нужно отдельно проверить, какая фабрика попала в контроллер.

Теоретически можно сделать декоратор, который сам создаёт контроллер и передаёт в его конструктор именно себя. Но тогда вы начинаете повторять часть логики `MVCFactory::createController()`: вычисление класса, создание объекта, передачу зависимостей вроде form factory, dispatcher, router, cache controller, user factory, mailer factory и logger. В ядре часть этой инициализации находится в private-методах `MVCFactory`, поэтому аккуратно повторить её снаружи непросто.

Использовать рефлексию (reflection), чтобы после создания контроллера заменить protected-свойство `factory`, технически возможно, но это уже не хороший расширяемый API, а зависимость от внутреннего устройства `BaseController`.

## Способ 2. Полностью заменить `MVCFactoryInterface` через `$container->set()`

Другой путь - не декорировать фабрику, а зарегистрировать вместо неё свою. Например, наследоваться от `Joomla\CMS\MVC\Factory\MVCFactory` и переопределить `getClassName()`. Тогда все обычные методы `createController()`, `createModel()`, `createView()` и `createTable()` останутся из ядра, но имя класса можно будет заменить.

```
$container->set(
    MVCFactoryInterface::class,
    static function (Container $container) use ($extensionName) {
        $factory = new class ('Joomla\\Component\\' . ucfirst($extensionName)) extends MVCFactory {
            protected function getClassName(string $suffix, string $prefix)
            {
                $class = parent::getClassName($suffix, $prefix);

                return match ($class) {
                    \Joomla\Component\Content\Site\Model\ArticleModel::class
                        => \Webtolk\Plugin\System\MVCFactoryOverride\Model\ArticleModel::class,
                    default => $class,
                };
            }
        };

        // И мы помним, что фабрике нужны все зависимости, которые мы видим
        // в provider.php компонента. Устанавливаем их здесь.
        $factory->setFormFactory($container->get(FormFactoryInterface::class));
        $factory->setDispatcher($container->get(DispatcherInterface::class));
        $factory->setDatabase($container->get(DatabaseInterface::class));
        $factory->setSiteRouter($container->get(SiteRouter::class));
        $factory->setCacheControllerFactory($container->get(CacheControllerFactoryInterface::class));
        $factory->setUserFactory($container->get(UserFactoryInterface::class));
        $factory->setMailerFactory($container->get(MailerFactoryInterface::class));

        return $factory;
    }
);
```

Плюс этого подхода в том, что контроллер будет создан этой же фабрикой, а значит при последующем `$controller->getModel()` он снова обратится к ней. Для задачи "заменить вот эти модели ядра на мои классы" это понятная и прямая схема.

**Минус тоже очевиден:** теперь вы повторяете код из `Joomla\CMS\Extension\Service\Provider\MVCFactory`. В Joomla 6.1 provider выбирает между `MVCFactory` и `ApiMVCFactory` в зависимости от типа приложения и передаёт в фабрику несколько зависимостей: `FormFactoryInterface`, `DispatcherInterface`, `DatabaseInterface`, `SiteRouter`, `CacheControllerFactoryInterface`, `UserFactoryInterface`, `MailerFactoryInterface`. Если в будущей версии Joomla этот набор изменится, ваш код нужно будет проверить и обновить.

> **Что и как использовать?** `extend()` меньше привязан к внутренней сборке фабрики, но минимальный декоратор может не пере
> ватить модели, создаваемые через контроллер. Полная замена через `set()` лучше под
> одит для сопоставления "штатный класс - мой класс", но требует аккуратно повторять создание и настройку фабрики.

## Если компонент ваш собственный - плагин обычно не нужен

Да, и всю эту статью можно было не читать. Можно просто написать сразу вменяемую модель и/или другие классы компонента. Компонент сам объявляет свою инфраструктуру, и вам не нужно зависеть от порядка системных плагинов.

## Что с legacy-компонентами

Не каждый компонент в реальной жизни уже живёт в новой архитектуре. Если у компонента нет `services/provider.php` и Joomla включает fallback, создаётся `LegacyComponent`. Такой объект умеет вернуть `LegacyFactory` через `getMVCFactory()`, но это не тот же сценарий, где service provider компонента зарегистрировал `MVCFactoryInterface` в дочернем контейнере.

Поэтому перед подменой всегда надо проверить:

- это действительно компонент, а не модуль или плагин;
- имя компонента то, которое вам нужно;
- в контейнере есть `MVCFactoryInterface::class`;
- сервис не protected;
- вы вмешиваетесь до создания `ComponentInterface`.

## Что будет, если несколько плагинов Joomla меняют одну фабрику?

**Это риск.** Системные плагины выполняются в определённом порядке, установленным в админке сайта. Также на их выполнение влияет приоритет плагина, указанный в коде самого плагина для каждого конкретного триггера. Поэтому если несколько плагинов вмешиваются в `MVCFactoryInterface` одного и того же компонента, результат зависит от выбранного способа.

- Если несколько плагинов делают `$container->set(MVCFactoryInterface::class, ...)`, фактически выигрывает тот, кто перезаписал сервис последним. Дворовая шутка из за гаражей вспоминается: "Кто последний, тот и папа".
- Если несколько плагинов делают `$container->extend()`, может получиться цепочка декораторов. Что само по себе не страшно, так как паттерн декоратор для этого и придуман был. Главное, что он не во всех сценариях может гарантированно отработать.
- Если два плагина подменяют один и тот же класс модели, итоговый результат всё равно надо проверять отдельно.

Поэтому хороший плагин должен ограничивать область вмешательства: только нужный компонент, только нужный client, только нужная модель или table. Не стоит превращать `MVCFactory` в универсальный перехватчик всего сайта. И в целом нужно помнить. что MVCFactory может работать в разных типах приложения (сайт, админка, CLI, REST API и даже Daemon-приложение). И системные плагины тоже очень острый инструмент.

## Когда нужна подмена `MVCFactory`, а когда - нет?

Подмена фабрики нужна только в тех случаях, когда вы на проекте работаете с компонентом ядра Joomla (а их не так-то много на самом деле) и вам нужно изменить или дополнить работу штатных классов. Всё.

Если у вас свой компонент - вам не нужна подмена MVCFactory таким изощрённым способом и эта статья тоже.

## Об авторе

![Толкачев Сергей Юрьевич](https://web-tolk.ru/images/uslugi/sergey-tolkachyov-apr-2023.webp)

### Толкачев Сергей Юрьевич

Joomla-разработчик. [Контрибьютер ядра Joomla](https://github.com/joomla/joomla-cms/pulls?q=is%3Apr+author%3Asergeytolkachyov+). Один из ведущих Telegram-канала русскоязычного Joomla-сообщества [JoomlaFeed](https://t.me/joomlafeed), один из модераторов [чата русскоязычного Joomla-сообщества](https://t.me/joomlaru). Мои расширения в официальном маркетплейсе расширений Joomla - [Joomla Extensions Directory](https://extensions.joomla.org/profile/profile/details/528051/). Имею публикации в [официальном журнале международного Joomla-сообщества - Joomla Community Magazine](https://magazine.joomla.org/authors/sergeytolkachyov) и на [официальном сайте русскоязычного Joomla-сообщества](https://joomlaportal.ru/users/sergey-tolkachyov).

Муж. Отец 3 детей.

Россия, Саратов.

## JSON-LD Schema

```json
{
    "@context": "https://schema.org",
    "@type": "BreadcrumbList",
    "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17",
    "itemListElement": [
        {
            "@type": "ListItem",
            "position": 1,
            "item": {
                "@id": "https://web-tolk.ru/",
                "name": "Главная"
            }
        },
        {
            "@type": "ListItem",
            "position": 2,
            "item": {
                "@id": "https://web-tolk.ru/blog",
                "name": "Блог"
            }
        },
        {
            "@type": "ListItem",
            "position": 3,
            "item": {
                "name": "Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory"
            }
        }
    ]
}
```

```json
{
    "@context": "https://schema.org",
    "@graph": [
        {
            "@type": "Organization",
            "@id": "https://web-tolk.ru/#/schema/Organization/base",
            "name": "WebTolk",
            "url": "https://web-tolk.ru/",
            "logo": {
                "@type": "ImageObject",
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo",
                "url": "images/webtolk-1080p.jpg",
                "contentUrl": "images/webtolk-1080p.jpg",
                "width": 1920,
                "height": 1080
            },
            "image": {
                "@id": "https://web-tolk.ru/#/schema/ImageObject/logo"
            },
            "sameAs": [
                "https://github.com/WebTolk",
                "https://github.com/sergeytolkachyov",
                "https://vk.com/web_tolk",
                "https://vk.com/webtolkru",
                "https://tenchat.ru/sergeytolkachyov",
                "https://t.me/sergeytolkachyov",
                "https://t.me/webtolkru"
            ]
        },
        {
            "@type": "WebSite",
            "@id": "https://web-tolk.ru/#/schema/WebSite/base",
            "url": "https://web-tolk.ru/",
            "name": "WebTolk",
            "publisher": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            }
        },
        {
            "@type": "WebPage",
            "@id": "https://web-tolk.ru/#/schema/WebPage/base",
            "url": "https://web-tolk.ru/blog/pereopredelenie-klassov-yadra-joomla-s-pomoshchyu-plagina-na-primere-mvcfactory",
            "name": "Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory - WebTolk",
            "description": "Как без правки ядра Joomla подменять MVC-классы компонентов через системный плагин, DI-контейнер и MVCFactory. Сравниваем старый подход Joomla 2.5/3.x через onAfterRoute с             современной схемой Joomla5/6, показываем extend(), set(), риски декораторов, и порядок загрузки расширений в ядре.",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebSite/base"
            },
            "about": {
                "@id": "https://web-tolk.ru/#/schema/Organization/base"
            },
            "inLanguage": "ru-RU",
            "breadcrumb": {
                "@id": "https://web-tolk.ru/#/schema/BreadcrumbList/17"
            }
        },
        {
            "@type": "Article",
            "image": "https://web-tolk.ru/images/blog/pereopredelenie-klassov-yadra-joomla-s-pomoshchyu-plagina-na-primere-mvcfactory/header.webp",
            "headline": "Переопределение классов ядра Joomla с помощью плагина на примере MVCFactory",
            "description": "Как без правки ядра Joomla подменять MVC-классы компонентов через системный плагин, DI-контейнер и MVCFactory. Сравниваем старый подход Joomla 2.5/3.x через onAfterRoute с             современной схемой Joomla5/6, показываем extend(), set(), риски декораторов, и порядок загрузки расширений в ядре.",
            "author": {
                "@type": "person",
                "name": "Сергей Толкачев",
                "url": "https://web-tolk.ru",
                "email": "info@web-tolk.ru",
                "address": {
                    "@type": "PostalAddress",
                    "addressLocality": "Саратов"
                }
            },
            "datePublished": "2026-05-08T00:00:00+00:00",
            "@id": "https://web-tolk.ru/#/schema/com_content/article/177",
            "isPartOf": {
                "@id": "https://web-tolk.ru/#/schema/WebPage/base"
            }
        }
    ]
}
```
