Row transformers

For arlanix/product-feed 1.0.0

Row transformers

This page is for the PHP developer whose feed needs what the mapping cannot express: a price your ERP decides, a product one market must not see, one row per pack size. A row transformer is your own code over a feed's rows. It runs between the built product and the mapping, so every channel, file, SFTP, Merchant API and Preview, sees what it returns. For the other ways to extend a feed, see Extend the module.

Write one

Implement Arlanix\Feed\Model\Delivery\RowTransformerInterface:

<?php
declare(strict_types=1);

namespace Vendor\Module\Model;

use Arlanix\Feed\Model\Delivery\RowTransformerInterface;
use Arlanix\Feed\Model\Feed;
use Arlanix\Feed\Model\Mapping\RowContext;

class PackSizes implements RowTransformerInterface
{
    public function appliesTo(Feed $feed): bool
    {
        return $feed->getType() === 'google_shopping';
    }

    public function prepare(Feed $feed, array $rows): void
    {
        // Once per page of up to 500 rows, before any of them is transformed: read what you need in bulk here.
    }

    public function transform(Feed $feed, RowContext $row): array
    {
        if ($row->getValue('sku') === 'DISCONTINUED') {
            return [];                                               // left out
        }

        return [
            $row->withVariant('6', ['title' => $row->getValue('name') . ', pack of 6']),
            $row->withVariant('12', ['title' => $row->getValue('name') . ', pack of 12']),
        ];
    }
}

transform() answers with the rows of the product it was given:

It returns What happens
[$row->with([...])] The row goes on with those fields overwritten.
[] The product is left out of the feed. A row delivered for it before is taken down.
[$row->withVariant('a', [...]), $row->withVariant('b', [...])] One row per variant, each delivered and taken down on its own. A variant of a variant keeps both keys: a-x. A key is 1 to 64 characters, both keys together included; an empty or longer one throws InvalidArgumentException.

Each row arrives with its prices, stock and addresses already in it (price, final_price, special_price, currency, url, …), so overwriting one of them is all it takes to change what the mapping reads.

A row of another product, or the same variant of a product twice, stops the run with a LogicException naming the transformer.

What a row holds

transform() and prepare() are given Arlanix\Feed\Model\Mapping\RowContext objects, one per product row. A row holds the product attributes the store view's feeds read, the fields the module computes for every row, and, for the child of a configurable product, its parent's fields under parent. An attribute is in the row only when a feed of the store view maps it, or a resolver in its mapping declares it; see Declare the attributes it reads.

Method Returns
getValue(string $path): mixed A field's value, or null when the row has none. A dot reads into a nested field: parent.sku.
has(string $path): bool Whether getValue() gives something other than null.
with(array $overrides): RowContext The same row with these fields overwritten. A key with a dot is a field of that name, not a path.
withVariant(string $key, array $overrides = []): RowContext One of several rows of the product, with these fields overwritten; see Write one.
getVariantKey(): string The row's variant key, empty for a product's only row.
getProductId(): int The product's id.
getStoreId(): int The store view the row is for.
getData(): array Every field of the row, as an array.

A RowContext never changes: with() and withVariant() return a new row and leave the one they are called on as it was.

Register it

In your module's etc/di.xml, add the transformer to RowTransformerPool. sortOrder decides the order when there are several, each running on what the last one returned:

<type name="Arlanix\Feed\Model\Delivery\RowTransformerPool">
    <arguments>
        <argument name="transformers" xsi:type="array">
            <item name="pack_sizes" xsi:type="array">
                <item name="class" xsi:type="object">Vendor\Module\Model\PackSizes</item>
                <item name="sortOrder" xsi:type="number">10</item>
            </item>
        </argument>
    </arguments>
</type>

An item may also be the object itself, with no sortOrder; it then counts as 0, and runs before every transformer with a positive one.

Something registered there that does not implement the interface fails at the first request, naming the item.

What the store sees

A product a transformer leaves out is counted in the grid's Health like one the mapping leaves out. Why? says a module of this installation left it out, and the feed's products list gives the reason as Excluded by row transformer "pack_sizes"., so the item name is the one to choose with the store in mind.