Всем привет! Это третий сезон курса «Паттерны и практики написания кода». Первая серия вводная: в ней бэкенд-инженер Юра Афанасьев знакомит вас с историей паттернов и их функциями, а также рассказывает, как с ними правильно работать.
Подписывайтесь на канал AvitoTech в Telegram, там мы рассказываем больше о профессиональном опыте наших инженеров, проектах и работе в Авито, а также анонсируем митапы и статьи.
Довелось мне в последнее время поглядеть на "современные" проекты на Kotlin, и я признаюсь был поражен повсеместным использованием корутин в коде. Используют их просто везде: для обработки входящих соединений, для запросов в базы данных, для вызова внешних API, для операций с файлами на сетевых файловых системах. Знаете, в языке есть слова-паразиты — так вот кажется мне что в Kotlin корутины — это паттерн-паразит. Кажется корутины — это как магические заклинания, которые волшебным образом запустят код "где-то там, в локальном облаке".
Каждый раз когда я сталкиваюсь с туториалами про корутины — самым первым примером идёт конечно sleep на 10 тысяч "потоков". Притом как вы понимаете, вызывать нужно специально подготовленный для этого sleep, а не настоящий Thread.sleep — потому что с ним никакой магии не получится. Это создаёт обманчивое впечатление будто бы любая блокирующая операция автоматически волшебным образом превращается в асинхронную, за бесплатно увеличивая пропускную способность приложения. Хотелось бы напомнить любителям корутин что это не так.
Никто за вас не перепишет обычное чтение из сокета/файлового дескриптора на асинхронные ввод-вывод с проверкой наличия байт для чтения во входящем потоке через Selector::select — чудес не бывает. И как бы вы не увеличивали kotlinx.coroutines.io.parallelism рано или поздно при безудержном использовании корутин пул потоков забьётся чем-нибудь медленным и ваше приложение просто встанет раком.
Для примера попросите чатжпт написать вам простейший echo socket server на корутинах. Ну и клиента, который попробует работать с этим сервером в 100 обычных потоков (не так уж и много для нормального продакшн бэкенда). Надеюсь для вас не будет сюрпризом то, что сервер будет обрабатывать только 64 первых счастливых клиентов, а к остальным доберётся только когда эти первые клиенты закроют соединения.
Кардинальное упрощение привязки GitHub, GitLab, Bitbucket в Amvera Cloud
Привязка репозиториев GitHub, GitLab, BitBucketвызывала у наших пользователей затруднения, и мы обещали упростить процесс.
Теперь для привязки репозитория достаточно указать токен и выбрать ветку и репозиторий.
Способ позволяет организовать максимально простой деплой и обновление приложений через git push. Для обновления приложения достаточно сделать коммит в привязанный репозиторий, и оно соберется и бесшовно запустится автоматически.
Заполняем три поля и CI/CD готов
Подробная инструкция по подключению доступна по ссылке.
Amvera Cloud — это облако для простого деплоя приложений через git push (или интерфейс). Встроенный CI/CD, бэкапы и мониторинг позволяют развернуть проект тремя командами в IDE и не думать о настойке инфраструктуры. Amvera проще, чем использование VPS.
Магические шестиугольники. Обзор соревнования по программированию
Приветствую всех любителей спортивного программирования!
В данной статье рассмотрим интересный сайт для программистов - Al Zimmermann's Programming Contests.
На этой площадке проводится несколько соревнований в год. Каждое соревнование (контест) длится в среднем несколько месяцев. Задачи по своей формулировке не очень сложные. Но вот над решениями вам придётся изрядно потрудится.
В ряде случаев Joomla может взаимодействовать с нашим компонентом. Например:
Роутер Joomla может использовать роутер нашего компонента для анализа и создания ЧПУ-адресов;
Если наш компонент поддерживает категории, то com_categories будет отображать в представлении категорий сводку по каждой категории, содержащую количество материалов с этой категорией, с разбивкой по статусу публикации;
Если наш компонент поддерживает пользовательские поля, то com_fields будет вызывать метод getContexts(), чтобы получить типы материалов, к которым можно привязать пользовательские поля;
Если наш компонент поддерживает мультиязычные связи, то com_associations потребуется знать типы материалов, для которых возможны связи;
И, конечно же, Joomla потребуется запустить наш компонент, чтобы получить вывод.
Причины введения класса расширения становятся понятнее, если рассмотреть, как другие части кода Joomla взаимодействовали с нашим расширением в Joomla 3.
В Joomla 3 все эти части кода обращались к кодовой базе нашего компонента довольно хаотичным образом — вызывая функции из различных вспомогательных файлов.
А в Joomla 4 это упрощено:
Начиная с Joomla 4, другие компоненты получают доступ к нашему компоненту com_example, вызывая метод:
$extension = $app->bootComponent("com_example");
Затем они могут вызывать необходимые им методы через этот экземпляр класса расширения.
Справка:
Класс расширения (Extension) компонента вы можете найти в административной части компонента. Например, для компонента com_content это файл Root/administrator/components/com_content/src/Extension/ContentComponent.php, для com_example — Root/administrator/components/com_example/src/Extension/ExampleComponent.php
Сразу после создания экземпляра класса расширения компонента код библиотеки Joomla вызовет метод boot вашего класса расширения, передавая дочерний экземпляр Контейнера внедрения зависимостей (Dependency Injection Container):
$extension->boot($container);
По сути, это возможность делать буквально всё, что вам нужно. Например, метод иногда используется для настройки определённых классов, которые будут использоваться с вызовами HtmlHelper::_(). Или же можно сохранить ссылку на ваш дочерний DI-контейнер (который иначе может быть сложно получить).
После первого создания экземпляра вашего компонента Joomla кэширует этот экземпляр. При повторном вызове
$extension = $app->bootComponent("com_example");
Joomla просто возвращает существующий экземпляр вашего класса расширения, вместо повторного создания объекта и вызова метода boot(). Вы даже можете вызвать bootComponent, передав свой компонент, чтобы получить ссылку на свой собственный объект расширения.
Всякий раз, когда Joomla загружает расширение, она создает дочерний Dependency Injection Container (далее контейнер), исключительно для использования этого расширения. Это показано на схеме ниже.
Child DIC
Дочерний контейнер содержит указатель на родительский контейнер и функционирует аналогично основному контейнеру, но с некоторыми отличиями:
При каждом вызове метода set() для этого дочернего контенера пара ключ-значение добавляется в дочерний контейнер;
При каждом вызове метода get() для этого дочернего контейнера ресурс извлекается из дочернего контейнера, но если он там не найден, то поиск выполняется также в родительском контейнере.
Начиная с версии Joomla 4, разработчики Joomla рекомендуют создателям расширений использовать внедрение зависимостей (dependency injection) для своих расширений, определяя файл services/provider.php. Загрузка расширения теперь выполняется в два этапа, которые обрабатываются внутри файла services/provider.php:
Класс расширения регистрируется в дочернем контейнере;
Класс расширения извлекается из дочернего контейнера, создавая экземпляр этого класса.
Давайте рассмотрим минимальный пример этого для компонента com_example с пространством имён Mycompany\Component\Example.
use Joomla\CMS\Extension\ComponentInterface;
use Joomla\DI\Container;
use Joomla\DI\ServiceProviderInterface;
use Mycompany\Component\Example\Administrator\Extension\ExampleComponent;
return new class implements ServiceProviderInterface
{
public function register(Container $container): void
{
$container->set(
ComponentInterface::class,
function (Container $container)
{
$component = new ExampleComponent();
return $component;
}
);
}
};
Мы видим, что когда Joomla выполняет require этого PHP-файла, возвращается экземпляр класса.
$provider = require $path; // $path points to the relevant services/provider.php file
Переменная $provider указывает на объект, который является экземпляром этого анонимного класса. Кроме того, класс реализует интерфейс Joomla\DI\ServiceProviderInterface, что, по сути, означает, что он содержит метод register с указанной выше сигнатурой.
Когда Joomla выполняет
if ($provider instanceof ServiceProviderInterface)
{
$provider->register($container);
будет вызван метод register, который добавит запись в дочерний контейнер с
key - ComponentInterface::class – это сокращённый способ в PHP указать строку 'Joomla\CMS\Extension\ComponentInterface';
value - функция, которая возвращает новый экземпляр класса ExampleComponent (основного класса расширения компонента com_example).
$extension = $container->get($type);
Функция выше будет запущена и вернет новый экземпляр ExampleComponent.
Вы можете самостоятельно изучить код Joomla в файле libraries/src/Extension/ExtensionManagerTrait.php и убедиться, что описанный выше шаблон также применяется к модулям и плагинам.
Обратите также внимание на следующую строку в этом файле:
if ($extension instanceof BootableExtensionInterface)
{
$extension->boot($container);
}
Таким образом, если класс расширения реализует интерфейс BootableExtensionInterface, Joomla немедленно вызовет метод boot() экземпляра расширения, как описано в документации по расширениям.
Последнее время замечаю тенденцию, что многие хостинги и публичные облака вводят платную поддержку.
Позиционируется поддержка как дополнительная опция и гарантия времени ответа. Но на мой взгляд это выглядит как вымогательство денег, когда компания может оказывать качественную поддержку, но если вы их не “подкупите” дополнительно, не будет.
Я основатель облака для простого деплоя проектов через Git push – Amvera Cloud. И вижу, что пользователи пишут нам в поддержку. И говоря честно – люди пишут только тогда, когда другие способы не помогли и они не знают, как решить их насущную проблему. А это значит мы не доработали и сделали что-то непонятно или неудобно. И это наша обязанность постараться им помочь. И я не вижу морального права просить за это с них деньги.
Поддержка может работать не идеально, можно даже разделять клиентов во время высокой нагрузки на постоянных или новых, с высоким или низким чеком или любым другим образом, чтобы кому-то помочь в первую очередь. Но помочь нужно всем.
И главное, я не верю, что на платной поддержке можно сильно заработать. Это просто предлог, чтобы работать хуже, чем может компания.
Поддержка должна быть бесплатной, всегда, и без всяких но!
Они объявлены устаревшими с Joomla 4.x и вместо них предлагается использовать Joomla\CMS\Factory::getContainer()->get('Соответствующий_интерфейс').
Например, вместо Joomla\CMS\Factory::getDbo() — Factory::getContainer()->get(DatabaseInterface::class);
Но давайте посмотрим на описание метода Joomla\CMS\Factory::getContainer():
Возвращает глобальный контейнер сервисов, создавая его только в случае отсутствия.
Этот метод рекомендуется использовать только в коде, ответственном за создание новых сервисов и требующем разрешения зависимостей. Его следует применять только когда контейнер недоступен другими способами.
Допустимые сценарии использования:
Статический метод getInstance(), вызывающий сервис из контейнера (пример: Joomla\CMS\Toolbar\Toolbar::getInstance());
Фронт-контроллер приложения, загружающий и исполняющий класс Joomla (пример: файл cli/joomla.php);
Получение опциональных зависимостей конструктора во время переходного периода для сохранения обратной совместимости (в этом случае следует добавлять уведомление об устаревании).
Не рекомендуется использовать этот метод как прямую замену статическим вызовам, например заменять Factory::getDbo() на Factory::getContainer()->get(DatabaseInterface::class). Вместо этого код следует рефакторить для использования внедрения зависимостей.
В последнем абзаце видим явное противоречие и рекомендацию использовать зависимости.
Как же внедрять зависимости?
Рассмотрим как это сделано в плагине joomla группы content:
В сервис-провайдере плагина Root/plugins/content/joomla/services/provider.php используются методы setDatabase() и setUserFactory() для установки зависимостей.
А в классе расширения плагина plugins/content/joomla/src/Extension/Joomla.php используются методы getDatabase() и getUserFactory(). Аналогично в компонентах.
Мой пример использования:
В компоненте обновления цен для JoomShopping, в моделях работающих с товарами я заменил драйвер базы данных на свой, соединяющийся с базой другого сайта. Код самих моделей при этом изменять не потребовалось.
Выводы:
Сервис-провайдер расширения — единая точка установки зависимостей для своего расширения.
Событие onAfterExtensionBoot - точка для замены зависимости в любом расширении.
Если бы расширения напрямую брали зависимости из глобального контейнера, такая замена была бы невозможна.
Есть такая мантра в гошке - "всегда обрабатывать ошибки"
А ведь так хочется чтобы они сами наверх прокидывались...
Недавно работал с либой валидатора, и нашел функцию у которой можно не проверять ошибку так как если она не отработает то приложение не запустится в любом случае
Апрель подошел к концу, и мы знакомим вас с обновлениями на платформе, которые произошли за прошедший месяц.
Функционал передачи проекта другому пользователю.
Теперь вы можете передать свой проект другому пользователю, имеющему аккаунт на платформе (для этого пользователю заранее нужно предоставить доступ к проекту). Новая фича доступна в проекте в разделе "Настройки/Пользователи".
Функционал будет полезен студиям, фрилансерам и всем тем, кто занимается заказной разработкой. Важная особенность: после передачи проекта на баланс заказчику, вы можете (опционально) оставить доступ к проекту для осуществления поддержки.
Дополнения и доработки:
Исправлена ошибка, которая могла приводить к зависанию статуса контейнер
Для «Сетевых дисков» добавлена поддержка subPath, что позволяет подключать отдельные папки «Сетевого диска» к контейнеру.
Исправлена ошибка, которая могла приводить к разрыву соединения через «Сетевой сервис».
Исправлена ошибка, которая приводила к излишней буферизации в «Сетевые сервисы / Маршруты».
Исправлена ошибка веб-интерфейса, в результате которой общее количество ресурсов не обновлялось при удаление из проекта контейнера или сетевого диска
Максимальное значение для переменных было увеличено до 1024 символов.
Продолжаем развивать наш каталог и в апреле добавили 3 новых приложения:
Совместно с Российской платформой NextBoxреализовали интеграцию в каталог приложений цифровой платформы для хранения и обработки данных.
NextBox помогает компаниям безопасно хранить и обмениваться корпоративными файлами, совместно работать с документами любых форматов в едином пространстве.
Также добавили 2 новых self-hosted приложения:
Redash - открытая платформа для визуализации данных и бизнес-аналитики, которая предоставляет простой интерфейс для создания дашбордов, запросов и отчетов. Сервис позволяет работать с различными источниками данных, включая базы данных, API и файлы, а также поддерживает создание пользовательских SQL-запросов для глубокого анализа информации.
KeyDB- высокопроизводительная база данных, полностью совместимая с Redis, которая использует многопоточную архитектуру для повышения пропускной способности. Она идеально подходит для приложений, требующих низкой задержки и высокой доступности, обеспечивая при этом простую интеграцию с существующими системами на базе Redis.
Dockhost— облачная платформа для хостинга приложений на основе Docker‑контейнеров (боты, сайты, базы данных и т. д.), которая позволяет запускать и масштабировать как простые проекты, так и сложные микросервисные приложения без необходимости настраивать и контролировать инфраструктуру.
Как вам такой сценарий. Некоторая группа специалистов по информационной безопасности (в плохом смысле) создаёт публичный сайт-прокси который рекламируется как бесплатное решение всех проблем программиста.
Когда программист делает запрос на генерацию кода скажем бекенда для веб сервера его запрос проксируется в ChatGPT, а потом модифицируется скриптом, который добавляет небезопасный код.
Попутно сайт собирает информацию о самом программисте, например, IP адрес, а в случае регистрации - email работодателя.
Информация сохраняется для разработки, через полгода-год когда код гарантировано попадает на прод происходит кибератака.
Читаемость Си-кода: грустный ликбез, чтобы жить стало веселее
Многие коллеги по цеху подтвердят, что читаемость кода на языке Си иногда оставляет желать лучшего. Как подтвердят и те, кто им плохо владеет, но так или иначе сталкивается в связи с рабочими задачами.
И всему виной (ожидаемо) выражения препроцессора и, как следствие, макросы. Да, вы правильно подумали. Именно те части кода, в которых вызываются такие легенды как:
Последние два очень легко спутать, если читать код невнимательно. Вместо них иногда рекомендуют использовать
#if defined // вместо #ifdef
#if !defined // вместо #ifndef
Но если же в компании/проекте/отделе нет определенного код-стайла, или он не предполагает написание длинного варианта, то, естественно, разрабы пишут короткую форму (я, честно признаться, тоже).
Чего далеко ходить - все open source проекты пестрят именно сокращенными вариантами этих выражений и с этим уже ничего не поделаешь.
А вот первое выражение из первой тройки игроков иногда вообще открывает врата ада, когда используется многострочный макрос с бэкслэш-символами (\) на концах строк.
Конечно же выражения препроцессора - это очень гибкий и полезный инструмент, позволяющий делать всё:
условную компиляцию;
выравнивание структур;
предотвращать повторные включения файла;
работать со строками;
грамотно оборачивать повторяющийся код и т.д. и т.п.
Но бывают случаи, когда упрощение кода с точки зрения алгоритма делает его слабо читаемым для того самого бедного программиста, пытающегося прочесть этот шедевр машинописного текста:
который определяется после объявления полей структуры.
Как обрабатывается аргумент x:
имя аргумента преобразуется в строку с помощью макроса "#x" и присваивается полю *name;
полю *value присваивается значение поля структуры b с названием аргумента x (тут нужно убедиться, что поле с именем x действительно существует в структуре b).
То есть чтобы при заполнении массива не писать каждый раз одинаковые строчки:
Ну и в самом конце вызываем удаление созданного макроса, чтобы оно не было использовано в коде в дальнейшем:
#undef _SPECIAL
Теперь после прочтения этого небольшого ликбеза вы можете смело пользоваться макросами и создавать более сложные и нечитаемые шедевры наконец-то разобрались в том, насколько гибкими могут оказаться выражения препроцессора ( вообще не понимаю, как вы жили без них раньше...)
Далее про то как сделать выделенный порт и http-пулл для promitheus-метрик, так вот докладываю👇
Мы используем embedded tomcat как и великое множество других Java-проектов(остальным сил и терпения), по этому этот пост применительно к нему.
📍Для создания выделенного порта достаточно создать отдельный коннектор спринговой java-конфигурацией и проинициализировать его через TomcatServletWebServerFactory.
❕Стоит обратить внимание что на данном порту будут доступны вообще все сервлеты доступные и на стандартном порту, но при наличии разграничения доступа обычно это не является проблемой.
P.S. Не хочу быть злом по этому в комменты в свою телегу выложу класс в текстовом варианте😁
Наверняка многие из вас юзают Apache POI. Для тех, кто не знает: это джаванская либа для работы с файлами Microsoft Office и OOXML — всякие Excel, Word и прочее.
А в чём опасность?
При генерации Excel-файла эта библиотека позволяет включить отслеживание ширины данных в колонках и автоподгонку ширины столбцов под содержимое (чтобы данные не скрывались из-за маленькой ширины столбца по умолчанию).
Включается это так: // Отслеживаем ширину для автосайзинга sheet.trackAllColumnsForAutoSizing(); // ... генерируем строки Excel-файла // Автосайзим столбцы sheet.autoSizeColumn(columnIndex);
Проблема: эта тема крайне ресурсоёмкая на больших объёмах данных. На тесте с 100 строками — всё быстро. На проде с 600К строк — 20 минут превращаются в 4 часа. 4 часа, Карл! Чего оно там делает вообще?!)
У нас такое не раз уезжало в прод и штука эта совсем неприятная я вам скажу:
Отчёт не собирается вовремя;
Заказчик негодует;
Паника, крики — в общем, ну его этот трекинг! 😅
Что делать?
В 99% случаев: достаточно автосайзинга только для заголовков:
В 1% случаев: нужно отавтосайзить 1-2 столбца из сотен. Тогда делаем точечный трекинг:
// Трекинг только нужных колонок sheet.trackColumnForAutoSizing(columnIndex); // ... генерация данных // Автосайзим sheet.autoSizeColumn(columnIndex);
Ну и можно конечно вручную задавать ширину столбцов, но это не всегда удобно.
P.S. Метод autoSizeColumn() очень тяжёлый. Выдержка из джавадоки:
Этот процесс может быть медленным на больших листах, поэтому его стоит вызывать только один раз для каждого столбца в конце обработки.
P.S.S. Еще кстати поя умеют не буферизовать весь excel’ник в память при генерации, надеюсь вы все этим пользуетесь ибо обратное дико некармическая тема)) Знаете ж как?
В 2ГИС мы пишем на Go и помогаем инженерам перейти на него с других языков. Знакомство с Go открывает возможность контрибьютить в одну из самых востребованных технологий современности. На Go написаны проекты, без которых сложно представить мир распределённых систем: K8s, CockroachDB, Badger, Prometheus, VictoriaMetrics, Jaeger, NATS, Temporal.
Переход на Go — реальность! В карточках рассказываем, как это получилось у Саши ↓
Хочешь так же? Прямо сейчас ищем ребят с Java и C#.
Такой способ позволяет разворачивать Docker‑образы без использования реестра — просто, удобно и быстро. Если у вас по любой причине нет реестра, но хочется использовать Docker, вы можете найти это полезным. Надеюсь, кому‑то пригодится в реальной практике. Если вы используете другие подходы или автоматизируете процесс — поделитесь в комментариях. Спасибо за внимание!
Привет, Хабр. Делимся подборкой ближайших открытых уроков. Это вебинары с преподавателями-практиками, где можно разобраться в теме и задать вопросы экспертам. Участие бесплатное, требуется регистрация.
Недавно ревьюил один интересный PR в CPython: в питон добавили еще один способ форматировать строки. Теперь – со специальным АПИ для внешних интеграций. Расскажу: как и зачем.
Основная причина: использовать f строки удобно, но нет никакого АПИ для перехвата момента "вставки" или интерполяции значений. Например, при форматировании html или sql – требуется специальным образом делать escape для значений. И раньше код вида f"{template}" представлял собой дыру в безопасности и потенциальное место для XSS.
string.templatelib.Template
Новый префикс t не будет создавать объект str, он будет создавать объект класса string.templatelib.Template:
Обратите внимание, что при создании template – у нас не произошло форматирование сразу. Мы создали объект, у которого есть свойства strings и interpolations, из которых можно собрать финальную отформатированную строку.
Давайте посмотрим на примере. Допустим, мы хотим формировать URL из наших данных:
>>> domain = 'example.com'
>>> query = 'python string formatting is too complex'
>>> template = t'https://{domain}?q={query}'
И сам код логики форматирования, где мы будем вставлять значения разным способом. Если у нас шаблон query, то мы будем использовать quote_plus для его форматирования. Остальные значения – будем вставлять как есть:
>>> from string.templatelib import Template, Interpolation
>>> from urllib.parse import quote_plus
>>> def format_url(template: Template) -> str:
... parts = []
... for part in template:
... match part:
... case str() as s: # regular string
... parts.append(s)
... case Interpolation(value, expression='query'):
... parts.append(quote_plus(value))
... case Interpolation(value):
... parts.append(value)
... return ''.join(parts)
Архитектура программной системы - это набор ограничений, которые формируют и направляют реализацию системы в сторону максимизации её ценности
Архитектура - это набор ограничений
Это набор ограничений: архитектура задаёт рамки/правила по которым должна строиться система
Архитектура формирует и направляет реализацию: у разработчиков должна быть свобода в реализации, но архитектура задаёт для них ограничения
Архитектура максимизирует ценность: ограничения поставлены так, чтобы вписываясь в них система принесла больше ценности. Именно архитектура определяет: какими паттернами реализовывать бизнес-логику, как связываться со сторонними сервисами, из каких категорий будет состоять программный код
И я придумал для этого метафору. Представьте, что мы строим дом. Тогда:
Системный архитектор: Нарисует чертежи всего дома (фасада, этажей). Задаст форму квартирам: сколько в квартире комнат, куда проведены трубы и электричество. Исходя из этого, сан-узел можно будет поставить только в одной комнате (куда проведены вода и канализация), а кухню только в другой (куда проведена вода и газ). Системный архитектор наложил на весь дом и на каждую квартиру верхне-уровневые ограничения: структурные (сколько в каждой квартире комнат, где сан-узел, где кухня, а где жилые комнаты) и поведенческие (это жилое помещение, а не склад или магазин). Но в какой комнате будет детская, а в какой рабочий кабинет - решать не ему.
Системный дизайнер: Он как дизайнер интерьеров. Определяет назначение каждой комнаты, учитывая уже наложенные архитектурные ограничения. Где будет гостиная, где спальня, где рабочий кабинет и т.п. И расставляет мебель (структурные ограничения), руководствуясь назначением каждой комнаты (поведенческие ограничения). И всё это так, чтобы живущим в квартире людям было удобно.
Программист: Это рабочий, который по чертежам дизайнера положит паркет, подключит фурнитуру и соберёт мебель.
Хотя названия этих ролей подходит под метафору, сам я считаю, что всю вышеописанную работу делают программисты. Я не считаю, что компания выиграет, если будет иметь отдельную должность системного архитектора или системного дизайнера. Для меня это больше грейды, чем профессия:
Системный архитектор - senior (опытный программист, способный спроектировать кластер контейнеров)
Системный дизайнер - middle (программист, способный спроектировать один контейнер)
Программист - junior (любой программист, способный работать внутри контейнера по готовым ограничениям)
Работаем в лоб: Прямое редактирование XML форм Joomla
Используя событие onContentPrepareForm можно изменять почти любую форму Joomla, но методов класса Joomla\CMS\Form\Form обычно не хватает для работы со сложными формами (например с полями типа subform).
Но есть простое решение - работать с формой как c экземпляром SimpleXMLElement.
Получаем XML формы.
echo $form->getXml()->asXMl();
die;
Отправляем его в ChatGPT с описанием что и как надо изменить в форме, и просим написать PHP-код.