Durant la réalisation d'un projet client en Ibexa Commerce, j'ai eu besoin de rajouter un attribut discriminant sur un type de produit pour contenir un simple texte. Sauf que Ibexa Commerce n'inclut pas nativement ce type d'attribut.
Si l'attribut concernait le produit et non les variantes, j'aurais simplement pu remplacer l'attribut par un champ de contenu, vu que les produits sont des contenus. Pour les variantes en revanche, je suis limité par les types d'attributs existants.
J'ai donc entrepris d'examiner le code d'Ibexa Commerce pour comprendre comment sont gérés les types d'attributs, et pouvoir créer le mien. Au final, cela consiste en une table pour stocker les données, quelques classes PHP, et un peu de configuration de services.
Pour commencer, un peu de configuration pour déclarer le type d'attribut. Dans votre configuration de services :
services:
app.commerce.attribute_type.string:
class: Ibexa\ProductCatalog\Local\Repository\Attribute\AttributeType
arguments:
$identifier: string
tags:
- name: ibexa.product_catalog.attribute_type
alias: string
On déclare simplement un nouveau type d'attribut avec comme identifiant string. Cet identifiant sera réutilisé pour faire le lien avec les autres services utilisés par notre type d'attribut.
Ibexa Commerce enregistre les attributs des produits dans les tables ibexa_product_specification_attribute_*, une par type d'attribut. Je vais donc faire de même et créer une table ibexa_product_specification_attribute_string.
Voici la migration Doctrine associée :
<?php
declare(strict_types=1);
namespace DoctrineMigrations;
use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;
final class Version20240122141124 extends AbstractMigration
{
public function up(Schema $schema): void
{
$this->addSql("CREATE TABLE ibexa_product_specification_attribute_string (id INT NOT NULL PRIMARY KEY, value VARCHAR(255) NULL, CONSTRAINT ibexa_product_specification_attribute_string_fk FOREIGN KEY (id) REFERENCES ibexa_product_specification_attribute (id) ON UPDATE CASCADE ON DELETE CASCADE);");
$this->addSql("CREATE INDEX ibexa_product_specification_attribute_string_value_idx ON ibexa_product_specification_attribute_string (value);");
}
public function down(Schema $schema): void
{
$this->addSql("DROP TABLE ibexa_product_specification_attribute_string");
}
}
Maintenant que j'ai ma table, il faut indiquer à Ibexa comment stocker mes valeurs. Tout d'abord, je créé une classe pour représenter la structure de la table :
<?php
declare(strict_types=1);
namespace App\Commerce\Attribute\String;
use Doctrine\DBAL\Types\Types;
use Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageDefinitionInterface;
class StringStorageDefinition implements StorageDefinitionInterface
{
// Liste de colonnes (hors id) avec leur type
public function getColumns(): array
{
return [
'value' => Types::TEXT,
];
}
// Nom de la table
public function getTableName(): string
{
return 'ibexa_product_specification_attribute_string';
}
}
Puis une classe pour faire convertir les données de la table en données applicatives et inversement :
<?php
declare(strict_types=1);
namespace App\Commerce\Attribute\String;
use Ibexa\Contracts\ProductCatalog\Local\Attribute\StorageConverterInterface;
class StringStorageConverter implements StorageConverterInterface
{
// On lit simplement le contenu de la colonne value
public function fromPersistence(array $data)
{
return $data['value'];
}
// On met la valeur applicative dans la colonne value
public function toPersistence($value): array
{
return [
'value' => $value,
];
}
}
Et pour finir, un peu de configuration :
services:
App\Commerce\Attribute\String\StringStorageConverter:
tags:
- name: ibexa.product_catalog.attribute.storage_converter
type: string # L'identifiant de notre type
App\Commerce\Attribute\String\StringStorageDefinition:
tags:
- name: ibexa.product_catalog.attribute.storage_definition
type: string # L'identifiant de notre type
Je crée une classe pour construire le formulaire d'édition :
<?php
declare(strict_types=1);
namespace App\Commerce\Attribute\String;
use Ibexa\Bundle\ProductCatalog\Validator\Constraints\AttributeValue;
use Ibexa\Contracts\ProductCatalog\Local\Attribute\ValueFormMapperInterface;
use Ibexa\Contracts\ProductCatalog\Values\AttributeDefinitionAssignmentInterface;
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\Validator\Constraints as Assert;
class StringValueFormMapper implements ValueFormMapperInterface
{
public function createValueForm(
string $name,
FormBuilderInterface $builder,
AttributeDefinitionAssignmentInterface $assignment,
array $context = []
): void {
$definition = $assignment->getAttributeDefinition();
// Les options sont presque copiées/collées telles qu'elles de la classe
// native IntegerValueFormMapper
$options = [
'disabled' => $context['translation_mode'] ?? false,
'label' => $definition->getName(),
'block_prefix' => 'string_attribute_value',
'required' => $assignment->isRequired(),
'constraints' => [
new AttributeValue([
'definition' => $definition,
]),
],
];
if ($assignment->isRequired()) {
$options['constraints'][] = new Assert\NotBlank();
}
// J'ajoute un champ texte, comme ma valeur est une simple string
$builder->add($name, TextType::class, $options);
}
}
Et encore une fois, un peu de configuration :
services:
App\Commerce\Attribute\String\StringValueFormMapper:
tags:
- name: ibexa.product_catalog.attribute.form_mapper.value
type: string # L'identifiant de notre type
Et enfin la dernière étape, je crée une classe pour indiquer à Ibexa comment formater cette valeur pour affichage :
<?php
declare(strict_types=1);
namespace App\Commerce\Attribute\String;
use Ibexa\Contracts\ProductCatalog\Local\Attribute\ValueFormatterInterface;
use Ibexa\Contracts\ProductCatalog\Values\AttributeInterface;
class StringValueFormatter implements ValueFormatterInterface
{
public function formatValue(AttributeInterface $attribute, array $parameters = []): ?string
{
// Comme ma valeur est déjà une chaîne, je la retourne telle qu'elle
return $attribute->getValue();
}
}
Et le dernier morceau de configuration :
services:
App\Commerce\Attribute\String\StringValueFormatter:
tags:
- name: ibexa.product_catalog.attribute.formatter.value
type: string # L'identifiant de notre type
Malgré l'absence de documentation sur le sujet, le code associé est simple et clair. Dans mon cas, j'ai simplement copié/collé le code associé au type Integer et je l'ai adapté pour une chaîne. Pour un type plus complexe, j'aurais pu m'inspirer du type SingleMeasurement, qui stocke à la fois une valeur numérique, et une unité.
Comment créer un nouveau type d'attribut produit dans Ibexa Commerce ?
Comment calculer dynamiquement des frais de port ? La documentation officielle qui explique comment créer ...
A la recherche d'un poste de travail temporaire ou permanent ? Vous recherchez un environnement ...
Après une découverte de surface d'Ibexa Commerce, entrons plus dans le détail pour comprendre son ...
Ibexa DXP propose un module pour gérer des produits pour la réalisation d'un site e-commerce. ...
Voici une présentation d'IbexaMailing, un module qui ajoute la gestion des newsletters à Ibexa. IbexaMailing est ...
C'est la dernière occasion de vous souhaitez le meilleur pour cette année 2024 et surtout ...
En ce début d'année, en ce mois de janvier, mois des grandes résolutions, dépensons moins!Prenez ...
Nous sommes très heureux et fiers d'être nominés aux Ibexa Partner Excellence Awards 🏆 dans ...
Comment créer un nouveau type d'attribut produit dans Ibexa Commerce ?
Comment calculer dynamiquement des frais de port ? La documentation officielle qui explique comment créer ...
A la recherche d'un poste de travail temporaire ou permanent ? Vous recherchez un environnement ...
Après une découverte de surface d'Ibexa Commerce, entrons plus dans le détail pour comprendre son ...
Ibexa DXP propose un module pour gérer des produits pour la réalisation d'un site e-commerce. ...
Voici une présentation d'IbexaMailing, un module qui ajoute la gestion des newsletters à Ibexa. IbexaMailing est ...
C'est la dernière occasion de vous souhaitez le meilleur pour cette année 2024 et surtout ...
En ce début d'année, en ce mois de janvier, mois des grandes résolutions, dépensons moins!Prenez ...
Nous sommes très heureux et fiers d'être nominés aux Ibexa Partner Excellence Awards 🏆 dans ...