dariusiii/tv-maze-php-api
dariusiii/tv-maze-php-api is a lightweight PHP client for TVmaze, providing easy access to TV show schedules, episode guides, cast and crew details, and search endpoints. Designed for Laravel and plain PHP projects, it wraps API requests in a simple, readable interface.
composer require dariusiii/tv-maze-php-api:^2.0
The current line is 2.0.x (latest: 2.0.6, a patch). If you were pinned to ^1.0 or an old 2.0.x, bump the constraint — nothing in the 2.0.5 → 2.0.6 diff changes the public API.curl extension is present. The wrapper talks HTTP via raw cURL, not Guzzle/PSR-18 (2.0.6 is literally "remove deprecated curl_close() call"). Run php -m | grep curl; on minimal Docker images this is the usual silent failure.composer.json to confirm the autoload namespace and the entry-point class. Wrapper repos frequently rename the namespace from the repo slug, so trust autoload.psr-4 over the URL.src/ directory and README for the client constructor. TVMaze's public API needs no key/token, so the constructor is usually empty or takes a base URL. Because it's cURL-based, don't expect an injectable HTTP client argument.use DariusIII\TvMaze\Client; // verify from composer.json
$client = new Client();
$hits = $client->search('Severance'); // array of {score, show}
$show = $client->show($hits[0]['show']['id']);
echo $show['name'] . ' — ' . ($show['image']['medium'] ?? 'no image');
// app/Providers/AppServiceProvider.php
$this->app->singleton(Client::class, fn () => new Client());
If you want a TvMaze:: facade, add a small class that proxies to the singleton via the container.Repository + cache layer (recommended default). Don't call the wrapper from controllers or Blade templates. Wrap it:
class ShowRepository
{
public function __construct(private Client $client) {}
public function find(int $id): ?array
{
return Cache::remember("tvmaze.show.{$id}", now()->addDay(),
fn () => $this->client->show($id));
}
public function search(string $q): array
{
return Cache::remember("tvmaze.search.".md5($q), now()->addMinutes(10),
fn () => $this->client->search($q));
}
}
Show metadata (name, premiered, network, image) is essentially immutable — 24h TTLs are safe. Episodes and schedule data change, so use shorter TTLs (1–6h).
Wrap behind your own interface, not just a repository. Since the package owns its transport (cURL), you can't swap in Http::fake() for tests. Define interface TvMazeGateway { public function show(int $id): ?array; public function search(string $q): array; }, bind the package Client to it in the container, and let tests bind a stub. This is the single highest-leverage change for testability.
$this->app->bind(TvMazeGateway::class, fn () => new TvMazeClientAdapter(app(Client::class)));
Separate the three concerns. TVMaze's endpoints map naturally to three fetch groups:
show, search, singlesearch (cache long)episodes, cast, crew (cache medium)schedule?date=YYYY-MM-DD&country=US (cache an hour, or prefetch via a scheduled command)Queue bulk work. Importing every episode for 500 shows will blow the rate limit. Use a job per show with ->delay() or chunking, and let the cache absorb repeat reads:
Show::pluck('tvmaze_id')->each(fn ($id) =>
SyncShowEpisodes::dispatch($id)->onQueue('tvmaze'));
Scheduled sync. TVMaze exposes a daily schedule endpoint — put it on a daily() or everySixHours() command in routes/console.php and write results into your own episodes table so the UI never waits on a remote call.
Search autocomplete. Front-end debounce → hit a Laravel endpoint that reads the 10‑minute cache → map the {score, show} shape down to {id, name, image.url, premiered} before returning JSON. Don't ship the raw TVMaze payload to the browser; it's fat and contains fields you shouldn't expose.
Normalize images. TVMaze returns image.medium (210×295) and image.original. Store both, render medium for lists and original for detail pages. All fields are nullable — a show without an image is normal.
DTO layer if the package returns arrays. If the wrapper hands back raw arrays, add a ShowData value object with fromArray() so your Blade/Inertia views aren't spelunking $show['_links']['self']['href'].
RetryMiddleware and Laravel's Http::retry() are not available here — plan for that rather than discovering it mid-incident.usleep/manual backoff in your adapter, or you'll get 429s that look like empty results if the package swallows exceptions.search is fuzzy, singlesearch is exact. Use singlesearch when you want one canonical match; it returns a 404 when nothing matches rather than an empty array, so catch that specifically.null for a miss and let callers decide — don't let a HTTP exception 500 a detail page.rating.average, network, image, summary, externals.imdb are all frequently null. Assume every field beyond id, name, url and _links can be missing.summary contains HTML. It arrives as an HTML string with <p> tags. Escape on output or strip via strip_tags() before storing/displaying.YYYY-MM-DD; passing a Carbon may or may not be coerced depending on the signature — cast with ->toDateString() explicitly./schedule is queried by broadcast date in the target country, not UTC. Store the country in config alongside base URL and timeout.config/tvmaze.php; write your own config array (services.tvmaze.base_uri, timeout, user_agent) and inject it into the singleton. Set a User-Agent — polite clients get throttled less.curl_close() removal (2.0.6) is a non-event for you. It's been a no-op since PHP 8.0, so behavior is identical on modern PHP; it just makes the package 8.5-deprecation-clean. Don't hunt for a migration guide — there isn't one. If you're somehow still on PHP 7.x, handle release is now left to refcounting/GC, which is harmless for short-lived request-scoped clients.composer dump-autoload -o and inspect the generated autoload files if the import IDE suggestion doesn't appear.Client and adds caching/retry/logging around it. Keeps composer update safe.2.0.x line is patch-only and low-risk, but with essentially no dependents the surface can still shift in a minor. Use "dariusiii/tv-maze-php-api": "^2.0" and commit the lock file.Log::debug('tvmaze', ['method' => __FUNCTION__, 'status' => $status, 'body' => $raw]). TVMaze responses are small enough to log whole during development.How can I help you explore Laravel packages today?