chronoarc/comicvine

A PHP wrapper for the Comicvine API

Maintainers

Package info

github.com/fakeheal/comicvine-sdk

pkg:composer/chronoarc/comicvine

Transparency log

Statistics

Installs: 12

Dependents: 0

Suggesters: 0

Stars: 1

Open Issues: 0

1.0.0 2026-07-03 08:07 UTC

This package is auto-updated.

Last update: 2026-07-03 08:16:25 UTC


README

Tests Packagist Version Packagist Downloads

A lightweight, slightly opinionated PHP SDK for the ComicVine API, built on top of Saloon v4. Every documented ComicVine endpoint is covered, with typed DTOs for every response.

๐Ÿ“ฆ Installation

composer require chronoarc/comicvine

Requires PHP 8.2+. Grab an API key at comicvine.gamespot.com/api.

๐Ÿš€ Quick start

use Chronoarc\Comicvine\Comicvine;

$comicvine = new Comicvine('your-api-key');

// Fetch a single resource โ€” /issue/4000-6
$issue = $comicvine->issues()->get(6)->dto();
echo $issue->name;              // "The Lost Race"
echo $issue->volume->name;      // "Chamber of Chills Magazine"
echo $issue->image->originalUrl;

// List resources โ€” /volumes
$volumes = $comicvine->volumes()->all(limit: 50)->dto();
echo $volumes->numberOfTotalResults;
foreach ($volumes->results as $volume) {
    echo $volume->name;
}

// Search everything โ€” /search
$results = $comicvine->search()->query('Batman', resources: ['character'])->dto();

Every resource method returns a Saloon Response; you choose how to consume it:

$response = $comicvine->characters()->get(1699);

$response->dto();     // typed DTO (Character)
$response->json();    // raw decoded payload
$response->status();  // HTTP status code

๐Ÿ—‚ Resources

Every ComicVine resource is exposed through a method on the Comicvine connector:

Connector method Endpoints DTOs
$comicvine->characters() /characters, /character Character, CharactersList
$comicvine->chats() /chats, /chat Chat, ChatsList
$comicvine->concepts() /concepts, /concept Concept, ConceptsList
$comicvine->episodes() /episodes, /episode Episode, EpisodesList
$comicvine->issues() /issues, /issue Issue, IssuesList
$comicvine->locations() /locations, /location Location, LocationsList
$comicvine->movies() /movies, /movie Movie, MoviesList
$comicvine->objects() /objects, /object ComicObject, ObjectsList
$comicvine->origins() /origins, /origin Origin, OriginsList
$comicvine->people() /people, /person Person, PeopleList
$comicvine->powers() /powers, /power Power, PowersList
$comicvine->promos() /promos, /promo Promo, PromosList
$comicvine->publishers() /publishers, /publisher Publisher, PublishersList
$comicvine->search() /search SearchResults
$comicvine->series() /series_list, /series Series, SeriesList
$comicvine->storyArcs() /story_arcs, /story_arc StoryArc, StoryArcsList
$comicvine->teams() /teams, /team Team, TeamsList
$comicvine->videoCategories() /video_categories, /video_category VideoCategory, VideoCategoriesList
$comicvine->videos() /videos, /video Video, VideosList
$comicvine->videoTypes() /video_types, /video_type VideoType, VideoTypesList
$comicvine->volumes() /volumes, /volume Volume, VolumesList
$comicvine->meta() /types Type, TypesList

All list endpoints share the same signature:

$comicvine->issues()->all(
    limit: 100,                          // page size, API max is 100
    offset: 200,                         // zero-based offset for pagination
    fieldList: ['id', 'name', 'volume'], // trim the response to specific fields
    sort: 'cover_date:desc',             // field:asc or field:desc
    filter: ['volume' => 1487],          // field => value pairs
);

All detail endpoints share the same signature:

$comicvine->characters()->get(1699, fieldList: ['id', 'name', 'publisher']);

Pagination

List DTOs extend PaginatedList and expose the full envelope:

$page = $comicvine->issues()->all(limit: 100)->dto();

$page->numberOfTotalResults; // e.g. 1024872
$page->numberOfPageResults;  // e.g. 100
$page->limit;                // 100
$page->offset;               // 0
$page->hasMorePages();       // true

$next = $comicvine->issues()->all(limit: 100, offset: $page->offset + $page->limit)->dto();

Search

/search paginates with a 1-based page parameter and caps limit at 10. Results are mixed-type: each item is hydrated into the DTO matching its resource_type (unknown types are kept as raw arrays):

use Chronoarc\Comicvine\Dto\Character\Character;

$results = $comicvine->search()->query('Batman', resources: ['character', 'volume'], page: 1)->dto();

foreach ($results->results as $result) {
    if ($result instanceof Character) {
        echo $result->realName; // "Bruce Wayne"
    }
}

๐Ÿงฌ DTOs

  • Every DTO is readonly and hydrated via ::fromArray(); you never parse JSON yourself.
  • Fields the API omits โ€” association fields on list responses, or anything excluded via field_list โ€” are null, never missing. null means "not fetched", an empty array means "fetched, and empty".
  • Cross-resource pointers are Reference objects (id, name, apiDetailUrl, siteDetailUrl). Specialised references add context: IssueReference (issueNumber), EpisodeReference (episodeNumber), PersonCredit (role), CountedReference (count, used by volume credits).
  • image fields hydrate into Image with all size renditions (iconUrl โ€ฆ originalUrl).
  • Gender is the Chronoarc\Comicvine\Enum\Gender enum (Other / Male / Female).
  • Quirks are normalised: the story arc count_of_isssue_appearances typo (sic, ComicVine's spelling) maps to countOfIssueAppearances, and a person's death object is flattened to a date string.

โš ๏ธ Error handling

The connector uses Saloon's AlwaysThrowOnErrors, so any non-2xx response throws a Saloon request exception:

use Saloon\Exceptions\Request\RequestException;

try {
    $comicvine->issues()->get(999999999)->dto();
} catch (RequestException $e) {
    $e->getResponse()->status();
}

ComicVine rate-limits API keys to 200 requests per resource per hour. Be a good citizen: request only the fields you need via fieldList, and cache responses where you can.

๐Ÿงฉ Extending

The SDK is deliberately thin so you can reach into any layer:

  • Custom requests โ€” extend Chronoarc\Comicvine\Requests\ListRequest or DetailRequest and send them with $comicvine->send(new MyRequest()).
  • Saloon features โ€” the connector is a regular Saloon connector: middleware, retries, caching plugins and mock clients all work as documented at docs.saloon.dev.
  • Raw responses โ€” skip DTOs entirely with ->json() when you need something the DTOs don't model.

๐Ÿงช Testing

composer test

Tests use Saloon's MockClient with recorded fixtures โ€” no live API calls, no API key needed.

The test suite also demonstrates how to mock the SDK in your own app:

use Chronoarc\Comicvine\Requests\GetIssueRequest;
use Saloon\Http\Faking\MockClient;
use Saloon\Http\Faking\MockResponse;

$mockClient = new MockClient([
    GetIssueRequest::class => MockResponse::fixture('valid_single_issue'),
]);

$comicvine = new Comicvine('fake-key');
$comicvine->withMockClient($mockClient);

๐Ÿค Contributions Welcome

Your feedback and contributions are highly appreciated! Whether it's submitting an issue, suggesting improvements, or adding new features, every bit helps make this SDK better for everyone.

Feel free to fork the repository, make pull requests, or just share ideas! Let's make this SDK awesome together.