Campaign stores
A store is persistence only. Batches, status changes and events live in the actions, so a store just keeps campaigns and their recipients. The configured one (config: store, env CAMPAIGNS_STORE) is bound in the container as Contracts\CampaignStore — scoped, so each request or queued job gets its own — and its reads return copies — changing a returned object changes nothing until it is saved:
use RoundlyConsulting\Campaigns\Contracts\CampaignStore;
// The configured store, as the actions see it (bound scoped — one per request or queued job):
$store = app(CampaignStore::class);
$store->find('e184d08f-5081-4fbb-9604-7a2460c3fb3a'); // ?Campaign — a copy
$store->recipients('e184d08f-5081-4fbb-9604-7a2460c3fb3a'); // Collection<int, CampaignRecipient>
$store->countRecipients('e184d08f-5081-4fbb-9604-7a2460c3fb3a'); // RecipientCounts: total, processed, failedThe contract
| Method | Purpose |
|---|---|
find(string $campaignUuid): ?Campaign | A stored campaign; null when unknown. |
all(int $offset = 0, int $limit = 10): Collection | A page of campaigns in creation order. |
insert(Campaign $campaign): bool | Insert a new campaign; false — and nothing written — when its uuid is taken, live or soft-deleted. Must be one atomic step. |
save(Campaign $campaign): void | Insert or update the campaign by its uuid. |
saveIfStatus(Campaign $campaign, CampaignStatus $expected): bool | Save only while the stored status is still $expected — the atomic compare-and-set every status change goes through. False for a campaign the store doesn’t hold. |
saveRecipients(string $campaignUuid, array $recipients): void | Insert or update each recipient keyed by (campaign, uuid) — the same recipient saved under two campaigns is kept in both. |
recipients(string $campaignUuid, int $offset = 0, ?int $limit = null): Collection | The campaign’s recipients in the order they were added; a null limit returns them all. |
findRecipient(string $campaignUuid, string $recipientUuid): ?CampaignRecipient | A recipient of this campaign; one of any other campaign reads as null. |
countRecipients(string $campaignUuid): RecipientCounts | The recipients counted by outcome — total, processed, failed — which progress is built from. |
insert() (refuse a taken uuid) and saveIfStatus() (write only while the stored status is the expected one) must each be one atomic step: they are what make duplicate uuids and racing starts safe.
Shipped stores
- Stores\InMemoryCampaignStore (default) — keeps campaigns in memory for the current request, console command or queued job. Ideal for tests and create-and-send flows; nothing survives it.
- Stores\DatabaseCampaignStore — keeps campaigns in the CampaignRecord and CampaignRecipientRecord models. See Database persistence.
Both run through one shared contract test suite, so they behave identically. To keep campaigns anywhere else, implement Contracts\CampaignStore and point the config at it — it’s resolved from the container, so constructor dependencies are injected:
// config/campaigns.php
'store' => \App\Campaigns\ApiCampaignStore::class, // implements Contracts\CampaignStoreCode that sends campaigns never talks to the store directly — it goes through the Campaigns facade, an injected CampaignManager or the actions (see DI and actions).
Show your open-source love
This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.
More ways to support, including cryptoBy donating, you agree to our donation terms.
Want this built into your product?
We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.