rebing/laravel-soccerama
Laravel wrapper for the Soccerama football API, offering a simple configured client to fetch leagues, fixtures, teams, players, standings, and live scores. Integrates cleanly with Laravel, letting you query soccer data through familiar Eloquent-style models and caching.
composer require rebing/laravel-soccerama
php artisan vendor:publish --tag=soccerama
Set the token in .env (the config key name may differ slightly — check config/soccerama.php after publishing):
SOCCERAMA_API_TOKEN=your-token-here
# optionally override the base URI if you're pointed at a proxy/self-hosted gateway
SOCCERAMA_BASE_URI=https://api.soccerama.pro/v2.0
On Laravel versions with package discovery the Rebing\Soccerama\SocceramaServiceProvider is registered automatically. On older installs, add the provider and the Soccerama facade alias to config/app.php by hand.
Where to look first, in this order:
config/soccerama.php — token, base URI, timeouts, and any per-endpoint defaults.src/Soccerama.php — the root class the facade resolves. Its public methods are the endpoint accessors (matches(), teams(), players(), standings(), topscorers(), livescores(), etc.). This is the real "API map."src/Endpoints/ (or src/Resources/) — one class per endpoint, exposing methods like all(), find($id), between($from, $to), today().src/ you'll find in the API reference.A minimal first call — pull the next week of fixtures:
use Rebing\Soccerama\Facades\Soccerama;
$fixtures = Soccerama::matches()->between(
now()->toDateString(),
now()->addDays(7)->toDateString()
);
foreach ($fixtures as $fixture) {
dump($fixture->home_team->name ?? null, $fixture->localteam_score ?? null);
}
Method names and the exact response shape depend on the wrapper version and the upstream API revision — verify against src/Soccerama.php before you build on it, and check that env('SOCCERAMA_API_TOKEN') resolves non-null (an empty token usually returns a 401 rather than a clear "token missing" error).
Facade for scripts, DI for everything else. The facade is fine in tinker, one-off artisan commands, and controllers with trivial needs. For anything you'll test, inject Rebing\Soccerama\Soccerama through the constructor so you can swap it in the container:
class FixtureImporter
{
public function __construct(private Soccerama $soccerama) {}
}
Wrap the wrapper in a repository. Don't scatter Soccerama::matches()->between(...) across the codebase. Put one class in front of the package that owns the mapping from API payloads to your Eloquent models / DTOs. The API's field names (localteam, visitorteam, localteam_score) do not match your domain models, and you do not want them leaking into views.
class SocceramaRepository
{
public function fixturesForDate(Carbon $date): Collection
{
$raw = Cache::remember("fixtures:{$date->toDateString()}", 3600, fn () =>
$this->soccerama->matches()->between($date->toDateString(), $date->toDateString())
);
return collect($raw)->map(fn ($m) => FixtureData::fromSoccerama($m));
}
}
Cache aggressively — this is the single highest-leverage pattern with Soccerama. The package does not cache by default (some forks ship an optional cache decorator; check config/soccerama.php). The upstream API is rate-limited, and reference data almost never changes. Sensible TTLs:
| Endpoint | TTL |
|---|---|
countries, competitions, seasons |
7–30 days |
teams, players, venues |
7 days |
standings, topscorers |
1 hour |
matches (past + scheduled) |
1 hour |
livescores |
don't cache, or 10–30s |
Key on the full request (endpoint + all query args), not just the endpoint — matches()->between($a, $b) and matches()->between($b, $c) must not collide.
Batch by date range, not by ID. A common mistake is looping over a list of teams and calling teams()->find($id) inside it. One between($from, $to) call returns everything for that window. This is the difference between one API call and fifty.
Schedule live data; don't poll in the request cycle. Use a Schedule::command() at 1–5 minute intervals during match windows to refresh livescores into your own table, and serve the app from your DB. This decouples page load latency from the upstream API and gives you a natural retry surface.
Test by binding a fake. The simplest approach is to resolve a stub in the container:
$this->instance(Soccerama::class, new FakeSoccerama($fixtures));
If the class is final or you can't mock it cleanly through the container, mock Illuminate\Support\Facades\Http instead and let the wrapper hit the HTTP fake — it works as long as the wrapper uses the Laravel HTTP client rather than raw Guzzle, which is worth confirming in src/ before relying on it.
Retry, don't crash. Wrap calls in retry(3, 1000) and let 429s and 5xx bubble up only after retries are exhausted. Log the endpoint and query string on failure; the upstream error body is usually more useful than the wrapper's exception message.
Verify the package is still viable before adopting it. 13 stars and zero dependents is a strong signal to read src/ end-to-end and run a live smoke test (one fixtures call, one team lookup) before writing any app code against it. Forks of wrappers often lag the upstream API's current response shape — a field renamed upstream breaks your mapping silently, not loudly.
Upstream token and base URI are the two config values that matter. If the token is wrong you get a 401/403; if the base URI is stale (the API has been through ownership changes over the years) you'll get DNS or 404 errors that look like auth failures. Test the base URI with curl outside the app first to isolate whether the problem is config or the package.
Responses are plain arrays/objects, not collections. between() typically returns an array of stdClass or an array of arrays depending on the version. Check for empty results explicitly; some endpoints return false or null on failure rather than throwing, and foreach over null gives you a TypeError three layers deep.
Timezone: assume UTC. Soccerama timestamps are UTC. If you're rendering match times for users you must convert at the display layer, and if you're filtering "today" you need to decide whose "today" — the server's, the user's, or the competition's. Getting this wrong produces missing fixtures around midnight.
Rate limits are enforced server-side and quietly. A tight loop of find() calls will start returning errors mid-run, which looks like an intermittent bug. Batch, cache, and throttle scheduled jobs.
Config caching. php artisan config:cache in production will bake SOCCERAMA_API_TOKEN (via env() reading in the config file) at deploy time. If you rotate the token you must re-cache config, not just restart the app.
Endpoint gaps are usually your problem, not the package's. If the wrapper doesn't expose an endpoint you need, don't fork the package — resolve the configured HTTP client / base URI from the container and hit the endpoint directly with Http::withToken(...)->get(...). Keep it in your repository class alongside the wrapped calls.
Extension point: swap the binding. If you need to add endpoints cleanly, subclass the root Soccerama class and rebind it in your own service provider's register():
$this->app->singleton(\Rebing\Soccerama\Soccerama::class, \App\Services\MySoccerama::class);
Do this after the package provider, or app()->register() ordering will silently overwrite it.
Pin the version in composer.json. Without upstream maintenance, an unpinned composer update on a transitive dependency can change the HTTP client or response handling under you. Commit composer.lock, and note the specific commit/tag you validated when you adopted the package — future you will want that when a call starts returning 500s.
How can I help you explore Laravel packages today?