diff --git a/src/CoreShop/Bundle/IndexBundle/Controller/IndexController.php b/src/CoreShop/Bundle/IndexBundle/Controller/IndexController.php index 8921812336..8b1b80284b 100644 --- a/src/CoreShop/Bundle/IndexBundle/Controller/IndexController.php +++ b/src/CoreShop/Bundle/IndexBundle/Controller/IndexController.php @@ -22,7 +22,7 @@ use CoreShop\Bundle\StudioFormBundle\Form\Schema\RuleFormSchemaCollector; use CoreShop\Component\Index\Interpreter\LocalizedInterpreterInterface; use CoreShop\Component\Index\Interpreter\RelationInterpreterInterface; -use CoreShop\Component\Index\Model\IndexableInterface; +use CoreShop\Component\Index\Service\IndexableClassesProviderInterface; use CoreShop\Component\Registry\ServiceRegistryInterface; use Pimcore\Model\DataObject; use Symfony\Component\DependencyInjection\Attribute\Autowire; @@ -49,6 +49,7 @@ public function getTypesAction(): Response public function getConfigAction( RuleFormSchemaCollector $schemaCollector, + IndexableClassesProviderInterface $indexableClassesProvider, #[Autowire(service: 'coreshop.form_registry.index.getter')] FormTypeRegistryInterface $getterFormTypeRegistry, #[Autowire(service: 'coreshop.form_registry.index.interpreter')] @@ -118,22 +119,10 @@ public function getConfigAction( } } - $classes = new DataObject\ClassDefinition\Listing(); - $classes = $classes->load(); - $availableClasses = []; - - foreach ($classes as $class) { - if ($class instanceof DataObject\ClassDefinition) { - $pimcoreClass = 'Pimcore\Model\DataObject\\' . ucfirst($class->getName()); - $implements = class_implements($pimcoreClass) ?: []; - - if (in_array(IndexableInterface::class, $implements, true)) { - $availableClasses[] = [ - 'name' => $class->getName(), - ]; - } - } - } + $availableClasses = array_map( + static fn (string $name) => ['name' => $name], + $indexableClassesProvider->getIndexableClassNames(), + ); $workersResult = []; diff --git a/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexType.php b/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexType.php index 5240faaa64..fe3ce7822a 100644 --- a/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexType.php +++ b/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexType.php @@ -19,7 +19,6 @@ use CoreShop\Bundle\ResourceBundle\Form\Registry\FormTypeRegistryInterface; use CoreShop\Bundle\ResourceBundle\Form\Type\AbstractResourceType; -use CoreShop\Bundle\ResourceBundle\Form\Type\PimcoreClassChoiceType; use Symfony\Component\Form\Extension\Core\Type\CheckboxType; use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; @@ -42,7 +41,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void $builder ->add('name', TextType::class) ->add('worker', IndexWorkerChoiceType::class) - ->add('class', PimcoreClassChoiceType::class) + ->add('class', IndexablePimcoreClassChoiceType::class) ->add('columns', IndexColumnCollectionType::class) ->add('indexLastVersion', CheckboxType::class) ; diff --git a/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexablePimcoreClassChoiceType.php b/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexablePimcoreClassChoiceType.php new file mode 100644 index 0000000000..f0e9351506 --- /dev/null +++ b/src/CoreShop/Bundle/IndexBundle/Form/Type/IndexablePimcoreClassChoiceType.php @@ -0,0 +1,54 @@ +indexableClassesProvider->getIndexableClassNames() as $name) { + $choices[$name] = $name; + } + + $resolver->setDefaults([ + 'choices' => $choices, + ]); + } + + public function getParent(): string + { + return ChoiceType::class; + } + + public function getBlockPrefix(): string + { + return 'coreshop_index_indexable_pimcore_class_choice'; + } +} diff --git a/src/CoreShop/Bundle/IndexBundle/Form/Type/Worker/MysqlWorkerTableIndexType.php b/src/CoreShop/Bundle/IndexBundle/Form/Type/Worker/MysqlWorkerTableIndexType.php index 0fbcbdda82..f9f70021aa 100644 --- a/src/CoreShop/Bundle/IndexBundle/Form/Type/Worker/MysqlWorkerTableIndexType.php +++ b/src/CoreShop/Bundle/IndexBundle/Form/Type/Worker/MysqlWorkerTableIndexType.php @@ -21,8 +21,8 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; use Symfony\Component\Form\Extension\Core\Type\CollectionType; +use Symfony\Component\Form\Extension\Core\Type\TextType; use Symfony\Component\Form\FormBuilderInterface; -use Symfony\Component\OptionsResolver\OptionsResolver; final class MysqlWorkerTableIndexType extends AbstractType { @@ -37,6 +37,7 @@ public function buildForm(FormBuilderInterface $builder, array $options): void 'required' => false, ]) ->add('columns', CollectionType::class, [ + 'entry_type' => TextType::class, 'allow_delete' => true, 'allow_add' => true, 'required' => false, @@ -44,13 +45,6 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ; } - public function configureOptions(OptionsResolver $resolver): void - { - $resolver->setDefault('data_class', TableIndex::class); - - parent::configureOptions($resolver); - } - public function getBlockPrefix(): string { return 'coreshop_index_worker_mysql'; diff --git a/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/ColumnsPanel.tsx b/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/ColumnsPanel.tsx index 37400de2eb..2373526123 100644 --- a/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/ColumnsPanel.tsx +++ b/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/ColumnsPanel.tsx @@ -69,13 +69,16 @@ export const ColumnsPanel: React.FC = ({ return } - // Create new column with defaults - const newColumn: IndexColumn = { + // `field.dataType` is the Pimcore ClassDefinition fieldtype (e.g. 'input', 'numeric'), + // not a valid index column type. Open the edit modal with the draft column and let the + // user pick a valid index column type (STRING, INTEGER, …). The column is only appended + // once the user clicks Apply (see handleSaveField with editingIndex === null). + const draftColumn: IndexColumn = { name: field.name || field.objectKey || '', objectKey: field.objectKey || '', objectType: field.objectType, dataType: field.dataType, - columnType: field.dataType || 'TEXT', + columnType: undefined, getter: field.getter, getterConfig: field.configuration, interpreter: field.interpreter, @@ -83,10 +86,9 @@ export const ColumnsPanel: React.FC = ({ configuration: field.configuration } - onChange({ - ...index, - columns: [...columns, newColumn] - }) + setEditingColumn(draftColumn) + setEditingIndex(null) + setDialogVisible(true) } const handleDrop = (info: DragAndDropInfo) => { @@ -102,14 +104,21 @@ export const ColumnsPanel: React.FC = ({ } const handleSaveField = (updatedColumn: IndexColumn) => { - if (editingIndex !== null) { - const newColumns = [...columns] - newColumns[editingIndex] = updatedColumn + if (editingIndex === null) { + // New field — append onChange({ ...index, - columns: newColumns + columns: [...columns, updatedColumn] }) + return } + + const newColumns = [...columns] + newColumns[editingIndex] = updatedColumn + onChange({ + ...index, + columns: newColumns + }) } const handleCloseModal = () => { diff --git a/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/FieldEditModal.tsx b/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/FieldEditModal.tsx index 4502a84ae7..09bcb3c923 100644 --- a/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/FieldEditModal.tsx +++ b/src/CoreShop/Bundle/IndexBundle/Resources/assets/pimcore-studio/src/modules/indexes/components/FieldEditModal.tsx @@ -56,7 +56,7 @@ export const FieldEditModal: React.FC = ({ name: field.name || '', getter: field.getter || undefined, interpreter: field.interpreter || undefined, - columnType: field.columnType || 'INTEGER' + columnType: field.columnType || undefined }) setSelectedGetter(field.getter) setSelectedInterpreter(field.interpreter) diff --git a/src/CoreShop/Bundle/IndexBundle/Resources/config/services.yml b/src/CoreShop/Bundle/IndexBundle/Resources/config/services.yml index 705ab93654..dd9c6a794b 100755 --- a/src/CoreShop/Bundle/IndexBundle/Resources/config/services.yml +++ b/src/CoreShop/Bundle/IndexBundle/Resources/config/services.yml @@ -339,6 +339,10 @@ services: - '@coreshop.repository.index' - '@coreshop.registry.index.worker' + # Indexable Classes Provider + CoreShop\Component\Index\Service\IndexableClassesProviderInterface: '@CoreShop\Bundle\IndexBundle\Service\IndexableClassesProvider' + CoreShop\Bundle\IndexBundle\Service\IndexableClassesProvider: ~ + CoreShop\Component\Index\Extension\DecimalIndexColumnTypeConfigExtension: tags: - { name: coreshop.index.extension } diff --git a/src/CoreShop/Bundle/IndexBundle/Resources/config/services/form.yml b/src/CoreShop/Bundle/IndexBundle/Resources/config/services/form.yml index a0ad5a672c..786fe22647 100644 --- a/src/CoreShop/Bundle/IndexBundle/Resources/config/services/form.yml +++ b/src/CoreShop/Bundle/IndexBundle/Resources/config/services/form.yml @@ -38,6 +38,12 @@ services: - { name: form.type } - { name: coreshop.studio_form } + CoreShop\Bundle\IndexBundle\Form\Type\IndexablePimcoreClassChoiceType: + arguments: + - '@CoreShop\Component\Index\Service\IndexableClassesProviderInterface' + tags: + - { name: form.type } + CoreShop\Bundle\IndexBundle\Form\Schema\IndexSchemaEnricher: tags: - { name: coreshop_studio_form.enricher } diff --git a/src/CoreShop/Bundle/IndexBundle/Service/IndexableClassesProvider.php b/src/CoreShop/Bundle/IndexBundle/Service/IndexableClassesProvider.php new file mode 100644 index 0000000000..8f47bf9aad --- /dev/null +++ b/src/CoreShop/Bundle/IndexBundle/Service/IndexableClassesProvider.php @@ -0,0 +1,55 @@ +load() as $class) { + if (!$class instanceof ClassDefinition) { + continue; + } + + $name = $class->getName(); + + if (null === $name || '' === $name) { + continue; + } + + $pimcoreClass = 'Pimcore\\Model\\DataObject\\' . ucfirst($name); + + if (!class_exists($pimcoreClass)) { + continue; + } + + if (in_array(IndexableInterface::class, class_implements($pimcoreClass) ?: [], true)) { + $result[] = $name; + } + } + + return $result; + } +} diff --git a/src/CoreShop/Bundle/IndexBundle/Worker/MysqlWorker.php b/src/CoreShop/Bundle/IndexBundle/Worker/MysqlWorker.php index b884d5e4ec..d849b9016f 100644 --- a/src/CoreShop/Bundle/IndexBundle/Worker/MysqlWorker.php +++ b/src/CoreShop/Bundle/IndexBundle/Worker/MysqlWorker.php @@ -207,14 +207,17 @@ protected function createTableSchema(IndexInterface $index, Schema $tableSchema) } if (array_key_exists('indexes', $index->getConfiguration())) { - /** - * @var TableIndex $tableIndex - */ foreach ($index->getConfiguration()['indexes'] as $tableIndex) { - if ($tableIndex->getType() === TableIndex::TABLE_INDEX_TYPE_UNIQUE) { - $table->addUniqueIndex($tableIndex->getColumns()); + $normalized = $this->normalizeTableIndex($tableIndex); + + if (null === $normalized) { + continue; + } + + if ($normalized['type'] === TableIndex::TABLE_INDEX_TYPE_UNIQUE) { + $table->addUniqueIndex($normalized['columns']); } else { - $table->addIndex($tableIndex->getColumns()); + $table->addIndex($normalized['columns']); } } } @@ -269,14 +272,17 @@ protected function createLocalizedTableSchema(IndexInterface $index, Schema $tab } if (array_key_exists('localizedIndexes', $index->getConfiguration())) { - /** - * @var TableIndex $tableIndex - */ foreach ($index->getConfiguration()['localizedIndexes'] as $tableIndex) { - if ($tableIndex->getType() === TableIndex::TABLE_INDEX_TYPE_UNIQUE) { - $table->addUniqueIndex($tableIndex->getColumns()); + $normalized = $this->normalizeTableIndex($tableIndex); + + if (null === $normalized) { + continue; + } + + if ($normalized['type'] === TableIndex::TABLE_INDEX_TYPE_UNIQUE) { + $table->addUniqueIndex($normalized['columns']); } else { - $table->addIndex($tableIndex->getColumns()); + $table->addIndex($normalized['columns']); } } } @@ -284,6 +290,37 @@ protected function createLocalizedTableSchema(IndexInterface $index, Schema $tab return $tableSchema; } + /** + * Accept both {@see TableIndex} objects (legacy programmatic setup) and plain arrays + * (the form + JSON-stored configuration format). Returns null for empty/invalid entries. + * + * @return array{type: string|null, columns: array}|null + */ + private function normalizeTableIndex(mixed $tableIndex): ?array + { + if ($tableIndex instanceof TableIndex) { + $columns = $tableIndex->getColumns(); + + if (empty($columns)) { + return null; + } + + return [ + 'type' => $tableIndex->getType(), + 'columns' => array_values($columns), + ]; + } + + if (is_array($tableIndex) && !empty($tableIndex['columns']) && is_array($tableIndex['columns'])) { + return [ + 'type' => $tableIndex['type'] ?? null, + 'columns' => array_values($tableIndex['columns']), + ]; + } + + return null; + } + protected function createRelationalTableSchema(IndexInterface $index, Schema $tableSchema) { $table = $tableSchema->createTable($this->getRelationTablename($index->getName())); diff --git a/src/CoreShop/Component/Index/Service/IndexableClassesProviderInterface.php b/src/CoreShop/Component/Index/Service/IndexableClassesProviderInterface.php new file mode 100644 index 0000000000..8578377539 --- /dev/null +++ b/src/CoreShop/Component/Index/Service/IndexableClassesProviderInterface.php @@ -0,0 +1,26 @@ + Pimcore DataObject class names whose generated model implements IndexableInterface + */ + public function getIndexableClassNames(): array; +}