kfoobar/laravel-sportmonks-soccer
A Laravel package for integrating the Sportmonks Soccer API, offering convenient access to fixtures, leagues, teams, players, standings, and live match data. Streamlines soccer data retrieval in Laravel apps with simple configuration.
This is a Laravel wrapper around the Sportmonks Football API, and v2.0.0 is a major version bump — it targets API v3 of the upstream service, switches to the Laravel HTTP client, and changes authentication to header-based tokens. If you're starting fresh, install the package, publish its config, and set your token in .env:
composer require sportmonks/football-api
php artisan vendor:publish --tag=sportmonks-config
SPORTMONKS_API_TOKEN=your-token-here
Because authentication is now header-based (Authorization: <token> under the hood), you no longer append ?api_token= to URLs or wire up a custom Guzzle handler. If your token is configured correctly, a smoke test should be your first call:
use Sportmonks\Football\Facades\Sportmonks;
$leagues = Sportmonks::leagues()->all();
Where to look first: the new upgrade guide and API endpoint documentation shipped with this release. The endpoint docs are now the source of truth for which methods exist, because v3 renamed or dropped several v1/v2 concepts. Start with the upgrade guide if you have existing code, then the endpoint docs to map the methods you actually call.
First use case: a "today's fixtures" page or a league table widget. Both exercise the three things v2 gets right — paginated collection access, includes/relations, and the rate-limit metadata you'll need once you start caching.
Wrap calls in a service or repository, not your controllers. The client is fluent and chainable, which is convenient but easy to scatter across a codebase. A thin FootballRepository that exposes the four or five queries your app actually needs (fixturesForDate, leagueTable, liveScores, teamSquad) keeps the v3 surface area contained — important, because a future v3 → v4 bump will land on this package again.
Treat pagination as a first-class concern. v2 adds pagination validation, meaning malformed or out-of-range page parameters now fail loudly instead of silently returning empty sets. Loop deliberately:
$page = 1;
do {
$response = Sportmonks::leagues()->all(['page' => $page]);
// persist $response->data()
$page++;
} while ($response->hasMorePages());
If the package exposes pagination metadata through a response object, prefer that over counting results. Don't assume a fixed per_page — validate against the documented maximum before building "load more" UI.
Use rate-limit metadata to drive caching, not retries. v2 surfaces rate-limit headers. Read them and store the remaining/reset values alongside your cached payloads:
$response = Sportmonks::fixtures()->between($from, $to, ['include' => 'participants;league']);
Cache::put("fixtures:{$from}:{$to}", $response->data(), now()->addMinutes(10));
Wrap all read calls in Cache::remember with a TTL matched to the data's volatility: live scores need seconds, league tables minutes, team/venue metadata days. This is the single biggest lever on staying inside your plan's quota.
Migrate liveBySeasonId() call sites to liveByLeagueId() before you upgrade production. This is a hard rename, not an alias. liveBySeasonId() is gone in v2 — anything polling live scores keyed by season will now 400 or throw. Audit with:
grep -rn "liveBySeasonId" app/ tests/
Note the semantic shift too: live data is now scoped by league, not season. If your app's core concept is "the current season of league X," resolve the season from the league once and cache that mapping, rather than trying to pass season IDs into live endpoints.
Remove any usage of video and aggregated topscorer methods. These were dropped because the upstream v3 endpoints don't support them. There's no drop-in replacement inside the package:
topscorers include on a league/season endpoint if the docs expose one. Expect this to be a small refactor, not a config toggle.Do this work before bumping the constraint, not after — those methods will fatal, not degrade.
Leverage the Laravel HTTP client's testability. Because v2 is built on Illuminate\Support\Facades\Http, your test suite can fake the whole API without touching the package's internals:
Http::fake([
'api.sportmonks.com/*' => Http::response(['data' => []], 200),
]);
This is a genuine improvement over a hand-rolled Guzzle stack — you get Http::assertSent(), request recording, and consistent failure simulation for free. Add one integration test that hits the real API in a nightly job to catch upstream schema drift.
Compose includes explicitly. v3 leans harder on include strings. Build them from constants or an enum rather than inline strings, and never include more than you render — includes inflate payload size and, on some plans, count toward rate limits.
^2.0 and read the upgrade guide end to end. Laravel's HTTP client and header auth are the two changes most likely to interact badly with existing middleware, proxies, or logging that assumed a query-string token.Authorization in any HTTP client beforeSending hook.liveByLeagueId() changes your data model. Live scores are keyed by league. If you were joining on season IDs, expect null relationships until you remap. Verify your fixtures' league IDs against the v3 leagues endpoint before assuming IDs carried over.->retry(3, 200) on the underlying client or wrap calls in a job with RateLimited middleware.$client->{$method}()) and string-based dispatch will blow up at runtime. Grep for both method names as strings.api.sportmonks.com/v3/...) and that any timeout/retry keys in the published config match what the new HTTP-client implementation reads. Stale config from a previous version can be silently ignored.Http::globalRequestMiddleware() rather than forking the package. For multi-tenant apps, resolve the token per-request via a service provider rather than relying on the single .env value.data.*.id and a couple of real fields rather than snapshotting whole responses, or your tests will churn on every upstream tweak.How can I help you explore Laravel packages today?