se/bmecat
PHP/Laravel package for working with BMEcat, the XML format used to exchange electronic product catalog data. Read and write catalog documents containing products, prices, units, suppliers and classifications, making it easier to import or export procurement data in PHP applications.
The package is a small, framework-agnostic library — there is no Laravel service provider, facade, or config file. Treat it like a low-level XML builder you wrap yourself.
Install it. The current release is 0.1.4, a patch that fixes namespace imports:
composer require se/bmecat:0.1.4
It is still pre-1.0, so treat the API as unstable and pin exactly. If you are upgrading from an earlier 0.1.x, re-check your use statements — the namespace fix may change class resolution.
Read the source before the README. The README is thin; the real API surface is in src/ and the fixtures in tests/. You are looking for two things: the root document class and how elements are appended (builder methods vs. nested objects) and the serialization entry point (toXml(), asXml(), or a __toString).
Sketch the smallest possible document to prove the API:
$doc = new \Se\Bmecat\Bmecat(); // confirm real class name in src/
$doc->addHeader($supplierName, $supplierId);
$doc->addCatalog($catalogId, $catalogName);
$doc->addProduct($sku, $title, $price, $currency);
file_put_contents(storage_path('app/bmecat.xml'), $doc->toXml());
Validate immediately against the BMEcat XSD (BMEcat 1.2 bmecat_1_2.xsd or 2005, depending on the version the package targets):
xmllint --noout --schema bmecat_1_2.xsd storage/app/bmecat.xml
Schema validation is the fastest feedback loop you have with a low-activity package.
First real use case: a nightly export of your products table into a BMEcat file that a B2B customer's procurement system imports. Do not wire this into a controller — the package is CPU-light but I/O-heavy, so your first integration should be an Artisan command writing to Storage::disk('exports').
Wrap the library in a single service class. Keep every reference to Se\Bmecat\* inside one class so a future fork/replacement is a one-file change:
// app/Services/BmecatExporter.php
class BmecatExporter
{
public function export(Collection $products, CatalogConfig $config): string
{
$doc = new \Se\Bmecat\Bmecat();
$doc->addHeader($config->supplierName, $config->supplierId);
$doc->addCatalog($config->catalogId, $config->catalogName);
foreach ($products as $product) {
$doc->addProduct(
$product->sku,
$product->name,
$this->price($product, $config),
$config->currency,
);
}
return $doc->toXml();
}
}
Map Eloquent models through a DTO, never directly. BMEcat cares about element order, decimal formatting, and escaped entities — things Eloquent attributes don't guarantee. Convert Product → BmecatProductData (a readonly value object) and let the exporter consume only DTOs. This is also where you put default tax rates, price groups, and unit-of-measure mapping.
Use a queued job for the actual export, an Artisan command to schedule it.
// app/Console/Commands/ExportBmecat.php
Product::query()->chunkById(500, fn ($chunk) =>
ExportBmecatJob::dispatch($chunk, $customerId)
);
Chunking matters because the package almost certainly builds a full in-memory tree — see the memory gotcha below.
Drive catalog configuration from config, not code. A config/bmecat.php with per-customer blocks (supplier ID, catalog ID, currency, price group, buyer ID) lets you support multiple B2B customers without branching. Store the generated filename and hash in a catalog_exports table for audit and re-download.
Compose documents from a domain catalog object. Keep your own Catalog aggregate (products, categories, classification codes) and have a thin BmecatRenderer translate it. This keeps BMEcat-version quirks out of your models and lets you add a JSON/CSV exporter later using the same aggregate.
Test with XML snapshots plus XSD validation. In a Pest/PHPUnit test, assert toXml() output validates against the bundled XSD (run xmllint in the test setup) and snapshot the normalized XML. That combination catches both "wrong data" and "wrong structure."
"se/bmecat": "0.1.4") and do not assume future maintenance. If you upgraded from an earlier patch, run a smoke test that instantiates the root class and serializes a tiny document.each()/dynamic property issues before writing application code.xs:sequence, so header → catalog → products must be added in that order. If serialization fails validation, element ordering is almost always the cause — diff your output against a known-good fixture before suspecting data.&, <, and umlauts. Confirm whether the package uses DOMDocument or string concatenation; if it's the latter, run every free-text field through htmlspecialchars($v, ENT_XML1 | ENT_QUOTES, 'UTF-8').XMLWriter for streaming..-decimal prices with a defined currency; a localized 12,90 will either fail validation or import as the wrong price. Centralize price formatting in one method.addRawXml(string $xml)-style escape hatch in your wrapper — or generate those fragments with DOMDocument and append them before serialization.dd(). dd($doc) on a partially built document is unreadable; write toXml() to storage/app/tmp/bmecat.xml and inspect in an editor with a schema plugin.add* methods, keeping the parent's element-append pattern. Avoid modifying vendor/ — a composer update will erase it.How can I help you explore Laravel packages today?