Skip to main content

Adding a Custom DataField Type

A custom DataField type is needed when creating DataFields for a new line item type (e.g. a third-party plugin with custom cart positions), or when introducing a semantically new category of fields.


Overview of steps

  1. Create the type enum
  2. Create the abstract base class for the new field
  3. Implement the concrete DataField
  4. Create the LineItemContextResolver
  5. Register the services

1. Create the type enum

The enum implements DataFieldTypeInterface and defines the type name.

<?php

declare(strict_types=1);

namespace MyPlugin\Connector\DataField;

use AgiqonConnector\Connector\DataField\DataFieldTypeInterface;

enum MyCustomDataFieldType: string implements DataFieldTypeInterface
{
case MyCustomType = 'my_custom_type';

public function getName(): string
{
return $this->value;
}
}

2. Create the abstract base class

The abstract class carries the #[DataFieldTypeAttribute(...)] for the new type and encapsulates the null check on the line item.

<?php

declare(strict_types=1);

namespace MyPlugin\Connector\DataField\Field;

use AgiqonConnector\Connector\DataField\Attribute\DataFieldTypeAttribute;
use AgiqonConnector\Connector\DataField\DataFieldResolutionContext;
use AgiqonConnector\Connector\DataField\Field\AbstractDataField;
use MyPlugin\Connector\DataField\MyCustomDataFieldType;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;

#[DataFieldTypeAttribute(MyCustomDataFieldType::MyCustomType)]
abstract readonly class AbstractMyCustomField extends AbstractDataField
{
public function resolve(DataFieldResolutionContext $context): ?string
{
$lineItem = $context->getLineItem();

// Check the line item and optionally filter by type
if ($lineItem === null || $lineItem->getType() !== 'my-custom-line-item-type') {
return null;
}

return $this->getValue($lineItem);
}

abstract public function getValue(LineItem $lineItem): ?string;
}

3. Implement the concrete DataField

<?php

declare(strict_types=1);

namespace MyPlugin\Connector\DataField\Field;

use AgiqonConnector\Connector\System\ConnectorSystemType;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;

readonly class MyCustomField extends AbstractMyCustomField
{
public function __construct()
{
parent::__construct(
'myCustomField',
[ConnectorSystemType::OCI, ConnectorSystemType::cXML]
);
}

public function getValue(LineItem $lineItem): ?string
{
// Custom logic to retrieve the value from the line item
$payload = $lineItem->getPayload();

return $payload['my_custom_value'] ?? null;
}
}

4. Create the LineItemContextResolver

The resolver determines via supports() whether it is responsible for a given line item.

<?php

declare(strict_types=1);

namespace MyPlugin\Connector\DataField\LineItemContextResolver;

use AgiqonConnector\Connector\DataField\LineItemContextResolver\AbstractLineItemContextResolver;
use Shopware\Core\Checkout\Cart\LineItem\LineItem;

class MyCustomContextResolver extends AbstractLineItemContextResolver
{
public function supports(LineItem $lineItem): bool
{
return $lineItem->getType() === 'my-custom-line-item-type';
}
}

5. Register the services

Register both services in your plugin's services.xml with the appropriate tags:

<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>

<!-- The new DataField -->
<service id="MyPlugin\Connector\DataField\Field\MyCustomField">
<tag name="agiqon_connector.data_field"/>
</service>

<!-- The matching LineItemContextResolver -->
<service id="MyPlugin\Connector\DataField\LineItemContextResolver\MyCustomContextResolver">
<tag name="agiqon_connector.line_item_context_resolver"/>
</service>

</services>
</container>

Result

After registration:

  • myCustomField is selectable in the connector configuration under the new type my_custom_type.
  • For cart positions of type my-custom-line-item-type, MyCustomContextResolver is used automatically.
  • All other positions are handled by the built-in resolvers.