carono/commerceml
Laravel package for exchanging data with 1C using the CommerceML XML format. Import and export catalogs, products, offers, prices, and orders, mapping CommerceML documents to Eloquent models through configurable parsers and XML generators.
Start by installing the package and pinning it, because the maintenance signal is thin (22 stars, 0 dependents):
composer require carono/commerceml
There is no service provider or config file, so there is nothing to publish. Your first 15 minutes should be spent reading, in this order:
README.md — the API surface is small; confirm the exact factory/parse method names and whether it expects a file path or an XML string.src/ — identify the root parser class and the entity classes (Product, Category, Offer, Price). This is where you learn the getters you'll actually call.composer.json — check the PHP constraint and whether it pulls in an old PHPUnit or XML dependency that conflicts with Laravel 10/11.tests/ (if present) — sample fixtures are the fastest way to see real 1C XML shapes.Your first use case should be the smallest possible: parse one import.xml and dump categories and products to a command, without touching the database.
php artisan make:command DebugCommerceML
// app/Console/Commands/DebugCommerceML.php
$xml = file_get_contents(storage_path('app/1c/import.xml'));
// Convert 1C's typical windows-1251 export before handing to the parser
$xml = mb_convert_encoding($xml, 'UTF-8', 'windows-1251');
$doc = CommerceML::parse($xml); // verify exact name in src/
foreach ($doc->getCategories() as $category) {
dump($category->getId(), $category->getName(), $category->getParentId());
}
Once categories and products print correctly, you have the shape you'll map to Eloquent and can move on to offers.xml.
Wrap it in an adapter — do not call the package from controllers or models. Given the low star count, you want a single seam to replace or fork later:
namespace App\Services\OneC;
interface CommerceMLReader
{
/** @return CategoryData[] */
public function categories(string $path): array;
/** @return ProductData[] */
public function products(string $path): array;
/** @return OfferData[] */
public function offers(string $path): array;
}
Map the package's objects into your own readonly DTOs. Every downstream consumer then depends on your DTOs, not Carono\CommerceML\*.
Follow the two-file protocol. 1C almost always ships a ZIP (or a directory) with import.xml (catalogue + products) and offers.xml (prices, stock, units), and sometimes orders.xml. Process in that order:
storage/app/1c/incoming/{uuid}/.<Ид> (GUID) into categories with a self-referencing parent_external_id.external_id, upsert into products.prices and stocks keyed by offer <Ид>.external_id was not seen in this batch (track an import_batch_id).Use external GUIDs as your join key, never names. The <Ид> is stable across exports; names get renamed constantly. Add external_id unique indexes on every synced table.
Queue it. A catalogue of 20k SKUs will blow past PHP's max_execution_time. Wrap each step (categories / products / offers) in a ShouldQueue job, and record import status on an imports table so you can show progress and retry a failed step.
Standard 1C HTTP handshake. If 1C is configured to POST to your app (rather than drop files), implement the mode=checkauth|init|file|import|query contract as a controller, save the uploaded file, dispatch the import job, and return success — do this before doing any parsing in the request.
Keep fixtures for tests. Drop two or three anonymised import.xml/offers.xml files into tests/Fixtures/1c/ and write feature tests against your adapter. This is your only defence against 1C changing its export subtly between client versions.
Encoding is the number-one failure. 1C frequently exports windows-1251 while declaring UTF-8 (or vice versa). If simplexml_load_string() returns false or you see mojibake, convert first and strip the BOM. Never trust the encoding attribute in the XML declaration.
CommerceML 2.0x vs 3.0 are different schemas. Versions 2.05/2.06 use <КоммерческаяИнформация><Каталог><Товары> while 3.0 restructures around <КоммерческаяИнформация><Каталог><Товары> with different nesting for properties and <Предложения>. Pin your parser to a fixture and assert the version by inspecting the <ВерсияСхемы> attribute; store it, and refuse to process a version you haven't tested.
Namespaces. If the exporter adds an XML namespace, plain SimpleXML xpath with Russian element names silently returns empty sets. Use registerXPathNamespace() or strip namespaces with a DOM pass before parsing. If the package returns empty arrays for no obvious reason, this is almost always why.
Large offers.xml kills memory. SimpleXML loads the whole document; a 50MB offers file can exceed a 256MB memory_limit. Before committing, test with a production-sized file. If it OOMs, either raise the limit for the queue worker or write a streaming XMLReader parser for offers only, keeping the package for the smaller import.xml.
Watch the offer ID convention. Offers often use <Ид> of the form {productGuid}#{characteristicGuid}. Split on # to resolve the product and the variant — do not assume offers map 1:1 to products.
Numeric parsing. Prices arrive as strings, sometimes with a comma decimal separator, and units come as separate <БазоваяЕдиница> / <ЕдиницаИзмерения> codes. Cast explicitly and default to 0.00 on missing values rather than letting null propagate into your price columns.
Images are relative paths. <Картинка> values refer to files inside the 1C export directory. Copy them to your storage disk during import and rewrite to your own URL — otherwise they break as soon as 1C cleans up its temp folder.
Package health. With 0 dependents and 22 stars, assume no upstream bug fixes. Pin composer.json to a specific version ("carono/commerceml": "1.*"), keep the adapter layer thin, and be prepared to vendor the source into app/ if it breaks on a PHP upgrade. The domain logic you need is small enough that forking or replacing with a hand-rolled SimpleXML reader is a realistic fallback — the adapter pattern above makes that a one-file change.
How can I help you explore Laravel packages today?