PHPackages                             seatplus/esi-schema - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. seatplus/esi-schema

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

seatplus/esi-schema
===================

Generated ESI response DTOs, versioned independently by ESI compatibility date. Zero runtime dependencies.

2.0.0(2mo ago)0804[23 PRs](https://github.com/seatplus/esi-schema/pulls)1MITPHPPHP ^8.3CI passing

Since May 13Pushed 3w agoCompare

[ Source](https://github.com/seatplus/esi-schema)[ Packagist](https://packagist.org/packages/seatplus/esi-schema)[ RSS](/packages/seatplus-esi-schema/feed)WikiDiscussions main Synced 3w ago

READMEChangelog (5)Dependencies (12)Versions (35)Used By (1)

seatplus/esi-schema
===================

[](#seatplusesi-schema)

**Typed ESI schema for PHP.** Every EVE Online ESI endpoint has its own generated class with typed pre-call metadata and a typed call method — no magic strings, no `array` guesswork.

Generated from the ESI OpenAPI spec (`compatibility_date=2026-05-19`). Zero runtime dependencies.

---

Installation
------------

[](#installation)

```
composer require seatplus/esi-schema
```

**Requirements:** PHP 8.3+

---

Quick Start
-----------

[](#quick-start)

Two call styles are available. Choose based on context.

### Option A — Direct static call (recommended for jobs and services)

[](#option-a--direct-static-call-recommended-for-jobs-and-services)

Each ESI endpoint has its own generated class under `src/Resources/{Tag}/`:

```
use Seatplus\EsiSchema\Resources\Assets\GetCharactersCharacterIdAssets;
use Seatplus\EsiSchema\Resources\Market\GetMarketsPrices;

// 1. Pre-call introspection — no transport needed
$meta = GetCharactersCharacterIdAssets::meta();
$meta->requiredScope;        // 'esi-assets.read_assets.v1'
$meta->rateLimitGroup;       // 'char-asset'
$meta->rateLimitMaxTokens;   // 1800
$meta->rateLimitWindow;      // '15m'
$meta->cacheAge;             // 3600
$meta->requiredRoles;        // []  (['Director'] for corp endpoints)
$meta->usesCursor;           // false

// Or access constants directly — no allocation at all
GetCharactersCharacterIdAssets::REQUIRED_SCOPE;        // 'esi-assets.read_assets.v1'
GetCharactersCharacterIdAssets::RATE_LIMIT_GROUP;      // 'char-asset'
GetCharactersCharacterIdAssets::RATE_LIMIT_MAX_TOKENS; // 1800
GetCharactersCharacterIdAssets::CACHE_AGE;             // 3600

// Check a token before dispatching a job
if ($meta->requiredScope !== null && !in_array($meta->requiredScope, $token->scopes, true)) {
    throw new InsufficientScopeException($meta->requiredScope);
}

// 2. Typed call — inject any EsiTransportInterface
$result = GetCharactersCharacterIdAssets::execute($transport, characterId: 12345, page: 1);
foreach ($result->data as $item) {
    echo $item->type_id;    // typed int
    echo $item->quantity;   // typed int
}
echo $result->pages;        // total pages from X-Pages header
echo $result->isCachedLoad; // true when served from RFC 7234 cache

// Public endpoint — REQUIRED_SCOPE is null
$prices = GetMarketsPrices::execute($transport);
GetMarketsPrices::REQUIRED_SCOPE; // null
```

### Option B — Fluent API (convenient for interactive use and esi-client)

[](#option-b--fluent-api-convenient-for-interactive-use-and-esi-client)

A generated `{Tag}Resource` wrapper class exists for every tag. Inject the transport once and call methods fluently:

```
use Seatplus\EsiSchema\Resources\AssetsResource;
use Seatplus\EsiSchema\Resources\CharacterResource;

// Construct with any EsiTransportInterface
$assets     = new AssetsResource($transport);
$characters = new CharacterResource($transport);

// Same parameters, same return types as the static API
$result = $assets->getCharactersCharacterIdAssets(characterId: 12345, page: 1);
$dto    = $characters->getCharactersCharacterId(characterId: 12345);

// With esi-client (EsiClient implements EsiTransportInterface):
$result = $esiClient->withToken($accessToken)->assets()->getCharactersCharacterIdAssets(12345, page: 1);
```

Each `{Tag}Resource` method is a thin wrapper — it simply calls `{OperationClass}::execute($this->transport, ...)`. Pre-call metadata and typed constants remain on the per-route class.

### Namespace table

[](#namespace-table)

Resource classes are grouped by ESI tag into 33 subfolders, each with a corresponding tag-group wrapper:

SubfolderExample classTag wrapper`Resources\Alliance``GetAlliancesAllianceId``AllianceResource``Resources\Assets``GetCharactersCharacterIdAssets``AssetsResource``Resources\Character``GetCharactersCharacterId``CharacterResource``Resources\Corporation``GetCorporationsCorporationId``CorporationResource``Resources\FactionWarfare``GetFwStats``FactionWarfareResource``Resources\Market``GetMarketsPrices``MarketResource``Resources\Universe``GetUniverseTypesTypeId``UniverseResource``Resources\Wallet``GetCharactersCharacterIdWallet``WalletResource``Resources\Skills``GetCharactersCharacterIdSkills``SkillsResource`… (33 total)Per-route classes: `Seatplus\EsiSchema\Resources\{Tag}\{PascalCaseOperationId}`
Tag wrappers: `Seatplus\EsiSchema\Resources\{Tag}Resource` (e.g. `Seatplus\EsiSchema\Resources\AssetsResource`)

### eveapi integration pattern

[](#eveapi-integration-pattern)

The intended use in queue jobs:

```
use Seatplus\EsiSchema\Resources\Assets\GetCharactersCharacterIdAssets;

class CharacterAssetJob extends EsiJob
{
    public function __construct(
        public readonly int $characterId,
        public readonly RefreshToken $token,
    ) {}

    // EsiJob base reads this to get scope, rate-limit group, etc.
    protected const string OPERATION = GetCharactersCharacterIdAssets::class;

    protected function executeJob(EsiTransportInterface $transport): void
    {
        $result = GetCharactersCharacterIdAssets::execute(
            $transport, $this->characterId
        );
        if ($result->isCachedLoad) return;
        Asset::upsert(/* ... */);
    }
}
```

---

Implementing a Transport
------------------------

[](#implementing-a-transport)

All resource classes depend only on `EsiTransportInterface`. Implement it to connect any HTTP client:

```
use Seatplus\EsiSchema\Contracts\EsiTransportInterface;
use Seatplus\EsiSchema\Contracts\EsiRawResponse;

class MyTransport implements EsiTransportInterface
{
    public function invoke(
        string $method,
        string $path,
        array $pathValues = [],
        array $queryParams = [],
        array $requestBody = [],
    ): EsiRawResponse {
        // ... perform the HTTP request, handle caching, auth etc.
        return new EsiRawResponse(
            data: $responseBody,          // decoded JSON (mixed)
            isCachedLoad: $wasCached,     // bool
            pages: $xPagesHeader ?? 1,    // int
            rateLimitRemaining: $remaining,
            rateLimitUsed: $used,
            retryAfter: $retryAfter,      // null unless 429
        );
    }
}
```

The reference implementation is [seatplus/esi-client](https://github.com/seatplus/esi-client), which handles OAuth, RFC 7234 caching, error-limit tracking, and retry logic.

---

Architecture
------------

[](#architecture)

```
EsiTransportInterface              # Contract: any transport implements this
       │
       ├── Resources/{Tag}Resource  # 33 generated tag wrappers — fluent API entry points
       │    └── AssetsResource
       │         ├── __construct(EsiTransportInterface $transport)
       │         └── getCharactersCharacterIdAssets($id, $page)  # delegates to ↓
       │
       └── Resources/{Tag}/         # 208 generated classes — one per ESI endpoint
            └── Assets/
                 └── GetCharactersCharacterIdAssets
                      ├── REQUIRED_SCOPE = 'esi-assets.read_assets.v1'  (typed const)
                      ├── RATE_LIMIT_GROUP = 'char-asset'                (typed const)
                      ├── CACHE_AGE = 3600                               (typed const)
                      ├── static meta(): OperationMeta   # pre-call typed metadata DTO
                      └── static execute($transport, ...): EsiResult     # typed call

```

**Key contracts:**

Class / InterfacePurpose`EsiOperationInterface`Contract for resource classes: `static meta(): OperationMeta``EsiTransportInterface`Contract for HTTP transport: `invoke() → EsiRawResponse``EsiRawResponse`Raw transport response: data + HTTP metadata + rate-limit state`EsiCursor`Cursor pagination tokens (`$before`, `$after`)`OperationMeta`Typed pre-call DTO: 7 readonly properties (no methods)`AbstractEsiDto`Base DTO for single-object responses: `$isCachedLoad`, `$pages``EsiResult`Typed wrapper for array/paginated endpoints`{Tag}Resource`Fluent wrapper — stores transport, methods delegate to per-route statics---

Design Decisions
----------------

[](#design-decisions)

### 1. Static resource classes — one class per ESI endpoint

[](#1-static-resource-classes--one-class-per-esi-endpoint)

Each ESI endpoint is represented as a **pure static class** (`final class`) rather than an instance method on a tag-grouped resource. This means:

- **Zero allocation**: `GetCharactersCharacterIdAssets::meta()` is a direct static call — no `new`, no DI.
- **PHPStan traces the return type directly**: `::execute()` returns `EsiResult`, fully known at static analysis time.
- **The class name is the identifier**: `OPERATION = GetCharactersCharacterIdAssets::class` is a typed constant reference — no magic strings needed in jobs.

### 2. Typed public constants for metadata

[](#2-typed-public-constants-for-metadata)

Each generated class exposes 7 individually typed `public const` declarations:

```
public const ?string REQUIRED_SCOPE        = 'esi-assets.read_assets.v1';
public const ?string RATE_LIMIT_GROUP      = 'char-asset';
public const ?int    RATE_LIMIT_MAX_TOKENS = 1800;
public const ?string RATE_LIMIT_WINDOW     = '15m';
public const ?int    CACHE_AGE             = 3600;
public const array   REQUIRED_ROLES        = [];
public const bool    USES_CURSOR           = false;
```

`meta()` simply wraps these into `new OperationMeta(...)`. The constants are also directly accessible without any method call or allocation.

### 3. `OperationMeta` as a pure typed DTO

[](#3-operationmeta-as-a-pure-typed-dto)

`OperationMeta` is a `final readonly class` with **only typed constructor properties** — no methods. Access its values as `$meta->requiredScope`, `$meta->cacheAge`, etc.

Token validation logic is **not** in this library — `tokenSatisfies()` was removed. Scope checks belong in eveapi, where the token models live.

### 4. Tag-based subfolders

[](#4-tag-based-subfolders)

The 208 resource classes live in `src/Resources/{Tag}/` (33 subfolders), matching ESI's tag taxonomy:

- **Group imports** are idiomatic: `use Seatplus\EsiSchema\Resources\Assets\{GetCharactersCharacterIdAssets, GetCorporationsCorporationIdAssets}`.
- Tag names with spaces become PascalCase: `Faction Warfare` → `FactionWarfare`.

### 5. Zero runtime dependencies

[](#5-zero-runtime-dependencies)

`composer.json` has **no `require` entries** (only `require-dev` for `symfony/yaml` used by the generator). The published library is pure PHP 8.3.

### 6. `EsiTransportInterface` as the sole injection boundary

[](#6-esitransportinterface-as-the-sole-injection-boundary)

All network I/O is delegated to a single `invoke()` method. The library knows nothing about Guzzle, cURL, OAuth tokens, or HTTP caching. Tests mock this interface — no network required.

### 7. Versioning tied to ESI compatibility\_date

[](#7-versioning-tied-to-esi-compatibility_date)

Branch / MajorESI compatibility\_dateComposerNotes`main``2026-05-19``dev-main`Always the latest — updated automatically`2.x``2026-05-19``^2.0`Stable freeze`1.x``2025-12-16``^1.0`Stable freezeWhen CCP introduces a new breaking compatibility date, the action regenerates on a new `N.x` branch, opens a `[REVIEW ONLY]` PR to `main`, and after merging both `main` and `N.x` point to the same commit.

---

Versioning
----------

[](#versioning)

Branch / MajorESI Compatibility DateComposer constraint`main``2026-05-19``dev-main``2.x``2026-05-19``^2.0``1.x``2025-12-16``^1.0`---

Regenerating
------------

[](#regenerating)

```
php bin/generate.php    # fetches latest spec, regenerates all DTOs + Resources
vendor/bin/pint         # auto-format generated output (run after generate if needed)
```

The generator reads the live OAS3 spec from `https://esi.evetech.net/meta/openapi.yaml?compatibility_date=2026-05-19`.

It emits:

- `src/Responses/*.php` — ~218 typed DTO classes (one per ESI schema object)
- `src/Resources/{Tag}/*.php` — 208 per-route static classes grouped by ESI tag (33 subfolders)
- `src/Resources/{Tag}Resource.php` — 33 flat tag-group wrapper classes for the fluent API

**Do not manually edit generated files.** Changes are overwritten on next regeneration. To change generated output, edit `bin/generate.php`.

---

Testing
-------

[](#testing)

```
composer test               # lint + types + type-coverage + unit
composer test:unit          # Pest tests only
composer test:types         # PHPStan static analysis
composer test:type-coverage # 100% type coverage check
composer lint               # Pint auto-format (modifies files)
```

---

Contributing
------------

[](#contributing)

See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed design rationale.

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance92

Actively maintained with recent releases

Popularity18

Limited adoption so far

Community10

Small or concentrated contributor base

Maturity60

Established project with proven stability

 Bus Factor1

Top contributor holds 85.7% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~1 days

Total

30

Last Release

23d ago

Major Versions

19.x-dev → 20.x-dev2026-06-27

20.x-dev → 21.x-dev2026-06-28

21.x-dev → 22.x-dev2026-06-29

22.x-dev → 23.x-dev2026-06-30

23.x-dev → 24.x-dev2026-07-01

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/6583519?v=4)[Herpaderp Aldent](/maintainers/herpaderpaldent)[@herpaderpaldent](https://github.com/herpaderpaldent)

---

Top Contributors

[![herpaderpaldent](https://avatars.githubusercontent.com/u/6583519?v=4)](https://github.com/herpaderpaldent "herpaderpaldent (6 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/seatplus-esi-schema/health.svg)

```
[![Health](https://phpackages.com/badges/seatplus-esi-schema/health.svg)](https://phpackages.com/packages/seatplus-esi-schema)
```

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
