• v5.0
    • Versions
    • master

 

  • Install Akeneo PIM
    • Install Akeneo PIM for development with Docker
    • Install Akeneo PIM manually
      • System Requirements
      • System installation on Debian 10 (Buster)
      • System installation on Ubuntu 18.04 (Bionic Beaver)
      • System installation on Ubuntu 20.04 (Focal Fossa)
      • Installing Akeneo PIM Community Edition (CE)
      • Installing Akeneo PIM Enterprise Edition (EE) with the Archive
      • Setting up the job queue daemon
      • Setting up the Events API
    • How to customize the Dataset
    • How to Add Translation Packs
  • Upgrade Akeneo PIM projects
    • How to apply a patch?
      • How to apply a patch - Community Edition
      • How to apply a patch - Enterprise Edition - Flexibility Cloud offer
      • How to apply a patch - Enterprise Edition - On Premise offer
    • Where is the Changelog?
    • How to upgrade to a minor version?
    • How to upgrade to a major version?
      • Upgrade from 3.2 to 4.0
      • Upgrade from 4.0 to 5.0
  • Import and Export data
    • How import works
    • Understanding the Product Import
    • Understanding the Product Export
    • Formats
      • Localized labels
      • Scopable labels
      • Association types data structure
      • Attribute data structure
      • Category data structure
      • Family data structure
      • Family variant data structure
      • Group data structure
      • Options data structure
      • Product data structure
      • Product model data structure
    • Akeneo Connectors
    • How to Customize Import / Export
      • How to create a new Connector
      • How to import Products from a XML file
      • How to clean a CSV file during a Product import
      • How to automate imports/exports
  • Manipulate the Akeneo PIM data
    • How to Customize Mass Edit Operations
      • How to register a new bulk action
      • How to Register a New Mass Edit Action on Products
    • How to Manipulate Products
      • How to Query Products
      • How to Create Products
      • How to Update Products
      • How to Validate Products
      • How to Save Products
      • How to Remove Products
    • How to Manipulate Non-Product Objects
      • How to Query Non-Product Objects
      • How to Create Non-Product Objects
      • How to Update Non-Product Objects
      • How to Validate Non-Product Objects
      • How to Save Non-Product Objects
      • How to Remove Non-Product Objects
    • How to add a custom action rule
      • General information about rule format
      • How to add a custom action in the rule engine
    • How to Define Access Control List
    • How to Customize the Catalog Structure
      • How to Create a Reference Data
    • How To Customize Teamwork Assistant (Enterprise Edition)
      • Customize notifications
      • Add a calculation step
      • How to log calculation step
      • Remove projects impacted by a custom catalog update
    • How to store assets externally
    • How to Configure Measurement Limits
  • Maintain Akeneo PIM projects
    • First aid kit
    • Bug qualification
    • Common issues
    • Scalability Guide
      • Audit with 3 Representative Catalogs
      • More than 10k attributes?
      • More than 10k families?
      • More than 10k categories?
      • More than 500 attributes usable in the product grids?
      • More than 100k products to export?
      • More than 1GB of product media to export?
    • How to purge history
      • How to Purge jobs executions
      • How to adapt the version purger to your needs
  • Contribute to Akeneo PIM
    • How to report an issue?
    • How to translate the user interface?
    • How to enhance the documentation?
    • How to contribute to a Connector?
    • How to submit a patch to the PIM?
    • How to contribute to the frontend part of the application
    • How behavior tests are architectured in the PIM?
      • Establishing Decorator Pattern
      • Using Spin In Behat
  • Use SSO authentication locally
  • Reference Entities
    • Configure Entity Limits
    • Create a new Reference Entity Attribute type
    • Enrich Records with a new Reference Entity Attribute type
    • Add a Custom Property to Your Custom Attribute Type
    • Refresh records completeness
  • Troubleshooting guide
  • Technical overview
    • Product Information
    • Teamwork Assistant (Enterprise Edition)
      • Project creation
      • Project completeness
      • Project Completeness widget
      • Catalog update impact
      • Scalability guide
      • Users permission summary for Behat tests
    • Collaborative workflow
      • Simple workflow
      • Partial workflow
  • Technical architecture
    • Best Practices
      • Create a project
      • Create a reusable bundle
      • Code Conventions
      • Coding Standards
    • How to implement your business logic using the event system
    • Events
      • Storage events
      • Workflow events (Enterprise Edition only)
    • How to Localize your data
      • How to change the PIM locale
      • How to Use Localizers
      • How to use Presenters
    • How to Add a Notification
    • Performances Guide
      • Memory usage of rules execution (Enterprise Edition)
      • Memory leak fix in Rules Engine (ORM)
      • More than 100 WYSIWYG editors in a page
      • PHP7 and HHVM Compatibility?
      • Job product batch size
    • How to Use the Web REST API
    • Standard format
      • Products
      • Other entities
      • Usage
    • Application Technical Information
      • Application Technical Dependencies
      • Server side set up for hosting
      • System Requirements
      • Recommended configuration
      • Client side configuration and compatibilities
      • Operation processes
      • Flow Matrix
  • Akeneo Cloud Edition
    • Flexibility
      • Environment accesses
      • System Administration & Services Management
      • Periodic tasks & Crontab configuration
      • Composer settings
      • Queue Management & Workers
      • Disk Usage Management
    • Serenity
  • Akeneo Onboarder
    • Prerequisites
    • How to install the Onboarder bundle
    • Synchronization
    • How to update a minor version or to apply a patch
      • How to update the Onboarder bundle - Enterprise Edition - Flexibility Cloud offer
      • How to update the Onboarder bundle - Enterprise Edition - On Premise offer
    • How to upgrade to a major version
    • Troubleshooting
    • How to uninstall the Onboarder bundle
    • Environment variables
      • Using the DotEnv file
      • Using environment variables

Create a new Reference Entity Attribute type¶

Note

Reference Entities feature is only available for the Enterprise Edition.

This cookbook will present how to create your own Attribute Type for Reference Entities. Currently, there are 6 types of Attribute for Reference Entities:

  • Image

  • Text

  • Reference entity single link

  • Reference entity multiple links

  • Single Option

  • Multiple Options

Requirements¶

During this chapter, we assume that you already created and registered a new bundle to add your custom Reference Entity Attribute. Let’s assume its namespace is Acme\\CustomBundle.

Create the Attribute¶

For this tutorial, we will create a custom metric attribute type for reference entity. So we’ll be able to create this kind of attribute on any Reference Entity:

Simple Metric as new attribute type

And to edit its common properties:

Edit Simple Metric attribute

Note

A small word on our architecture:

For the reference entities, we followed the Hexagonal architecture. So we split our classes in 3 different layers: Domain, Application & Infrastructure.

  • First we’ll create Domain classes (the new custom SimpleMetricAttribute itself and its Value Object)

  • Then Application classes (only Commands from CQRS principle (https://martinfowler.com/bliki/CQRS.html) and regular updaters)

  • And to finish Infrastructure classes (an Hydrator to hydrate our SimpleMetricAttribute coming from SQL)

It’s not mandatory to respect this architecture in your custom project.

1) Your new Custom Attribute Entity¶

Let’s start with our new custom Attribute. It must extend the \Akeneo\ReferenceEntity\Domain\Model\Attribute\AbstractAttribute.

<?php

namespace Acme\CustomBundle\Attribute;

use Akeneo\ReferenceEntity\Domain\Model\Attribute\AbstractAttribute;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeCode;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIdentifier;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIsRequired;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeOrder;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerChannel;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerLocale;
use Akeneo\ReferenceEntity\Domain\Model\LabelCollection;
use Akeneo\ReferenceEntity\Domain\Model\ReferenceEntity\ReferenceEntityIdentifier;

class SimpleMetricAttribute extends AbstractAttribute
{
    public static function createSimpleMetric(
        AttributeIdentifier $identifier,
        ReferenceEntityIdentifier $referenceEntityIdentifier,
        AttributeCode $code,
        LabelCollection $labelCollection,
        AttributeOrder $order,
        AttributeIsRequired $isRequired,
        AttributeValuePerChannel $valuePerChannel,
        AttributeValuePerLocale $valuePerLocale
    ) {
        return new self(
            $identifier,
            $referenceEntityIdentifier,
            $code,
            $labelCollection,
            $order,
            $isRequired,
            $valuePerChannel,
            $valuePerLocale
        );
    }

    /**
     * {@inheritdoc}
     */
    protected function getType(): string
    {
        return 'simple_metric';
    }
}

2) Create the attribute¶

Now that we have our Attribute model class, we need to create classes to handle its creation.

We’ll need first to add the “Creation command”, it needs to extend \Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\AbstractCreateAttributeCommand.

<?php

namespace Acme\CustomBundle\Attribute;

use Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\AbstractCreateAttributeCommand;

class CreateSimpleMetricAttributeCommand extends AbstractCreateAttributeCommand
{
}

To build this creation command, we need a factory:

<?php

namespace Acme\CustomBundle\Attribute;

use Acme\CustomBundle\Attribute\CreateSimpleMetricAttributeCommand;
use Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\AbstractCreateAttributeCommand;
use Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\CommandFactory\AbstractCreateAttributeCommandFactory;

class CreateSimpleMetricAttributeCommandFactory extends AbstractCreateAttributeCommandFactory
{
    public function supports(array $normalizedCommand): bool
    {
        return isset($normalizedCommand['type']) && 'simple_metric' === $normalizedCommand['type'];
    }

    public function create(array $normalizedCommand): AbstractCreateAttributeCommand
    {
        $this->checkCommonProperties($normalizedCommand);

        $command = new CreateSimpleMetricAttributeCommand(
            $normalizedCommand['reference_entity_identifier'],
            $normalizedCommand['code'],
            $normalizedCommand['labels'] ?? [],
            $normalizedCommand['is_required'] ?? false,
            $normalizedCommand['value_per_channel'],
            $normalizedCommand['value_per_locale']
        );

        return $command;
    }
}

And we also need to register it with a specific tag:

acme.application.factory.create_simple_metric_attribute_command_factory:
    class: Acme\CustomBundle\Attribute\CreateSimpleMetricAttributeCommandFactory
    tags:
        - { name: akeneo_referenceentity.create_attribute_command_factory }

Now that we have our command created, we need a factory to create our brand new SimpleMetricAttribute:

<?php

namespace Acme\CustomBundle\Attribute;

use Acme\CustomBundle\Attribute\CreateSimpleMetricAttributeCommand;
use Acme\CustomBundle\Attribute\SimpleMetricAttribute;
use Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\AbstractCreateAttributeCommand;
use Akeneo\ReferenceEntity\Application\Attribute\CreateAttribute\AttributeFactory\AttributeFactoryInterface;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AbstractAttribute;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeCode;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIdentifier;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIsRequired;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeOrder;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerChannel;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerLocale;
use Akeneo\ReferenceEntity\Domain\Model\LabelCollection;
use Akeneo\ReferenceEntity\Domain\Model\ReferenceEntity\ReferenceEntityIdentifier;

class SimpleMetricAttributeFactory implements AttributeFactoryInterface
{
    public function supports(AbstractCreateAttributeCommand $command): bool
    {
        return $command instanceof CreateSimpleMetricAttributeCommand;
    }

    public function create(
        AbstractCreateAttributeCommand $command,
        AttributeIdentifier $identifier,
        AttributeOrder $order
    ): AbstractAttribute {
        if (!$this->supports($command)) {
            throw new \RuntimeException(
                sprintf(
                    'Expected command of type "%s", "%s" given',
                    CreateSimpleMetricAttributeCommand::class,
                    get_class($command)
                )
            );
        }

        return SimpleMetricAttribute::createSimpleMetric(
            $identifier,
            ReferenceEntityIdentifier::fromString($command->referenceEntityIdentifier),
            AttributeCode::fromString($command->code),
            LabelCollection::fromArray($command->labels),
            $order,
            AttributeIsRequired::fromBoolean($command->isRequired),
            AttributeValuePerChannel::fromBoolean($command->valuePerChannel),
            AttributeValuePerLocale::fromBoolean($command->valuePerLocale)
        );
    }
}

Don’t forget to register it:

acme.application.factory.simple_metric_attribute_factory:
    class: Acme\CustomBundle\Attribute\SimpleMetricAttributeFactory
    tags:
        - { name: akeneo_referenceentity.attribute_factory }

Note

For your attribute type to appear translated in the UI, you can add the key pim_reference_entity.attribute.type.simple_metric in the JS translation file (located in src/Acme/CustomBundle/Resources/translations/jsmessages.en.yml)

3) Edit the attribute¶

For the edition of this attribute, as for now it has no custom property, we don’t have to create anything, it will be handled natively.

4) Retrieve the attribute¶

Now that we have our custom Attribute and commands to create/edit it, we’ll need to have a way to Hydrate it from the DB for example:

<?php

namespace Acme\CustomBundle\Attribute;

use Acme\CustomBundle\Attribute\SimpleMetricAttribute;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AbstractAttribute;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeCode;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIdentifier;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeIsRequired;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeOrder;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerChannel;
use Akeneo\ReferenceEntity\Domain\Model\Attribute\AttributeValuePerLocale;
use Akeneo\ReferenceEntity\Domain\Model\LabelCollection;
use Akeneo\ReferenceEntity\Domain\Model\ReferenceEntity\ReferenceEntityIdentifier;
use Akeneo\ReferenceEntity\Infrastructure\Persistence\Sql\Attribute\Hydrator\AbstractAttributeHydrator;
use Doctrine\DBAL\Platforms\AbstractPlatform;

class SimpleMetricAttributeHydrator extends AbstractAttributeHydrator
{
    protected function getExpectedProperties(): array
    {
        // We at least expect common properties
        return [
            'identifier',
            'reference_entity_identifier',
            'code',
            'labels',
            'attribute_order',
            'is_required',
            'value_per_locale',
            'value_per_channel',
            'attribute_type'
        ];
    }

    protected function convertAdditionalProperties(AbstractPlatform $platform, array $row): array
    {
        return $row;
    }

    protected function hydrateAttribute(array $row): AbstractAttribute
    {
        return SimpleMetricAttribute::createSimpleMetric(
            AttributeIdentifier::fromString($row['identifier']),
            ReferenceEntityIdentifier::fromString($row['reference_entity_identifier']),
            AttributeCode::fromString($row['code']),
            LabelCollection::fromArray($row['labels']),
            AttributeOrder::fromInteger($row['attribute_order']),
            AttributeIsRequired::fromBoolean($row['is_required']),
            AttributeValuePerChannel::fromBoolean($row['value_per_channel']),
            AttributeValuePerLocale::fromBoolean($row['value_per_locale'])
        );
    }

    public function supports(array $result): bool
    {
        return isset($result['attribute_type']) && 'simple_metric' === $result['attribute_type'];
    }
}

And to register it:

# src/Acme/CustomBundle/Resources/config/services.yml

services:
    acme.infrastructure.persistence.hydrator.attribute.simple_metric_attribute_hydrator:
        class: Acme\CustomBundle\Attribute\SimpleMetricAttributeHydrator
        arguments:
            - '@database_connection'
        tags:
            - { name: akeneo_referenceentity.attribute_hydrator }

Frontend Part of The New Attribute Type¶

To be able to create your brand new Simple Metric attribute on a Reference Entity, we need to add some code in the frontend part.

To do so, you can put all needed code in one single file but you can (and are encouraged) to split it into multiple files if needed.

To keep this example simple, we will create everything in this file :

src/Acme/CustomBundle/Resources/public/reference-entity/attribute/simple_metric.tsx

Note

If you create a new attribute type, Akeneo will need three things to manage it in the frontend:

  • A model: a representation of your attribute, its properties and overall behaviour

  • A reducer: to be able to know how to modify its custom properties and react to the user intentions (see https://redux.js.org/)

  • A view: as a React component to be able to render a user interface and dispatch events to the application

1) Model¶

The model of your custom attribute will contain the common properties of an attribute (code, labels, channel, etc) but also its custom properties and behaviours (even if for now, our Simple Metric attribute doesn’t have any). To integrate it with the rest of the PIM, your attribute needs to implement the Attribute interface and provide a denormalizer.

This is the purpose of this section: provide a denormalizer capable of creating your custom attribute implementing Attribute interface.

/**
 * ## Import section
 *
 * This is where your dependencies to external modules are, using the standard import method (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import)
 * The paths are relative to the public/bundles folder (at the root of your PIM project)
 */
import Identifier, {createIdentifier} from 'akeneoreferenceentity/domain/model/attribute/identifier';
import ReferenceEntityIdentifier, {
  createIdentifier as createReferenceEntityIdentifier,
} from 'akeneoreferenceentity/domain/model/reference-entity/identifier';
import LabelCollection, {createLabelCollection} from 'akeneoreferenceentity/domain/model/label-collection';
import AttributeCode, {createCode} from 'akeneoreferenceentity/domain/model/attribute/code';
import {
  NormalizedAttribute,
  Attribute,
  ConcreteAttribute,
} from 'akeneoreferenceentity/domain/model/attribute/attribute';

/**
 * This interface will represent your normalized attribute (usually coming from the backend but also used in the reducer)
 */
export interface NormalizedSimpleMetricAttribute extends NormalizedAttribute {
  type: 'simple_metric';
}

/**
 * Here we define the interface for our concrete class (our model) extending the base attribute interface
 */
export interface SimpleMetricAttribute extends Attribute {
  normalize(): NormalizedSimpleMetricAttribute;
}

/**
 * Here we are starting to implement our custom attribute class.
 * Note that most of the code is due to the custom property (defaultValue). If you don't need to add a
 * custom property to your attribute, the code can be stripped to it's minimal
 */
export class ConcreteSimpleMetricAttribute extends ConcreteAttribute implements SimpleMetricAttribute {
  /**
   * Here, our constructor is private to be sure that our model will be created through a named constructor
   */
  private constructor(
    identifier: Identifier,
    referenceEntityIdentifier: ReferenceEntityIdentifier,
    code: AttributeCode,
    labelCollection: LabelCollection,
    valuePerLocale: boolean,
    valuePerChannel: boolean,
    order: number,
    is_required: boolean
  ) {
    super(
      identifier,
      referenceEntityIdentifier,
      code,
      labelCollection,
      'simple_metric',
      valuePerLocale,
      valuePerChannel,
      order,
      is_required
    );

    /**
     * This will ensure that your model is not modified after it's creation (see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze)
     */
    Object.freeze(this);
  }

  /**
   * Here, we denormalize our attribute
   */
  public static createFromNormalized(normalizedSimpleMetricAttribute: NormalizedSimpleMetricAttribute) {
    return new ConcreteSimpleMetricAttribute(
      createIdentifier(normalizedSimpleMetricAttribute.identifier),
      createReferenceEntityIdentifier(normalizedSimpleMetricAttribute.reference_entity_identifier),
      createCode(normalizedSimpleMetricAttribute.code),
      createLabelCollection(normalizedSimpleMetricAttribute.labels),
      normalizedSimpleMetricAttribute.value_per_locale,
      normalizedSimpleMetricAttribute.value_per_channel,
      normalizedSimpleMetricAttribute.order,
      normalizedSimpleMetricAttribute.is_required
    );
  }

  /**
   * The only method to implement here: the normalize method. Here you need to provide a serializable object (see https://developer.mozilla.org/en-US/docs/Glossary/Serialization)
   */
  public normalize(): NormalizedSimpleMetricAttribute {
    return {
      ...super.normalize(),
      type: 'simple_metric'
    };
  }
}

/**
 * The only required part of the file: exporting a denormalize method returning a custom attribute implementing Attribute interface
 */
export const denormalize = ConcreteSimpleMetricAttribute.createFromNormalized;

2) Reducer¶

Now that we have our attribute model in the frontend, we need to define our Reducer to know how to modify custom properties and react to the user intentions. We’ll see later how to handle those custom properties, for now, it’s going to be a really simple reducer.

/**
 * Our custom attribute reducer needs to receive the normalized custom attribute as input, the code of the additional property and the value of the additional property.
 * It returns the normalized custom attribute.
 */
const simpleMetricAttributeReducer = (
  normalizedAttribute: NormalizedSimpleMetricAttribute,
  propertyCode: string
): NormalizedSimpleMetricAttribute => {
  switch (propertyCode) {
    // Our future custom behaviour will go there
    default:
      break;
  }

  return normalizedAttribute;
};

/**
 * The only required part of the file: exporting the custom attribute reducer.
 * Be aware that the export has to be named ``reducer``
 */
export const reducer = simpleMetricAttributeReducer;

3) View¶

The last part we need to do, it’s to create the React component to be able to render a user interface and dispatch events to the application (https://reactjs.org/docs/react-component.html).

const SimpleMetricAttributeView = () => {
  return '';
};

/**
 * The only required part of the file: exporting the custom attribute view. Note that the export name has to be ``view``
 */
export const view = SimpleMetricAttributeView;

4) Register our custom attribute¶

To be able to have everything working, we need to register our custom attribute in the src/Acme/CustomBundle/Resources/config/requirejs.yml :

config:
    config:
        akeneoreferenceentity/application/configuration/attribute:
            simple_metric:
                icon: bundles/pimui/images/attribute/icon-metric.svg
                denormalize: '@custom/reference-entity/attribute/simple_metric.tsx'
                reducer: '@custom/reference-entity/attribute/simple_metric.tsx'
                view: '@custom/reference-entity/attribute/simple_metric.tsx'

Note

Note that in this tutorial, we don’t have any custom property for this attribute, we’ll cover this point in another tutorial.

API Part of The New Attribute Type¶

1) Json schema validator when creating an attribute¶

To be able to validate an attribute when creating it through the API, you have to create a Json Schema validator.

<?php

declare(strict_types=1);

namespace Acme\CustomBundle\Attribute\JsonSchema;

use JsonSchema\Validator;

class SimpleMetricAttributeCreationValidator implements AttributeValidatorInterface
{
    private const API_SIMPLE_METRIC_ATTRIBUTE_TYPE = 'simple_metric';

    public function validate(array $normalizedAttribute): array
    {
        $record = Validator::arrayToObjectRecursive($normalizedAttribute);
        $validator = new Validator();
        $validator->validate($record, $this->getJsonSchema());

        return $validator->getErrors();
    }

    public function forAttributeTypes(): array
    {
        return [self::API_SIMPLE_METRIC_ATTRIBUTE_TYPE];
    }

    private function getJsonSchema(): array
    {
        return [
            'type' => 'object',
            'required' => ['code', 'type', 'value_per_locale', 'value_per_channel'],
            'properties' => [
                'code' => [
                    'type' => ['string'],
                ],
                'type' => [
                    'type' => ['string'],
                ],
                'labels' => [
                    'type' => 'object',
                    'patternProperties' => [
                        '.+' => ['type' => 'string'],
                    ],
                ],
                'value_per_locale' => [
                    'type' => [ 'boolean'],
                ],
                'value_per_channel' => [
                    'type' => [ 'boolean'],
                ],
                'is_required_for_completeness' => [
                    'type' => [ 'boolean'],
                ]
            ],
            'additionalProperties' => false,
        ];
    }
}

And to register it:

# src/Acme/CustomBundle/Resources/config/services.yml

services:
    acme.infrastructure.connector.api.create.simple_metric_attribute_validator:
        class: Acme\CustomBundle\Attribute\JsonSchema\SimpleMetricAttributeCreationValidator
        tags:
            - { name: akeneo_referenceentity.connector.api.create.attribute_validator }

2) Json schema validator when editing an attribute¶

The validation of the schema is not the same when editing an attribute through the API. Indeed, some properties are not mandatory. So, you will have to create a new Json schema validator.

<?php

declare(strict_types=1);

namespace Acme\CustomBundle\Attribute\JsonSchema;

use Akeneo\ReferenceEntity\Domain\Model\Attribute\AbstractAttribute;
use Acme\CustomBundle\Attribute\SimpleMetricAttribute;
use JsonSchema\Validator;

class SimpleMetricAttributeEditValidator implements AttributeValidatorInterface
{
    public function validate(array $normalizedAttribute): array
    {
        $record = Validator::arrayToObjectRecursive($normalizedAttribute);
        $validator = new Validator();
        $validator->validate($record, $this->getJsonSchema());

        return $validator->getErrors();
    }

    public function support(AbstractAttribute $attribute): bool
    {
        return $attribute instanceof SimpleMetricAttribute;
    }

    private function getJsonSchema(): array
    {
        return [
            'type' => 'object',
            'required' => ['code'],
            'properties' => [
                'code' => [
                    'type' => ['string'],
                ],
                'type' => [
                    'type' => ['string'],
                ],
                'labels' => [
                    'type' => 'object',
                    'patternProperties' => [
                        '.+' => ['type' => 'string'],
                    ],
                ],
                'value_per_locale' => [
                    'type' => [ 'boolean'],
                ],
                'value_per_channel' => [
                    'type' => [ 'boolean'],
                ],
                'is_required_for_completeness' => [
                    'type' => [ 'boolean'],
                ],
                '_links' => [
                    'type' => 'object'
                ],
            ],
            'additionalProperties' => false,
        ];
    }
}

And to register it:

# src/Acme/CustomBundle/Resources/config/services.yml

services:
    acme.infrastructure.connector.api.edit.simple_metric_attribute_validator:
        class: Acme\CustomBundle\Attribute\JsonSchema\SimpleMetricAttributeEditValidator
        tags:
            - { name: akeneo_referenceentity.connector.api.edit.attribute_validator }

Found a typo or a hole in the documentation and feel like contributing?
Join us on Github!