Resumable PrestaShop 9 Imports: Supplier Feeds Without Lost Data
A large supplier import rarely fails at a convenient boundary. The process may stop after saving a product but before recording progress, after downloading an image but before attaching it, or after reading half a file that the supplier then replaces.
The objective is not simply to keep a PHP process alive longer. It is to make partial progress explainable and restartable without duplicating products, losing updates or applying stale stock data. That requires an import model before it requires a faster loop.
This article describes a module-owned ingestion design for PrestaShop 9. It does not assume a built-in queue, checkpoint table or universal supplier-import API. Choose supported domain operations appropriate to the fields your integration writes.
A recurring import also needs reliable cron-job design: source checkpoints alone do not prevent overlapping workers or unsafe retries.
Freeze the source before processing it
Assign each received feed an immutable identity, such as a supplier identifier plus a content checksum and receipt record. Store the exact file that will be processed in a controlled location. A retry should read the same bytes unless an operator deliberately creates a new import.
Do not repeatedly reopen a supplier’s moving download URL and call that the same job. If the contents change between attempts, a row number or offset may refer to different data. The importer can then skip records or combine two snapshots without noticing.
Record the format, encoding, delimiter, expected columns and supplier’s meaning for the feed. Is it a complete catalog snapshot or a delta? Does absence mean deletion, no change or temporary omission? These rules determine reconciliation behavior.
Verify the file before applying writes. Reject unexpected headers, impossible encoding or a suspiciously incomplete delivery according to your agreed feed contract. A short file can be a valid delta or a truncated snapshot; the importer should not guess which.
Retain the source identity with every failure report so a developer can reproduce the exact input.
Resolve identity before creating or updating products
An SKU is not automatically a globally unique PrestaShop entity key. Suppliers can reuse references, combinations can have their own identifiers and different shops can intentionally map the same external item differently.
Create a mapping contract that includes the supplier, external item identity and any required shop or destination scope. Map that identity to the appropriate product or combination. Enforce uniqueness at the same boundary used by the lookup.
When a feed contains duplicate external identities, quarantine or resolve them through an explicit rule. Do not let the last row silently win unless that is the documented supplier contract. Record which rows conflicted and what action was taken.
Avoid fuzzy matching by product name during routine imports. Names are descriptive and can change. If initial reconciliation requires manual matching, keep that as a controlled setup step and persist the resulting mapping.
Before creating a new entity, check for an existing mapping and for unresolved prior attempts. A crash after creation but before mapping persistence can otherwise create another product on restart. The creation and mapping workflow needs an atomic boundary where feasible or a reconciliation strategy where it is not.
Stream records, but keep validation bounded too
Reading incrementally avoids loading an entire feed into memory, but streaming alone does not bound memory if the application retains every parsed record, error or hydrated entity.
The following PHP generator reads a local CSV file one record at a time. It is a parser example, not a complete PrestaShop importer. It explicitly sets the CSV escape argument rather than relying on its changing default behavior. PHP CSV reference.
<?php
function readSupplierRows(string $path): Generator
{
$stream = fopen($path, 'rb');
if ($stream === false) {
throw new RuntimeException('Cannot open supplier file');
}
try {
$header = fgetcsv($stream, 0, ',', '"', '');
if ($header === false || count(array_unique($header)) !== count($header)) {
throw new RuntimeException('Missing or duplicate CSV headers');
}
foreach (['external_id', 'quantity'] as $required) {
if (!in_array($required, $header, true)) {
throw new RuntimeException('Required CSV column is absent');
}
}
$record = 1;
while (($fields = fgetcsv($stream, 0, ',', '"', '')) !== false) {
++$record;
if ($fields === [null]) {
continue;
}
if (count($fields) !== count($header)) {
throw new RuntimeException('Column mismatch at record ' . $record);
}
yield $record => array_combine($header, $fields);
}
} finally {
fclose($stream);
}
}
Treat the yielded values as untrusted strings. Validate identifiers, numeric ranges and field lengths before mapping or persistence. Add your format-specific handling for byte-order marks, encoding and malformed content. Bound record size at ingestion or in a parser suited to hostile or unusually large input; incremental reading alone does not limit one enormous CSV field.
The record counter is a logical parser position, not necessarily a physical line number, because quoted CSV fields can contain newlines. Preserve that distinction in error reports and checkpoint design.
Commit progress with the work it describes
A checkpoint should identify the immutable source, parser position or partition, mapping version and last committed unit. Advance it only after the associated write has become durable.
For local transactional work, commit the business change and progress record together where the supported domain operation and storage permit it. Do not wrap arbitrary core commands and external side effects in a transaction and assume everything will roll back together.
If atomicity is unavailable, make the write repeat-safe or reconcile it before replay. For an absolute quantity snapshot, repeating the same accepted version may be safe if the integration has a clear stock-ownership policy. Repeating a decrement is a different operation and needs a stable event identity.
A batch should be small enough to retry and large enough to avoid excessive overhead. Choose the size from measured behavior and transaction constraints, not a universal number copied from another store.
Track per-record outcomes separately from the high-water checkpoint when failures can be skipped. Otherwise, advancing past a bad row can make it disappear from future processing. A quarantined record should retain its source identity, reason and retry or correction path.
Separate image acquisition from catalog persistence
Images introduce network timeouts, file validation and additional state transitions. Keeping every product transaction open while waiting for downloads makes recovery harder.
Consider a separate image-work record tied to the immutable feed item and intended product or combination. The catalog step records the desired image identity; a worker downloads, validates and attaches it through the supported media workflow.
Validate the remote source policy before fetching. Restrict schemes and destinations as appropriate, limit redirects, enforce time and size budgets and reject unexpected content. A filename extension alone does not prove an image is valid. These are importer design requirements, not claims about a particular core downloader.
Download to controlled temporary storage and only promote validated content. Preserve a stable external image identity or checksum so retries can recognize completed work rather than attaching duplicates.
Decide how an image failure affects publication. The product may remain unpublished until required media is ready, or the business may permit a partial catalog update. Make that policy explicit instead of letting worker timing decide what customers see.
Handle concurrency and stale feeds deliberately
Two imports for the same supplier and shop can race even if each is individually resumable. Establish whether they may run concurrently and which feed revision is allowed to update a record.
If a newer snapshot has already been applied, an older retry should not overwrite it merely because it finishes later. Store a source revision or ordering token where the supplier contract provides one, and enforce a consistent comparison policy.
Also coordinate with other writers. Staff may edit product descriptions while a feed updates stock. Declare field ownership so the import changes only the fields it owns. A full-object save based on an old read can overwrite unrelated manual edits.
For inventory, decide whether the supplier provides physical stock, available stock or another quantity. The integration must account for the store’s reservation and order workflow through supported stock operations. Do not write a raw core stock column simply because it resembles the supplied number.
Locks can serialize a partition, but they do not resolve an unknown remote outcome or an outdated source revision. Use them alongside identity and reconciliation. The cron reliability guide covers those execution concerns in more depth.
Reconcile complete snapshots only after completion is proven
A complete catalog feed may require detecting items no longer supplied. That destructive or deactivating phase should run only after the importer proves it processed a valid complete snapshot according to the supplier contract.
Do not deactivate every unseen product after a parser failure or interrupted download. Maintain a received, validated, processing and completed distinction for the feed. Missing records in an incomplete snapshot are not reliable evidence of withdrawal.
Before applying broad absence-based changes, produce a reviewable count and, where the business requires it, a candidate list. Compare it with expected ranges derived from the actual feed agreement and prior history. A dramatic change should be investigated rather than normalized automatically.
Keep deactivation separate from deletion unless the business explicitly requires deletion and all related consequences are understood. Resumability includes recovering from incorrect source data, not only process crashes.
Prove restart behavior with fault injection
Use a small synthetic feed with valid rows, a duplicate identity, an invalid quantity and a failing image. Stop the worker after a committed batch and restart it. Verify both progress and the resulting catalog.
Then stop it at the difficult boundary: after a business write may have completed but before acknowledgement. Confirm that replay or reconciliation produces one intended entity and one intended effect.
Test a second feed revision while the first has unfinished work. Verify that your ordering policy prevents stale overwrites. Run the same fixtures in two shops with distinct mappings to expose accidental scope leakage.
The operator view should report accepted, changed, unchanged, quarantined and pending-image records separately. A single imported count hides too much to support recovery.
A safe long-running import is a sequence of durable, identifiable steps. Immutable input, precise mapping, bounded processing and checkpoints tied to committed work allow the process to stop without losing its meaning—and to resume without guessing what happened before it failed.
For the underlying platform context, see our PrestaShop 9 performance guide and apply the version-specific checks above to the installation you are evaluating.
When selecting an extension, browse PrestaSuite’s PrestaShop 9 modules and check the documented version support and integration requirements against your own test results.