> ## Documentation Index
> Fetch the complete documentation index at: https://docs.stockshift.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Laravel

> Utilisez l'API StockShift depuis une application Laravel. Le paquet repose sur le SDK PHP de StockShift. Il ajoute la configuration, une façade, des requêtes qui renvoient des collections, une route webhook qui déclenche des événements Laravel, des appels mis en file d'attente, un substitut de test et des commandes Artisan.

Il exige PHP 8.2 ou une version ultérieure et Laravel 12 ou 13.

## Installation

```bash theme={null}
composer require stockshift/laravel
php artisan stockshift:install
```

`stockshift:install` publie `config/stockshift.php`, ajoute `STOCKSHIFT_TOKEN` et `STOCKSHIFT_WEBHOOK_SECRET` à `.env` et à `.env.example` et vous demande leurs valeurs. Créez une clé d'API dans StockShift sous Paramètres > Accès > Clés d'API. Ensuite, vérifiez-la :

```bash theme={null}
php artisan stockshift:test
```

## Démarrage rapide

```php theme={null}
use StockShift\Laravel\Facades\StockShift;

StockShift::catalog()->createItem(['sku' => 'BOLT-M4', 'name' => 'M4 hex bolt', 'type' => 'stocked', 'make_or_buy' => 'buy']);

$low = StockShift::stock()
    ->where('status', 'available')
    ->orderBy('available_to_promise')
    ->lazy();

foreach ($low as $row) {
    echo $row['sku'], ' ', $row['available_to_promise'], "\n";
}
```

## Faire des appels

La façade donne accès à chaque domaine de l'API, comme `catalog()` ou `inventory()`, et chaque domaine a une méthode par opération. Ce sont les méthodes du SDK, avec les mêmes arguments et les mêmes types PHPStan :

```php theme={null}
$item = StockShift::catalog()->getItem('01J8QZ4M...');
echo $item['data']['sku'];
```

Vous pouvez aussi appeler un chemin directement avec `get()`, `post()`, `put()`, `patch()`, `delete()` ou `request()` :

```php theme={null}
StockShift::get('/catalog/items/{item}', ['path' => ['item' => '01J8QZ4M...']]);
```

La façade, une `StockShift\Laravel\Connection` injectée et un `StockShift\Sdk\StockShift` injecté utilisent tous la connexion par défaut.

## Requêtes

Chaque liste de l'API a un constructeur de requêtes. `where()` envoie les filtres propres à l'API, et les résultats reviennent sous forme de collections Laravel :

```php theme={null}
$bolts = StockShift::items()
    ->search('bolt')
    ->where('status', 'active')
    ->select('sku', 'name')
    ->get();                  // Collection

$first = StockShift::items()->where('type', 'stocked')->first();
$count = StockShift::purchaseOrders()->where('status', 'sent')->count();
```

`lazy()` renvoie une `LazyCollection` qui ne récupère la page suivante que lorsque vous y arrivez. Avec `limit()` ou `take()`, les pages au-delà de la limite ne sont jamais récupérées :

```php theme={null}
StockShift::movements()->where('item', 'BOLT-M4')->lazy()->take(50)->each(function (array $movement) {
    // ...
});
```

L'API renvoie chaque liste dans son propre ordre fixe. `orderBy()` et `orderByDesc()` trient plutôt de votre côté, donc ils lisent d'abord toute la liste. Les champs peuvent utiliser la notation pointée.

Les constructeurs de la façade sont `items()`, `stock()`, `movements()`, `counts()`, `transfers()`, `locations()`, `suppliers()`, `purchaseOrders()`, `customers()`, `salesOrders()`, `quotes()`, `invoices()`, `returns()`, `builds()`, `boms()` et `workCenters()`. Toute autre liste fonctionne à partir de son chemin :

```php theme={null}
StockShift::query('/sales/price-lists/{priceList}', ['priceList' => $id])->get();
```

Autres méthodes :

| Méthode | Ce qu'elle fait |
| - | - |
| `when($condition, fn ($query) => ...)` | Complète la requête seulement si la condition est remplie. |
| `after($cursor)` | Commence après un curseur que vous avez gardé. |
| `page($cursor = null)` | Une seule page, avec `records` et `nextCursor`, pour paginer à la main. |
| `remember($ttl = null)` | Garde en cache les résultats de `get()`, `first()` et `count()`. |
| `toQuery()` | La chaîne de requête que l'API recevra. |

Les énumérations sont envoyées sous forme de valeur et les dates en ISO 8601.

## Connexions

Une connexion, c'est un jeton, le secret webhook qui l'accompagne et ses paramètres. Ajoutez-en d'autres dans `config/stockshift.php` pour appeler plusieurs organisations :

```php theme={null}
'connections' => [
    'default' => [/* ... */],
    'eu' => [
        'token' => env('STOCKSHIFT_EU_TOKEN'),
        'webhook_secret' => env('STOCKSHIFT_EU_WEBHOOK_SECRET'),
    ],
],
```

```php theme={null}
StockShift::connection('eu')->items()->get();
```

Pour en injecter une, marquez le paramètre :

```php theme={null}
use StockShift\Laravel\Attributes\StockShiftConnection;
use StockShift\Laravel\Connection;

public function __construct(#[StockShiftConnection('eu')] private Connection $stockshift) {}
```

Chaque connexion a ces paramètres :

| Clé | Valeur par défaut | Signification |
| - | - | - |
| `token` | `STOCKSHIFT_TOKEN` | Une clé d'API ou un jeton d'accès OAuth. |
| `webhook_secret` | `STOCKSHIFT_WEBHOOK_SECRET` | Le secret de signature, ou plusieurs séparés par des virgules pendant que vous le renouvelez. |
| `base_url` | `https://app.stockshift.io/api/v1` | L'adresse de l'API. |
| `timeout`, `connect_timeout` | `30`, `10` | En secondes. |
| `max_retries` | `2` | Nombre de nouvelles tentatives pour un appel qui peut être répété. |
| `max_retry_after` | `60` | Le plus long `Retry-After`, en secondes, qui vaut la peine d'attendre. |
| `rate_limit` | `null` | Appels par minute que cette application fait avec un même jeton. |
| `headers` | `[]` | En-têtes envoyés avec chaque appel. |

### Un jeton par locataire

Dans une application où chaque locataire (tenant) connecte sa propre organisation StockShift, cherchez le jeton avant chaque appel :

```php theme={null}
// In a service provider's boot method
StockShift::resolveTokenUsing(fn (string $connection) => tenant()->stockshift_token);
```

Ou créez une connexion pour un jeton là où vous en avez besoin :

```php theme={null}
StockShift::withToken($team->stockshift_token)->items()->get();
```

Les résultats de requêtes en cache et les limites de débit sont tenus séparément pour chaque jeton.

## Erreurs

Un appel qui échoue lève l'`ApiException` du SDK, qui contient le corps des détails du problème RFC 9457. Basez votre logique sur `errorCode`, qui est stable :

```php theme={null}
use StockShift\Sdk\Exception\ApiException;

try {
    StockShift::catalog()->createItem($item);
} catch (ApiException $error) {
    if ($error->errorCode === 'validation_failed') {
        return back()->withErrors($error->errors());
    }

    throw $error;
}
```

Un appel qui ne reçoit aucune réponse lève une `ConnectionException`. Les deux étendent `StockShiftException`.

## Nouvelles tentatives, clés d'idempotence et appels en file d'attente

Le SDK réessaie un `429` après `Retry-After`. Il réessaie un `5xx` ou une défaillance réseau pour `GET`, `PUT` et `DELETE`, et pour un `POST` ou un `PATCH` seulement s'il a une clé d'idempotence :

```php theme={null}
StockShift::integrations()->pushStockNow($connectionId, ['idempotencyKey' => (string) Str::uuid()]);
```

`later()` envoie un appel depuis une tâche en file d'attente. Un `POST` ou un `PATCH` reçoit une clé d'idempotence à la création de la tâche, donc les propres nouvelles tentatives de la tâche envoient la même clé et l'API applique la modification une seule fois :

```php theme={null}
StockShift::later('POST', '/catalog/items', ['body' => $item])->onQueue('stockshift');
```

La tâche est `StockShift\Laravel\Jobs\SendRequest`. Elle fait cinq tentatives. Une réponse `4xx` autre que `429` la fait échouer immédiatement, parce que la renvoyer donnerait la même réponse. Un jeton fourni par `withToken()` accompagne la tâche. La tâche est chiffrée dans la file d'attente.

## Limites de débit

Définissez `rate_limit` sur une connexion pour plafonner les appels par minute que votre application fait avec un même jeton. Tous les processus qui partagent le magasin de cache partagent le compte. Un appel qui dépasserait la limite attend de pouvoir être envoyé. Laissez-le à `null` pour vous fier à la limite de l'API, que le client attend déjà.

Les requêtes passent par le client HTTP de Laravel, donc `Http::fake()`, les événements `RequestSending` et `ResponseReceived` et des outils comme Telescope les voient.

## Webhooks

Le paquet ajoute une route `POST /stockshift/webhook`. Donnez son URL complète à StockShift quand vous ajoutez un point de terminaison webhook, et mettez le secret de signature que StockShift vous montre dans `STOCKSHIFT_WEBHOOK_SECRET`.

Chaque livraison est vérifiée par rapport à l'en-tête `StockShift-Signature`. Une livraison qui échoue reçoit un `400` et ne va pas plus loin. Une livraison qui passe déclenche deux événements Laravel : `WebhookReceived` pour chaque livraison, puis la classe propre à l'événement, comme `InventoryStockMoved` :

```php theme={null}
use Illuminate\Support\Facades\Event;
use StockShift\Laravel\Events\InventoryStockMoved;

Event::listen(function (InventoryStockMoved $event) {
    // $event->id, $event->type, $event->created, $event->data['item_id'], $event->connection
});
```

Chaque type d'événement de la référence de l'API a une classe dans `StockShift\Laravel\Events`, avec `$data` typé pour PHPStan. `WebhookReceived` se déclenche aussi pour les types qui n'ont pas de classe dans cette version du paquet.

### Écouter avec un attribut

Marquez une classe ou une méthode avec `#[OnStockShiftWebhook]` :

```php theme={null}
namespace App\Listeners;

use StockShift\Laravel\Attributes\OnStockShiftWebhook;
use StockShift\Laravel\Events\InventoryStockMoved;

#[OnStockShiftWebhook('inventory.stock.moved')]
class UpdateShelfLabels
{
    public function handle(InventoryStockMoved $event): void
    {
        // ...
    }
}
```

Sur une classe, il appelle `handle()` ou `__invoke()`. Sur une méthode, il appelle cette méthode. Donnez plusieurs types, ou terminez-en un par `*` pour en couvrir plusieurs, comme `sales.order.*`. Typez le paramètre comme `WebhookEvent` quand il peut recevoir plus d'une classe. `*` seul écoute `WebhookReceived`.

Les classes de `app/Listeners` sont analysées. Changez `webhooks.discover` dans la configuration pour analyser d'autres dossiers. Un type qui ne correspond à aucun événement échoue au démarrage avec un indice, pour qu'une faute de frappe ne passe pas inaperçue. Un écouteur que la découverte d'événements de Laravel a déjà trouvé par son indication de type n'est pas ajouté deux fois. Les écouteurs qui implémentent `ShouldQueue` sont mis en file d'attente comme d'habitude.

`php artisan optimize` met en cache les écouteurs qu'il trouve. `stockshift:cache` et `stockshift:clear` font la même chose séparément.

### Nouvelles tentatives et file d'attente

StockShift réessaie une livraison qui ne reçoit pas de `2xx`, et chaque nouvelle tentative garde l'`id` de l'événement. Le paquet mémorise les identifiants pendant une journée et déclenche les événements une seule fois. Si un écouteur lève une exception, l'identifiant est oublié et la livraison reçoit un `500`, pour que la nouvelle tentative soit traitée. Changez `webhooks.deduplicate_for` pour garder les identifiants plus longtemps, ou mettez-le à `0` pour n'en garder aucun.

Définissez `STOCKSHIFT_WEBHOOK_QUEUE` pour répondre à StockShift immédiatement et déclencher les événements depuis une tâche en file d'attente. `STOCKSHIFT_WEBHOOK_QUEUE_CONNECTION` choisit la connexion de file d'attente.

### Autres routes

Mettez `webhooks.path` à `null` pour omettre la route et la placer vous-même. Une route pour une autre connexion vérifie le secret de cette connexion :

```php theme={null}
Route::stockshiftWebhooks('webhooks/stockshift-eu', 'eu');
```

Le middleware fonctionne aussi seul, sous `stockshift.webhook` ou `stockshift.webhook:eu`. Il place l'événement vérifié dans l'attribut de requête `stockshift.event`. Les routes webhook n'ont besoin d'aucune exception CSRF parce qu'elles ne font pas partie du groupe `web`. Pour ajouter un middleware, comme `throttle:120,1`, inscrivez-le dans `webhooks.middleware`.

## Appeler l'API à la main

`Http::stockshift()` est une requête HTTP Laravel avec l'URL de base, le jeton et les délais d'expiration de la connexion. Elle ne réessaie pas et ne lève pas d'exception en cas d'erreur :

```php theme={null}
$response = Http::stockshift()->get('catalog/items', ['search' => 'bolt']);
$response->json('data.items');

Http::stockshift('eu')->post('catalog/items', $item);
```

## Commandes Artisan

| Commande | Ce qu'elle fait |
| - | - |
| `stockshift:install` | Publie la configuration et ajoute les paramètres à `.env`. |
| `stockshift:test` | Vérifie le jeton et montre à qui il appartient. |
| `stockshift:webhooks` | Liste les points de terminaison webhook de l'organisation. `--events` liste les types d'événements auxquels un point de terminaison peut s'abonner. |
| `stockshift:cache`, `stockshift:clear` | Met en cache ou vide les écouteurs par attribut. |

Chacune prend `--connection` quand elle appelle l'API. `php artisan about` montre la connexion par défaut et si les écouteurs sont en cache.

## Tests

`StockShift::fake()` répond aux appels avec les réponses que vous fournissez et enregistre chacun d'eux. Rien n'atteint l'API, et aucun jeton n'est nécessaire :

```php theme={null}
use StockShift\Laravel\Facades\StockShift;
use StockShift\Laravel\Testing\RecordedRequest;

$fake = StockShift::fake([
    'GET /catalog/items' => StockShift::page([['sku' => 'BOLT-M4']]),
    'GET /catalog/items/*' => ['data' => ['sku' => 'BOLT-M4']],
    'POST /catalog/items' => StockShift::response(['data' => ['id' => '01J9']], 201),
    'DELETE /catalog/items/*' => 204,
    'POST /purchasing/orders' => StockShift::problem(422, 'validation_failed', 'The supplier is archived.'),
]);

// Run your code, then:
StockShift::assertSent('POST /catalog/items', fn (RecordedRequest $request) => $request->body['sku'] === 'BOLT-M4');
StockShift::assertNotSent('DELETE /catalog/items/*');
StockShift::assertSentCount(3);
```

Une clé est une méthode suivie d'un chemin après l'URL de base. `*` correspond à n'importe quoi. Une valeur est un corps JSON, un code de statut, une réponse PSR-7 ou une fermeture qui prend la `RecordedRequest` et renvoie l'un de ceux-ci. `StockShift::page()` construit une page de liste avec les enregistrements sous le nom que la liste utilise. Un appel sans réponse lève `UnexpectedRequest`. Appeler `fake()` de nouveau ajoute des réponses au même substitut.

`RecordedRequest` a `connection`, `method`, `path`, `query`, `body`, `header()` et `idempotencyKey()`. `recorded()` les renvoie toutes, ou celles qui correspondent à une route. `assertNothingSent()` vérifie qu'aucun appel n'a été fait.

Pour tester vos écouteurs de webhooks, envoyez une livraison signée par la route :

```php theme={null}
StockShift::fakeWebhook('inventory.stock.moved', [
    'item_id' => '01J8QZ4M...',
    'quantity' => '2.000000',
])->send()->assertNoContent();
```

`dispatch()` déclenche les événements sans la requête HTTP. `payload()` et `signature()` donnent le corps et l'en-tête si vous voulez l'envoyer vous-même.

Comme les appels passent par le client HTTP de Laravel, `Http::fake()` fonctionne aussi, si vous préférez simuler au niveau HTTP.

## Versions

Le paquet suit la gestion sémantique de version. Il prend en charge les versions de Laravel qui reçoivent des correctifs de sécurité et les versions de PHP que chacune d'elles prend en charge. Une nouvelle version majeure du SDK arrive avec une nouvelle version majeure du paquet.


## Related topics

- [Webhooks](https://docs.stockshift.io/fr-ca/guides/webhooks.md)
- [SDK](https://docs.stockshift.io/fr-ca/sdks/index.md)
- [StockShift pour les développeurs](https://docs.stockshift.io/fr-ca/index.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.