glitchr / omnifood
Omnifood: one contract for the restaurant platforms - delivery orders (Uber Eats, Deliveroo, Just Eat) and table reservations (TheFork, Zenchef) - their menus, their stores, their webhooks, and its Symfony bundle.
Requires
- php: >=8.2
- symfony/http-client-contracts: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- symfony/config: ^6.4|^7.0|^8.0
- symfony/dependency-injection: ^6.4|^7.0|^8.0
- symfony/http-client: ^6.4|^7.0|^8.0
- symfony/http-kernel: ^6.4|^7.0|^8.0
Suggests
None
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-03 16:36:05 UTC
README
One contract for the restaurant platforms - the delivery orders (Uber Eats, Deliveroo, Just Eat) and the table reservations (TheFork, Zenchef) - the Omnibus of the kitchen, beside glitchr/omnitrade (payments), glitchr/omnibus (shipping) and glitchr/omnipost (publishing).
// A webhook: checked, read, the same shape whatever the platform. $notification = $ubereats->notify($request->getContent(), $request->headers->all()); $order = $notification->order ?? $ubereats->order($notification->reference); $ubereats->accept($order->reference, new \DateTimeImmutable('+20 minutes')); // within 11.5 minutes $ubereats->ready($order->reference); // the courier may come $deliveroo->pushMenu($menu); // checked first: InvalidMenuException, nothing sent $deliveroo->setAvailability('gyoza', false); // out of stock $justeat->pause(new \DateTimeImmutable('+30 minutes')); foreach ($thefork->reservations(new \DateTimeImmutable('today'), new \DateTimeImmutable('tomorrow')) as $reservation) { echo $reservation->date->format('H:i'), ' ', $reservation->covers, ' ', $reservation->guest->name(), "\n"; }
Capabilities, not one big interface. The platforms do different things: a platform implements
what it offers - OrdersInterface, MenuInterface, StoreInterface, ReservationsInterface,
NotifiableInterface - and Auth\RefreshableInterface / Auth\OAuthInterface for its tokens.
What a platform offers but its public documentation does not show throws NotSupportedException:
nothing is guessed.
One model. Order (its Lines and their Modifiers, Customer, Courier, Money in minor
units, OrderType, OrderStatus), Menu\{Menu, Category, Item, ModifierGroup, Modifier} (prices,
VAT rates, the EU-14 Allergens, the restaurant's own references), Reservation (Guest,
ReservationStatus), Slot, Hours, StoreStatus, Notification, Token. Each keeps the
platform's own answer in $raw.
Checked before it is sent. capabilities() says what a platform takes (its order types, how long
it gives to accept, how deep modifiers nest, allergens, photos, VAT rates, names' lengths...); the
Validator lists what a menu breaks, by path, for a back office to show; pushMenu() runs it first.
The keys stay with the restaurant. A platform is built from options - a client id, a secret, a
store id - and calls the platform directly through the application's HTTP client. Omnifood depends
on nothing but symfony/http-client-contracts; it never depends on omnibase.
| Package | Platform | Capabilities |
|---|---|---|
omnifood/ubereats |
Uber Eats (Marketplace APIs) | orders, menu, store, webhooks, token, OAuth (store linking) |
omnifood/deliveroo |
Deliveroo (Partner Platform) | orders, menu, store, webhooks, token |
omnifood/justeat |
Just Eat Takeaway.com (JET Connect) | orders (pushed), menu, store, webhooks |
omnifood/thefork |
TheFork (B2B API) | reservations, webhooks, token |
omnifood/zenchef |
Zenchef | credentials only: the API is documented on request |
None of these platforms gives credentials without a partner agreement: every package is written
from the platform's public documentation and tested against recorded answers (MockHttpClient),
not verified against the live APIs. Each README says what to ask for.
Install
composer require glitchr/omnifood omnifood/ubereats omnifood/deliveroo omnifood/justeat omnifood/thefork
Symfony
Omnifood\Bridge\Symfony\OmnifoodBundle: every omnifood/* package installed registered,
Omnifood\Registry and Omnifood\Validator autowired, each configured platform injectable by its
name, as what it does.
omnifood: platforms: ubereats: { factory: ubereats, options: { client_id: '%env(default::UBEREATS_CLIENT_ID)%', client_secret: '%env(default::UBEREATS_CLIENT_SECRET)%', store_id: '%env(default::UBEREATS_STORE_ID)%' } } deliveroo: { factory: deliveroo, options: { client_id: '%env(default::DELIVEROO_CLIENT_ID)%', client_secret: '%env(default::DELIVEROO_CLIENT_SECRET)%', brand_id: '%env(default::DELIVEROO_BRAND_ID)%', site_id: '%env(default::DELIVEROO_SITE_ID)%', menu_id: '%env(default::DELIVEROO_MENU_ID)%', webhook_secret: '%env(default::DELIVEROO_WEBHOOK_SECRET)%' } } thefork: { factory: thefork, options: { client_id: '%env(default::THEFORK_CLIENT_ID)%', client_secret: '%env(default::THEFORK_CLIENT_SECRET)%', restaurant_id: '%env(default::THEFORK_RESTAURANT_ID)%', webhook_token: '%env(default::THEFORK_WEBHOOK_TOKEN)%' } }
public function __construct(OrdersInterface $ubereats, MenuInterface $deliveroo, ReservationsInterface $thefork, Registry $omnifood, Validator $validator) {}
Nothing is built when the container compiles: a platform is built the first time it is asked for,
and a key left empty only shows when a call needs it (InvalidConfigException) - capabilities()
needs none. Keys typed in a back office rather than kept in the environment:
$registry->create('ubereats', ['client_id' => ..., 'client_secret' => ...]). An application's own
PlatformFactoryInterface is registered too (autoconfigured).
Without Symfony
$registry = new Registry([new UberEatsPlatformFactory($http), new TheForkPlatformFactory($http)], [ 'ubereats' => ['factory' => 'ubereats', 'options' => ['client_id' => '...', 'client_secret' => '...', 'store_id' => '...']], 'thefork' => ['factory' => 'thefork', 'options' => ['client_id' => '...', 'client_secret' => '...', 'restaurant_id' => '...']], ]); $registry->orders(); // the platforms that take orders $registry->reservations(); // the ones that take reservations
Docker: every platform with your keys
docker/ runs this package with every omnifood/* platform - from GitHub, or from the checkouts
beside this one when OMNIFOOD_PLUGINS=../.. is set - and a console that exercises them with the
keys in docker/.env (copy .env.dist):
cd docker && cp .env.dist .env docker compose run --rm omnifood platforms # installed, configured, what each does docker compose run --rm omnifood capabilities ubereats # no key needed docker compose run --rm omnifood menu:push deliveroo /omnifood/core/docker/menu.example.json --validate docker compose run --rm omnifood orders deliveroo --since "-2 hours" docker compose run --rm omnifood order ubereats <id> docker compose run --rm omnifood accept ubereats <id> --ready-at "+20 minutes" docker compose run --rm omnifood deny ubereats <id> --reason too_busy docker compose run --rm omnifood ready deliveroo <id> docker compose run --rm omnifood store:status ubereats docker compose run --rm omnifood reservations thefork --from today --to "+7 days" docker compose run --rm omnifood notify ubereats /omnifood/core/docker/body.json -H "X-Uber-Signature: ..." docker compose run --rm omnifood refresh thefork docker compose run --rm omnifood test # every package's tests
More in docs/.
License: LGPL-3.0-or-later.