Download the PHP package nietonchique/sofascore-api-bundle without Composer
On this page you can find all versions of the php package nietonchique/sofascore-api-bundle. It is possible to download/install these versions without Composer. Possible dependencies are resolved automatically.
Download nietonchique/sofascore-api-bundle
More information about nietonchique/sofascore-api-bundle
Files in nietonchique/sofascore-api-bundle
Package sofascore-api-bundle
Short Description Symfony bundle and standalone PHP client for the unofficial SofaScore API: matches, players, teams, tournaments and live per-sport statistics, with a pluggable HTTP / headless-Chrome transport.
License MIT
Informations about the package sofascore-api-bundle
SofaScore API Bundle
A Symfony bundle and standalone PHP client for the (unofficial) SofaScore API — matches, players, teams, tournaments and live per-sport statistics.
It is a faithful PHP port of the Python sofascore-wrapper
library, redesigned around a pluggable transport layer, typed DTOs for the core
entities, and full PHPStan (max) / Deptrac / PHPUnit coverage.
Disclaimer. This is an unofficial client and is not affiliated with, endorsed by, or supported by SofaScore. The API it talks to is undocumented and may change or block access at any time. Using it may violate SofaScore's Terms of Service — use at your own risk.
Requirements
- PHP 8.4+
- Symfony 8.0+ components (when used as a bundle)
- Optional: a Chromium/Chrome binary +
chrome-php/chromefor the headless-browser transport (Cloudflare fallback)
Installation
In a Symfony application using Symfony Flex the bundle is registered
automatically. Otherwise add it to config/bundles.php:
Usage
Standalone (any PHP project)
In Symfony (dependency injection)
SofascoreClient and every endpoint group are autowired services:
Endpoint groups
Access each group via the client factory methods:
| Method | Group | Bound argument |
|---|---|---|
search(string $q, int $page = 0) |
Search |
search term + page |
match(?int $matchId = null) |
MatchEndpoint |
match (event) id |
player(int $playerId) |
Player |
player id |
playerSearch(string $query) |
PlayerSearch |
query |
team(int $teamId) |
Team |
team id |
league(int $leagueId) |
League |
unique-tournament id |
manager(int $managerId) |
Manager |
manager id |
transfers() |
Transfers |
— |
news() |
News |
— |
userData() |
UserData |
— |
flag(string $flagCode) |
Flag |
country code |
americanFootball() / baseball() / basketball() / cricket() / esports() / iceHockey() / mma() / motorsport() / rugby() / tennis() |
per-sport groups | — |
Return types
The API surface is large and SofaScore changes response fields without notice, so the return style is intentionally hybrid and predictable:
- The primary entity-detail getters return typed DTOs:
MatchEndpoint::getMatch(): Event,Player::getPlayer(): Player,Team::getTeam(): Team,League::getLeague(): Tournament. Every DTO keeps the full original payload accessible via->raw/->toArray(). - Every other method returns a decoded
array(the raw JSON), exactly as the Python library does.
Translations
Many SofaScore entities ship an embedded fieldTranslations dictionary. The typed
DTOs expose it as a ?FieldTranslations object:
LanguageCode is a convenience catalogue of known SofaScore locale codes
(including the base set en, ru, sr and the codes observed in real responses).
You can still pass any arbitrary string to nameIn() / shortNameIn().
For methods that return raw arrays (e.g. match()->gamesByDate()), the
fieldTranslations key is preserved in the response unchanged.
Error handling
Every exception thrown by the bundle implements SofascoreExceptionInterface:
| Exception | Thrown when |
|---|---|
ApiException |
any non-2xx / undecodable response — base class, exposes getStatusCode() and getUrl() |
ApiBlockedException (extends ApiException) |
HTTP 403 (Cloudflare); triggers the chain fallback |
NotFoundException (extends ApiException) |
HTTP 404 (unknown entity) |
InvalidArgumentException |
invalid argument, e.g. an unknown sport slug |
Transports & Cloudflare (403)
SofaScore sits behind Cloudflare. The bundle ships three transports behind a
single TransportInterface:
http—HttpClientTransport, a plain Symfony HTTP client with a realistic browser header set. Fast, no external dependencies.chrome—ChromeTransport, drives a headless Chromium viachrome-php/chrome(no Node.js). Warms up on the site root to obtain a Cloudflare clearance cookie before calling the API.chain(default) — tries HTTP first and falls back to Chrome on a 403.
The
X-Requested-Withheader is required. SofaScore answers every API request that lacks it with a Cloudflare403 "challenge".HttpClientTransportsends it automatically (the value is not validated — a random per-instance hex token is used to mimic the site's own XHR token), so the defaulthttptransport reaches the API directly, no browser needed.Some IPs are geo/reputation-blocked (e.g. requests originating from Russia, or certain datacenter ranges) and get a
403regardless of headers. Route through a clean exit with thehttp.proxyoption — any SOCKS5/HTTP proxy works:When still blocked, the transport raises
ApiBlockedExceptionrather than returning bogus data, andChainTransportfalls back to the Chrome transport.
Configuration (bundle)
All decorators are opt-in and disabled by default:
Quality
Run the live integration tests (they skip automatically when Cloudflare blocks the current IP):
Development
A bundle is a library — "developing" it means editing code and running the test suite (there is no app server to start). Use the host PHP (8.4+) directly:
…or run everything in Docker (no PHP needed on the host; the container runs as your user, so it leaves no root-owned files):
Using the bundle in a Dockerized app
The bundle is pure PHP; a minimal consumer image needs only PHP + ext-curl:
For the headless-Chrome fallback, also install a Chromium binary and the optional
dependency (composer require chrome-php/chrome, then chrome.binary: chromium):
On a server/cloud the container's IP is a datacenter IP that Cloudflare blocks — route through a proxy (
http.proxy) to a clean residential/mobile exit.
Troubleshooting
- Every request throws
ApiBlockedException(403). Three independent causes:- the
X-Requested-Withheader is missing — it is sent automatically, so this only happens if you overrodehttp.headersand dropped it; - the exit IP is geo/reputation-blocked (e.g. Russia, some datacenter ranges).
Set
http.proxy(andchrome.proxy) to a clean exit; - an older TLS handshake — Cloudflare fingerprints it (seen from inside
containers even with a clean IP + header). The client defaults to TLS 1.3,
which clears it; if you lowered
http.crypto_method, raise it back.
- the
transport: chromefails with "class not found". Install the optional dependency and make sure a Chromium binary is available:composer require chrome-php/chromeand setchrome.binary.rate_limit/cacheenabled but the container errors. Installsymfony/rate-limiter, and pointcache.poolat a PSR-6 pool (cache.appships with FrameworkBundle).- Headless Chrome still gets 403. SofaScore's Cloudflare challenge is hard for
headless automation; use the
httptransport with a clean residential/mobile proxy instead — it only needs theX-Requested-Withheader, which is sent for you.
Credits
- Ported from
tommhe14/sofascore-wrapper(Python, MIT).
License
MIT © Aleksandr Ryzhkov
All versions of sofascore-api-bundle with dependencies
psr/cache Version ^3.0
psr/log Version ^3.0
symfony/config Version ^8.0
symfony/dependency-injection Version ^8.0
symfony/http-client Version ^8.0
symfony/http-client-contracts Version ^3.6
symfony/http-kernel Version ^8.0