ProductXmlExporter
in package
Exports the catalog in the import format.
Mirrors the import grammar so exactly that the generated file passes unchanged through the same
XmlImportParser/ImportValidator/XSD trio as a supplier file (the roundtrip doubles as a format
reference). Like FeedXmlGenerator: reads only through the Api managers, streams with XMLWriter
into a file and writes it atomically.
Every exported section is also written back on reimport (ImportProductWriter::apply*); the
export writes the current state, so a reimport of an unchanged export writes the same values.
IMPORTANT: the card <image> carries only src (the XSD productImageType has no assetId), unlike
variant <images><image assetId="">. The export therefore builds an absolute URL for the card photo and
always writes assetId for variant gallery images (an existing asset, no download on reimport).
At most ImportRules::MAX_IMAGES_PER_VARIANT images per variant: only the first 20 are written, and an XML comment is added.
Table of Contents
Constants
- ALL_SECTIONS : array<string|int, mixed> = [self::SECTION_CATEGORIES, self::SECTION_TEXTS,...
- DEFAULT_LIMIT : int = 500
- Safe cap of cards per synchronous admin export (read-only, higher than the import "run now" limit of 200).
- SECTION_ACCESSORIES : string = 'accessories'
- SECTION_CATEGORIES : string = 'categories'
- SECTION_IMAGES : string = 'images'
- SECTION_PARAMETERS : string = 'parameters'
- SECTION_PRICES : string = 'prices'
- SECTION_STOCK : string = 'stock'
- SECTION_TAGS : string = 'tags'
- SECTION_TEXTS : string = 'texts'
Properties
- $currencyManager : CurrencyManager
- $em : EntityManagerInterface
- $httpRequest : IRequest
- $languageManager : LanguageManager
- $parameterManager : ParameterManager
- $productManager : ProductManager
- $productSetManager : ProductSetManager
- $settingContext : SettingContext
- $siteBaseUrl : SiteBaseUrl|null
- $stockItemManager : StockItemManager
- $userGroupManager : UserGroupManager
- $warehouseManager : WarehouseManager
Methods
- __construct() : mixed
- export() : array{relativePath: string, fileName: string, productCount: int, variantCount: int, bundleCount: int, truncated: bool, requestedCount: int}
- absoluteAssetUrl() : string
- buildTargetPath() : array{abs: string, relative: string, fileName: string}
- flattenSetIds() : array<int, int>
- loadCatalogData() : ProductSet>>}
- loadParameterOptionCodebook() : array<int, array{key: ?string, name: ?string}>
- Option choices are child rows of the {@see Parameter} entity (`parentId`); for an option type `ProductSetParameterRel::getValue()` holds their `id` as a string. The batch loads the key (plus the readable name in the default language as a fallback when a choice has no `key`).
-
loadPriceCodebook()
: ProductSetCurrentPrice>>, currencies: array
, baseUserGroupId: int} - Returns the currently valid price per axis (currency x fromAmount x userGroupId) read from the read-only view {@see ProductSetCurrentPrice} (join `product_set_prices` x `max_published_time_for_prices`, `published_time <= NOW()`): the same source of truth as the front end, no own series grouping in PHP.
-
loadStockCodebook()
: ItemAvailability>, warehouseCodes: array
} - Returns stock availability per variant, batched through {@see StockItemManager::getAvailabilityBatch()} (the same read API as `ProductSetManager::getAvailability()`). The warehouse is `labelWarehouseId` (the first active one by priority with `available > 0`), falling back to the first `perWarehouse` row.
- openWriter() : XMLWriter
- Streams the document straight into a temporary file: `XMLWriter::openUri()` does not hold the whole document in memory, unlike `openMemory()`. The caller finishes it (`endDocument`/`flush`) and does the atomic `rename()` to the final path ({@see export()}, like {@see FeedXmlGenerator::publishFeedFile()}).
- resolveLanguageIds() : array<string, int>
- resolveProductIds() : array<int, int>
- Maps `productSetId` to its card (fallback `product`/`variant.product`, same as {@see FeedXmlGenerator::resolveProduct()}; the direct `ProductSet.product` is an optional denormalization).
- trimDecimal() : string
- `Vat.percents` and similar decimal columns without needless trailing zeros (`21.00` becomes `21`).
- writeAccessories() : void
- writeBundle() : void
- writeBundleItems() : void
- writeBundles() : int
- Writes `<bundles>` only from explicitly selected `$productSetIds` (a bundle has no `product`, so {@see resolveProductIds()} never adds it to the cards), which keeps the export-to-import roundtrip for bundles. Shares `writeTags()`/`writePrices()`/`writeImages()` with the variant; `writeParameters()`, `writeStock()` and `writeAccessories()` are not called because the bundle grammar has none of them.
- writeCategories() : void
- writeCDataSafe() : void
- Writes CDATA safely. `XMLWriter::writeCData()` only wraps the content and does not split the closing sequence `]]>` inside the text, so a single description containing it (even as a textual example) would produce `<![CDATA[a]]>b]]>` and make the whole export invalid XML.
- writeImages() : void
- writeLangElement() : void
- Text per language; without a `lang` attribute only for the default language of the file.
- writeParameters() : void
- writePrices() : void
- writeProduct() : void
- writeSettings() : void
- writeStock() : void
- writeTags() : void
- writeVariant() : void
Constants
ALL_SECTIONS
public
array<string|int, mixed>
ALL_SECTIONS
= [self::SECTION_CATEGORIES, self::SECTION_TEXTS, self::SECTION_PARAMETERS, self::SECTION_TAGS, self::SECTION_PRICES, self::SECTION_STOCK, self::SECTION_IMAGES, self::SECTION_ACCESSORIES]
DEFAULT_LIMIT
Safe cap of cards per synchronous admin export (read-only, higher than the import "run now" limit of 200).
public
int
DEFAULT_LIMIT
= 500
SECTION_ACCESSORIES
public
string
SECTION_ACCESSORIES
= 'accessories'
SECTION_CATEGORIES
public
string
SECTION_CATEGORIES
= 'categories'
SECTION_IMAGES
public
string
SECTION_IMAGES
= 'images'
SECTION_PARAMETERS
public
string
SECTION_PARAMETERS
= 'parameters'
SECTION_PRICES
public
string
SECTION_PRICES
= 'prices'
SECTION_STOCK
public
string
SECTION_STOCK
= 'stock'
SECTION_TAGS
public
string
SECTION_TAGS
= 'tags'
SECTION_TEXTS
public
string
SECTION_TEXTS
= 'texts'
Properties
$currencyManager read-only
private
CurrencyManager
$currencyManager
$em read-only
private
EntityManagerInterface
$em
$httpRequest read-only
private
IRequest
$httpRequest
$languageManager read-only
private
LanguageManager
$languageManager
$parameterManager read-only
private
ParameterManager
$parameterManager
$productManager read-only
private
ProductManager
$productManager
$productSetManager read-only
private
ProductSetManager
$productSetManager
$settingContext read-only
private
SettingContext
$settingContext
$siteBaseUrl read-only
private
SiteBaseUrl|null
$siteBaseUrl
= null
$stockItemManager read-only
private
StockItemManager
$stockItemManager
$userGroupManager read-only
private
UserGroupManager
$userGroupManager
$warehouseManager read-only
private
WarehouseManager
$warehouseManager
Methods
__construct()
public
__construct(EntityManagerInterface $em, ProductManager $productManager, ProductSetManager $productSetManager, ParameterManager $parameterManager, LanguageManager $languageManager, CurrencyManager $currencyManager, UserGroupManager $userGroupManager, StockItemManager $stockItemManager, WarehouseManager $warehouseManager, SettingContext $settingContext, IRequest $httpRequest[, SiteBaseUrl|null $siteBaseUrl = null ]) : mixed
Parameters
- $em : EntityManagerInterface
- $productManager : ProductManager
- $productSetManager : ProductSetManager
- $parameterManager : ParameterManager
- $languageManager : LanguageManager
- $currencyManager : CurrencyManager
- $userGroupManager : UserGroupManager
- $stockItemManager : StockItemManager
- $warehouseManager : WarehouseManager
- $settingContext : SettingContext
- $httpRequest : IRequest
- $siteBaseUrl : SiteBaseUrl|null = null
export()
public
export(array<int, int> $productIds, array<int, int> $productSetIds, array<int, string> $languages, bool $includeIds, array<int, string> $sections[, int $limit = self::DEFAULT_LIMIT ]) : array{relativePath: string, fileName: string, productCount: int, variantCount: int, bundleCount: int, truncated: bool, requestedCount: int}
Parameters
- $productIds : array<int, int>
-
explicit cards
- $productSetIds : array<int, int>
-
sets, mapped to their card
- $languages : array<int, string>
-
language aliases; empty means all configured
- $includeIds : bool
- $sections : array<int, string>
-
allowed sections (ALL_SECTIONS); empty means all
- $limit : int = self::DEFAULT_LIMIT
Return values
array{relativePath: string, fileName: string, productCount: int, variantCount: int, bundleCount: int, truncated: bool, requestedCount: int}absoluteAssetUrl()
private
absoluteAssetUrl(object $asset, string $baseUrl) : string
Parameters
- $asset : object
- $baseUrl : string
Return values
stringbuildTargetPath()
private
buildTargetPath() : array{abs: string, relative: string, fileName: string}
Return values
array{abs: string, relative: string, fileName: string}flattenSetIds()
private
flattenSetIds(array<int, array<int, ProductSet>> $setsByProduct) : array<int, int>
Parameters
- $setsByProduct : array<int, array<int, ProductSet>>
Return values
array<int, int>loadCatalogData()
private
loadCatalogData(array<int, int> $productIds, array<int, string> $enabledSections) : ProductSet>>}
Parameters
- $productIds : array<int, int>
- $enabledSections : array<int, string>
Return values
ProductSet>>}loadParameterOptionCodebook()
Option choices are child rows of the {@see Parameter} entity (`parentId`); for an option type `ProductSetParameterRel::getValue()` holds their `id` as a string. The batch loads the key (plus the readable name in the default language as a fallback when a choice has no `key`).
private
loadParameterOptionCodebook(array<int, array<int, ProductSet>> $setsByProduct, int|null $defaultLanguageId) : array<int, array{key: ?string, name: ?string}>
Option and range types are told apart without duplicating the numeric constants of
ParameterType (they live only in ImportValidator):
value2 !== null means range, otherwise parameter->getHasOptions() means option, otherwise text,
the same signal as the validator.
Parameters
- $setsByProduct : array<int, array<int, ProductSet>>
- $defaultLanguageId : int|null
Return values
array<int, array{key: ?string, name: ?string}> —choice id => key/name
loadPriceCodebook()
Returns the currently valid price per axis (currency x fromAmount x userGroupId) read from the read-only view {@see ProductSetCurrentPrice} (join `product_set_prices` x `max_published_time_for_prices`, `published_time <= NOW()`): the same source of truth as the front end, no own series grouping in PHP.
private
loadPriceCodebook(array<int, array<int, ProductSet>> $setsByProduct) : ProductSetCurrentPrice>>, currencies: array, baseUserGroupId: int}
Parameters
- $setsByProduct : array<int, array<int, ProductSet>>
Return values
ProductSetCurrentPrice>>, currencies: arrayloadStockCodebook()
Returns stock availability per variant, batched through {@see StockItemManager::getAvailabilityBatch()} (the same read API as `ProductSetManager::getAvailability()`). The warehouse is `labelWarehouseId` (the first active one by priority with `available > 0`), falling back to the first `perWarehouse` row.
private
loadStockCodebook(array<int, array<int, ProductSet>> $setsByProduct) : ItemAvailability>, warehouseCodes: array}
Parameters
- $setsByProduct : array<int, array<int, ProductSet>>
Return values
ItemAvailability>, warehouseCodes: arrayopenWriter()
Streams the document straight into a temporary file: `XMLWriter::openUri()` does not hold the whole document in memory, unlike `openMemory()`. The caller finishes it (`endDocument`/`flush`) and does the atomic `rename()` to the final path ({@see export()}, like {@see FeedXmlGenerator::publishFeedFile()}).
private
openWriter(string $tmpPath) : XMLWriter
Parameters
- $tmpPath : string
Return values
XMLWriterresolveLanguageIds()
private
resolveLanguageIds(array<int, string> $languages) : array<string, int>
Parameters
- $languages : array<int, string>
Return values
array<string, int> —alias => id in request order (the first one is the default language of the file)
resolveProductIds()
Maps `productSetId` to its card (fallback `product`/`variant.product`, same as {@see FeedXmlGenerator::resolveProduct()}; the direct `ProductSet.product` is an optional denormalization).
private
resolveProductIds(array<int, int> $productIds, array<int, int> $productSetIds) : array<int, int>
Returns a sorted, deduplicated list of card ids.
Parameters
- $productIds : array<int, int>
- $productSetIds : array<int, int>
Return values
array<int, int>trimDecimal()
`Vat.percents` and similar decimal columns without needless trailing zeros (`21.00` becomes `21`).
private
trimDecimal(string $value) : string
Parameters
- $value : string
Return values
stringwriteAccessories()
private
writeAccessories(XMLWriter $writer, ProductSet $set) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
writeBundle()
private
writeBundle(XMLWriter $writer, ProductSet $bundle, array<string, int> $languageIds, string $defaultLangAlias, bool $includeIds, array<int, string> $enabledSections, ProductSetCurrentPrice>>, currencies: array, baseUserGroupId: int} |null $priceCodebook) : void
Parameters
- $writer : XMLWriter
- $bundle : ProductSet
- $languageIds : array<string, int>
- $defaultLangAlias : string
- $includeIds : bool
- $enabledSections : array<int, string>
-
$priceCodebook
: ProductSetCurrentPrice>>, currencies: array
, baseUserGroupId: int} |null
writeBundleItems()
private
writeBundleItems(XMLWriter $writer, ProductSet $bundle) : void
Parameters
- $writer : XMLWriter
- $bundle : ProductSet
writeBundles()
Writes `<bundles>` only from explicitly selected `$productSetIds` (a bundle has no `product`, so {@see resolveProductIds()} never adds it to the cards), which keeps the export-to-import roundtrip for bundles. Shares `writeTags()`/`writePrices()`/`writeImages()` with the variant; `writeParameters()`, `writeStock()` and `writeAccessories()` are not called because the bundle grammar has none of them.
private
writeBundles(XMLWriter $writer, array<int, int> $productSetIds, array<string, int> $languageIds, string $defaultLangAlias, bool $includeIds, array<int, string> $enabledSections) : int
Parameters
- $writer : XMLWriter
- $productSetIds : array<int, int>
- $languageIds : array<string, int>
- $defaultLangAlias : string
- $includeIds : bool
- $enabledSections : array<int, string>
Return values
intwriteCategories()
private
writeCategories(XMLWriter $writer, Product $product, int|null $defaultLanguageId) : void
Parameters
- $writer : XMLWriter
- $product : Product
- $defaultLanguageId : int|null
writeCDataSafe()
Writes CDATA safely. `XMLWriter::writeCData()` only wraps the content and does not split the closing sequence `]]>` inside the text, so a single description containing it (even as a textual example) would produce `<![CDATA[a]]>b]]>` and make the whole export invalid XML.
private
static writeCDataSafe(XMLWriter $writer, string $value) : void
The text is therefore split at the ]]> boundary into several CDATA sections so that ]] ends one and
> starts the next; the parser joins them back. The sequence is neither escaped nor removed, the
description stays identical character by character.
a]]>b -> <![CDATA[a]]]]><![CDATA[>b]]> -> read as a]]>b
Parameters
- $writer : XMLWriter
- $value : string
writeImages()
private
writeImages(XMLWriter $writer, ProductSet $set, array<string, int> $languageIds) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
- $languageIds : array<string, int>
writeLangElement()
Text per language; without a `lang` attribute only for the default language of the file.
private
writeLangElement(XMLWriter $writer, string $tag, string $alias, string $defaultLangAlias, string $value, bool $cdata) : void
Parameters
- $writer : XMLWriter
- $tag : string
- $alias : string
- $defaultLangAlias : string
- $value : string
- $cdata : bool
writeParameters()
private
writeParameters(XMLWriter $writer, ProductSet $set, array<int, array{key: ?string, name: ?string}> $optionCodebook) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
- $optionCodebook : array<int, array{key: ?string, name: ?string}>
writePrices()
private
writePrices(XMLWriter $writer, ProductSet $set, ProductSetCurrentPrice>>, currencies: array, baseUserGroupId: int} $priceCodebook) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
-
$priceCodebook
: ProductSetCurrentPrice>>, currencies: array
, baseUserGroupId: int}
writeProduct()
private
writeProduct(XMLWriter $writer, Product $product, array<int, ProductSet> $sets, array<string, int> $languageIds, string $defaultLangAlias, bool $includeIds, array<int, string> $enabledSections, ProductSetCurrentPrice>>, currencies: array, baseUserGroupId: int} |null $priceCodebook, ItemAvailability>, warehouseCodes: array} |null $stockCodebook, array<int, array{key: ?string, name: ?string}>|null $optionCodebook, string $baseUrl) : void
Parameters
- $writer : XMLWriter
- $product : Product
- $sets : array<int, ProductSet>
- $languageIds : array<string, int>
- $defaultLangAlias : string
- $includeIds : bool
- $enabledSections : array<int, string>
-
$priceCodebook
: ProductSetCurrentPrice>>, currencies: array
, baseUserGroupId: int} |null -
$stockCodebook
: ItemAvailability>, warehouseCodes: array
} |null - $optionCodebook : array<int, array{key: ?string, name: ?string}>|null
- $baseUrl : string
writeSettings()
private
writeSettings(XMLWriter $writer, string $defaultLangAlias) : void
Parameters
- $writer : XMLWriter
- $defaultLangAlias : string
writeStock()
private
writeStock(XMLWriter $writer, ProductSet $set, ItemAvailability>, warehouseCodes: array} $stockCodebook) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
-
$stockCodebook
: ItemAvailability>, warehouseCodes: array
}
writeTags()
private
writeTags(XMLWriter $writer, ProductSet $set, int|null $defaultLanguageId) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
- $defaultLanguageId : int|null
writeVariant()
private
writeVariant(XMLWriter $writer, ProductSet $set, array<string, int> $languageIds, string $defaultLangAlias, bool $includeIds, array<int, string> $enabledSections, ProductSetCurrentPrice>>, currencies: array, baseUserGroupId: int} |null $priceCodebook, ItemAvailability>, warehouseCodes: array} |null $stockCodebook, array<int, array{key: ?string, name: ?string}>|null $optionCodebook) : void
Parameters
- $writer : XMLWriter
- $set : ProductSet
- $languageIds : array<string, int>
- $defaultLangAlias : string
- $includeIds : bool
- $enabledSections : array<int, string>
-
$priceCodebook
: ProductSetCurrentPrice>>, currencies: array
, baseUserGroupId: int} |null -
$stockCodebook
: ItemAvailability>, warehouseCodes: array
} |null - $optionCodebook : array<int, array{key: ?string, name: ?string}>|null