Всего 125 212 комментариев

Николай Савин
39 минут назад
+1
Тогда вопрос: а зачем нужен Phinx? При создании новых полей он не участвует, новые таблицы не создаёт. Миграции происходят только при обновлении компонента?
Смотри. В конструкторе полей, поле добавляется в нативную таблицу ms3_products. Это делает миграция.
Путь такой.
1. Заполняю модальное окно данными, жму сохранить
2. Контроллер создает миграцию (Alter table Add column) и сразу же ее запускает. Вот на этом моменте используется phinx
3. Делаем запись в таблицу extra_fields.
4. Ну и где то за кадром происходит слияние нативной карты модели msProductData и новых дополнительных полей.

Примерно такая же схема при удалении поля.
Жму кнопку удалить, создается миграция на удаление колонки из таблицы. Она запускается сразу же, после создания.

Я думал, что после миграции ты запускаешь билдер
Почему схема такая, а не предложенный тобой вариант.
Ну для начала поля из extra_field можно отключать. Колонка физически остается в базе данных, но перестает попадать в карту msProductData. Это может быть полезным.

Кроме того физически перегенерированную карту msProductData обновление минишопа просто перезапишет. Можно конечно это разрулить на уровне резолверов, но зачем? Предложенная схема вроде справляется с задачей
Aleksandr Huz
50 минут назад
0
А как ты их между собой связал? Это же разные сущности.
Я думал, что после миграции ты запускаешь билдер, который будет обновлять схему, карту и классы, и тогда магия $modx->map[$class] не нужна.

Но, похоже, это не так. Тогда вопрос: а зачем нужен Phinx? При создании новых полей он не участвует, новые таблицы не создаёт. Миграции происходят только при обновлении компонента?

Допустим, в новой версии компонента добавлено новое поле — значит, нужно создавать новую миграцию и обновлять схему ещё раз?

Честно говоря, я не понимаю явного преимущества использования phinx в этом кейсе.
Ранее, мы использовали резолверы для создания и обновления таблиц и это почти всегда приводило к неприятностям, по мере появления новых полей
Может быть, просто потому что я с такими неприятностями не сталкивался.
Николай Савин
2 часа назад
0
Привет.
Будет ли поддержка modx3?
Так MS3 целиком предназначен для MODX3. Я не занимаюсь развитием платформы для MODX2.
Продукты будут в отдельной таблице?
Тут ничего не изменилось. Поля товара как хранились в ms2_products так и хранятся.
Миграции phinx — а xpdo объекты будут работать
А как ты их между собой связал? Это же разные сущности.
Если вопрос о том, как новые поля попадут в xpdo модель — то там наш старый привычный еще из ms2 способ, который на лету обновляет $meta['map']

Цепочка загрузки:
  • 1. OnMODXInit → $ms3->loadMap()
  • 2. MiniShop3::loadMap() → $this->extraFields->loadMap()
  • 3. ExtraFields::loadMap() → загружает ms3_extra_fields (active=1)
  • 4. ExtraFields::getFieldInfo() → формирует метаданные
  • 5. ExtraFields::addFieldToMap() → МОДИФИЦИРУЕТ $modx->map[$class] ← ВОТ ГДЕ МАГИЯ!
  • 6. xPDO видит новые поля как нативные
Если придираться, то:
Тут я бы разделил ответ
1. Репозиторий — это паттерн. Нет четких правил по его использованию или обязательств его использовать. Так что каждый волен делать так, как видит. Вот Laravel до пятой версии вообще всю логику внутри моделей тягал и ничего.
2. На практике, насколько я видел как пишут другие, сервис и репозиторий по сути своей одно и тоже. Их задача разгрузить контроллер, вынести логику в отдельный контейнер.

Я вижу так.
Контроллер принимает запрос от API и отдает ответ. На этом все. Его зона ответственности Request-Магия-Response.
Репозиторий и\или Service — логика. Здесь идет разбор запроса, обращения к DB, построение ответа. Задача сервиса — подготовить ответ согласно запроса.
Model — исключительно работа с базой. Ее задача сходит в базу и выполнить поставленную задачу.
Aleksandr Huz
3 часа назад
0
Отличный план.

1. Будет ли поддержка modx3?
2. Продукты будут в отдельной таблице?
3. Миграции phinx — а xpdo объекты будут работать? Миграция будет запускаться вручную или при обновлении компонента? Например, при создании поля, что будет происходить?

Сервисы создают записи, обновляют и удаляют ее, дергая каждый свою XPDO модель.
Если придираться, то:
Сервис — это бизнес-логика и обработка данных
Репозиторий — запросы к базе
Контроллер — запросы, ответы и валидация данных

То есть, контроллер вызывает сервис, сервис вызывает репозиторий, а он в свою очередь обращается к модели.
Алексей Суслов
8 часов назад
0
Мой верный помощник Claude Code
Поделитесь, пожалуйста, вашим опытом работы с ним — какой план используете, сталкиваетесь ли при ежедневной работе с нехваткой лимитов, сравнивали ли с другими ИИ?
Денис
Вчера в 22:53
0
доброго времени суток.
Актуализируйте плиз, всё по статье рабочее?
если пресет не прописывать в sendit.inc.php то письмо о заказе прилетает на почту (и в письме кстати нет ID товара), но в заказах minishop не показывается
если прописать пресет, то ругается что ID не заполнен…
если вручную даже прописать или вообще из пресета убрать обязательность поля ID то после нажатия на кнопку просто ничего не происходит…
в чем может быть причина? всё как в статье, за исключением иправления косяка в JS — oncLickModal.
Евгений
Вчера в 06:20
0
Есть форма на FormIt с полем типа файл — как это поле будет отображаться в гугл таблице? Смогут ли просмотреть этот файл все пользователи, кто имеет доступ к таблице?
Николай Артамонов
23 октября 2025, 21:08
0
Сам отвечу, может кому-то пригодится.

В классе компонента и его плагине есть проверки статуса заказа. Если статус отличный от «новый», то там сразу идет редирект на страницу ошибки оплаты без отправки данных в платежную систему и соответвенно без колбека от нее.

В моем случае письмо со ссылкой на оплату отправляется при выставлении дополнительного статуса заказа «Принят к оплате». Соответвенно сразу фейлится.

В итоге пришлось сделать копию класса и внести правки – добавить новый статус и поправить условие проверки.
Roman
23 октября 2025, 13:18
0
Ну не знаю, только устарновил Sendit и у форм c AjaxForm появились уведомления
Роман
23 октября 2025, 07:34
0
на событие OnBeforeDocFormSave сработал этот вариант:
<?php
/**
 * Plugin: Unique URL Checker
 * Event: OnBeforeDocFormSave
 */

$resource = $scriptProperties['resource'];
if (!($resource instanceof modResource)) {
    return;
}

$mode = $scriptProperties['mode'];
$pagetitle = $resource->get('pagetitle');

// Генерируем базовый alias из pagetitle
$baseAlias = $resource->cleanAlias($pagetitle);

if (empty($baseAlias)) {
    return;
}

$context = $resource->get('context_key');
$parent = $resource->get('parent');
$resourceId = $resource->get('id');

$finalAlias = $baseAlias;
$counter = 1;
$maxAttempts = 100;

while ($counter <= $maxAttempts) {
    $criteria = [
        'alias' => $finalAlias,
        'context_key' => $context,
        'parent' => $parent,
        'id:!=' => $resourceId,
        'deleted' => false
    ];
    
    $existing = $modx->getCount('modResource', $criteria);
    
    if ($existing == 0) {
        break;
    }
    
    $finalAlias = $baseAlias . '-' . $counter;
    $counter++;
}

// Устанавливаем финальный alias
$resource->set('alias', $finalAlias);

if ($finalAlias != $baseAlias) {
    $modx->log(modX::LOG_LEVEL_INFO, 
        "Сгенерирован уникальный alias: {$finalAlias} (ресурс ID: {$resourceId})"
    );
}

return;
Роман
23 октября 2025, 07:20
0
вот ИИ предложил, сам не проверял:
<?php
/**
 * Plugin: Unique URL Checker
 * Event: OnDocFormSave
 * Description: Проверяет уникальность URL документа и добавляет цифры к alias при дублировании
 */

// Проверяем, что мы в контексте сохранения документа
if ($mode == modSystemEvent::MODE_NEW || $mode == modSystemEvent::MODE_UPD) {
    $resource = &$scriptProperties['resource'];
    
    // Пропускаем если это не обычный ресурс или если alias пустой
    if (!($resource instanceof modResource) || empty($resource->get('alias'))) {
        return;
    }
    
    // Получаем контекст и текущий alias
    $context = $resource->get('context_key');
    $originalAlias = $resource->get('alias');
    $currentAlias = $originalAlias;
    $parent = $resource->get('parent');
    
    // Исключаем текущий ресурс из проверки при обновлении
    $excludeId = $resource->get('id') ?: 0;
    
    $counter = 1;
    $maxAttempts = 100; // Защита от бесконечного цикла
    
    // Проверяем уникальность alias
    while ($counter <= $maxAttempts) {
        // Проверяем существование ресурса с таким alias в том же контексте и родителе
        $criteria = [
            'alias' => $currentAlias,
            'context_key' => $context,
            'parent' => $parent,
            'id:!=' => $excludeId,
            'deleted' => false
        ];
        
        $existingResource = $modx->getObject('modResource', $criteria);
        
        // Если ресурс с таким alias не найден - выходим из цикла
        if (!$existingResource) {
            break;
        }
        
        // Если нашли дубликат - добавляем цифру к alias
        if ($counter == 1) {
            $currentAlias = $originalAlias . '-' . $counter;
        } else {
            // Увеличиваем число в суффиксе
            $currentAlias = preg_replace('/-\d+$/', '-' . $counter, $originalAlias);
        }
        
        $counter++;
    }
    
    // Если alias изменился - сохраняем новый alias
    if ($currentAlias != $originalAlias) {
        $resource->set('alias', $currentAlias);
        
        // Добавляем сообщение в системный лог
        $modx->log(modX::LOG_LEVEL_INFO, 
            "Автоматически изменен alias ресурса {$resource->get('id')} с '{$originalAlias}' на '{$currentAlias}' для избежания дублирования"
        );
    }
}

return;
?>
Артур Шевченко
22 октября 2025, 21:17
0
Ну сейчас, я бы рекомендовал смотреть в сторону SendIt. Там можно вызвать любой сниппет, не надо писать свой js, SendIt сам умеет вставлять ответ куда надо, есть пагинация.
Roman
22 октября 2025, 21:11
0
Попробовал альтернативный вариант, но мне не нужен msProducts, хочу просто поиск по ресурсам, используя SimpleSearch. Пните, куда копать.
Артур Шевченко
22 октября 2025, 15:28
0
Если бы я хотел предложить свои услуги — предложил бы. А вообще, я намекал на то, что тут безвозмездная помощь, и безвозмездное решение я уже изложил. Если же вы хотите, чтобы кто-то вместо вас его реализовал, пишите в раздел «Работа»
Серый
22 октября 2025, 11:56
-1
Любите когда вас упрашивают вместо того чтобы сразу озвучить прейскурант?
Артур Шевченко
22 октября 2025, 11:52
0
Т.е. ты хочешь чтобы кто-то вместо тебя написал плагин за спасибо? Хитро)))
Серый
22 октября 2025, 11:31
0
И на том спасибо)))
Вы серьёзно считаете, что чел, способный написать плагин, может не знать на какое событие его назначать?)))
Наумов Алексей
22 октября 2025, 10:41
+1
Да, конечно, само собой ничего магически не произойдет.