iq2i/prestashop-webservice-bundle
A Symfony bundle that provides a simple client for the PrestaShop Webservice API, letting you query and manage shops, products, orders, customers, and other resources from your Symfony application without writing raw HTTP requests.
Reality check first: this is a Symfony bundle, not a Laravel package. Laravel does not have a bundle/kernel registration mechanism, so composer require iq2i/prestashop-webservice-bundle will pull in code you cannot boot the normal way. Treat the bundle as documentation of intent — a thin DI wrapper around PrestaShop's official PSWebServiceLibrary — and use the underlying library directly.
Minimal steps:
composer require prestashop/prestashop-webservice-lib
If that package name doesn't resolve, grab the single-file PSWebServiceLibrary.php from PrestaShop's /webservice docs and drop it in app/Support/PrestaShop/.config/services.php:
'prestashop' => [
'url' => env('PS_SHOP_URL'), // https://shop.example.com/
'key' => env('PS_WS_KEY'),
'debug' => env('APP_DEBUG', false),
],
The WS key is generated in PrestaShop admin under Advanced Parameters → Webservice, per-key with per-resource permissions.Resources/config/services.xml (or services.yml) once. It shows you exactly which parameters the bundle injects into the client — shop URL, key, debug flag — and that's the shape of the singleton you want to bind in a Laravel ServiceProvider./api/orders?date=1&filter[date_upd]=[2024-01-01]&display=full, hydrate into your own orders table. Read-only is the safest way to prove connectivity, key scoping, and XML parsing before you attempt writes.Bind the client as a singleton. The library is a plain PHP object holding a cURL handle; recreating it per call wastes connections and complicates timeouts.
$this->app->singleton(\PrestaShopWebService::class, fn () => new \PrestaShopWebService(
config('services.prestashop.url'),
config('services.prestashop.key'),
config('services.prestashop.debug')
));
Write a per-resource repository, not a god-service. One class per WS resource (ProductRepository, OrderRepository, StockRepository) wrapping the same client:
class ProductRepository
{
public function __construct(private \PrestaShopWebService $ws) {}
public function paginate(int $offset, int $limit, string $display = 'full')
{
return $this->ws->get([
'resource' => 'products',
'display' => $display,
'limit' => "$offset,$limit",
'sort' => 'id_ASC',
]);
}
public function find(int $id) { /* resource + id */ }
public function create(\SimpleXMLElement $xml) { /* ->add(['postXml' => $xml->asXML()]) */ }
public function update(\SimpleXMLElement $xml) { /* ->edit([...]) */ }
}
Use the "blank schema" trick for writes. Never hand-craft XML from memory; PrestaShop tells you the exact node shape:
$blank = $ws->get(['url' => $url . '/api/products?schema=blank']);
$xml = $blank->product;
$xml->price = '19.99';
$xml->name->language[0] = 'Widget'; // multilingual: one node per lang id
$xml->name->language[0]['id'] = 1;
$xml->associations->categories->category->id = 2;
$ws->add(['resource' => 'products', 'postXml' => $xml->asXML()]);
Sync via queued jobs, one job per resource page. Long display=full fetches over a large catalogue will blow PHP's memory and hit the WS timeout. Chunk with limit=0,100, dispatch SyncProductsPage per offset, and use filter[date_upd]=[last_sync_at] for incrementals. Store last_sync_at per resource in a sync_checkpoints table.
Stock updates are the killer feature. For a Laravel storefront or ERP pushing stock into PrestaShop, the pattern is: stock_availables?filter[id_product]=[...], then edit with the quantity field only — but read the resource first, because PrestaShop silently discards partial XML if required nodes are missing.
Normalize on the way out. The library returns SimpleXMLElement, not arrays, and multilingual fields are nested by language id. Hydrate into DTOs or Eloquent models at the repository boundary; do not let SimpleXMLElement leak into controllers or jobs.
SimpleXMLElement, so ->asXML() before postXml, and (string) $node before casting to int/float.display=full is expensive. Use narrowed displays where the PrestaShop version supports them: display=[id,name,price]. On PS 1.6 and older this syntax is unreliable — test against your shop's exact version before relying on it.limit returns a small first page (10). Always pass limit=0,100 (or your chunk size) explicitly; otherwise you'll silently sync a fraction of the catalogue and wonder why.filter[id]=[1|2|3] for OR, filter[date_upd]=>[2024-01-01] for ranges, date=1 to force date-typed comparisons. URL-encoding matters; let the client handle it or build the query string with http_build_query and PHP_QUERY_RFC3986.$xml->name->language[0] without ['id'], PrestaShop may drop it or attach it to the wrong language. Map your languages in a config array, not magic indexes.500 with an HTML error page when an override or module breaks the WS. The library throws PrestaShopWebserviceException — log the full response body alongside the HTTP status, or you'll debug blind.products usually means the resource isn't enabled on the key, not that the product is missing. Check the key's resource list first before chasing code.images/products/{id}, combinations, stock_availables. Plan your sync jobs as a graph, not a single fetch.sleep() between pages in bulk syncs.PrestaShopClient interface in your app so tests can swap in a fake repo — otherwise every unit test needs a live shop, which you don't have.How can I help you explore Laravel packages today?