Skip to main content

Overview

Heimdinger.lol aggregates League of Legends data from multiple sources to provide comprehensive champion, skin, and asset information. The application implements a robust fallback system to ensure data availability.

Data Source Priority

1

Boris API (Primary)

Custom internal API providing processed champion data
2

Meraki Analytics (Fallback)

Community-maintained CDN with structured LoL data
3

Community Dragon (Assets)

High-quality champion images and ability icons

Boris Static Data Client

The BorisStaticDataClient service is the primary interface for fetching champion and rate data with automatic fallback handling.

Service Location

app/Services/BorisStaticDataClient.php

Configuration

Boris API credentials are configured in config/services.php:
config/services.php:16
'boris' => [
    'url' => env('BORIS_URL', 'https://boris.heimerdinger.lol'),
    'api_key' => env('BORIS_API_KEY'),
],
Set BORIS_URL and BORIS_API_KEY in your .env file to configure the connection.

Data Fetching Methods

Get Champions Data

Retrieves all champion information with automatic fallback:
app/Services/BorisStaticDataClient.php:21
public function getChampions(): array
{
    $payload = $this->fetchWithFallback(
        self::CHAMPIONS_ENDPOINT,
        self::MERAKI_CHAMPIONS_URL,
        fn (mixed $payload): bool => $this->isChampionPayload($payload)
    );
    
    return $this->normalizeChampionPayload($payload);
}
Endpoints:
  • Primary: https://boris.heimerdinger.lol/lolstaticdata/champions.json
  • Fallback: https://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/champions.json

Get Champion Rates

Fetches win rates, pick rates, and ban rates:
app/Services/BorisStaticDataClient.php:32
public function getChampionRates(): array
{
    return $this->fetchWithFallback(
        self::CHAMPION_RATES_ENDPOINT,
        self::MERAKI_CHAMPION_RATES_URL,
        fn (mixed $payload): bool => $this->isChampionRatesPayload($payload)
    );
}
Endpoints:
  • Primary: https://boris.heimerdinger.lol/lolstaticdata/championrates.json
  • Fallback: https://cdn.merakianalytics.com/riot/lol/resources/latest/en-US/championrates.json

Fallback System

The service implements a sophisticated fallback mechanism to ensure data availability:
1

Attempt Boris API

Make request to Boris API with authentication:
app/Services/BorisStaticDataClient.php:109
$response = Http::withHeaders([
    'X-API-Key' => (string) config('services.boris.api_key'),
])->get($this->borisUrl() . $endpoint);
2

Validate Payload

Verify the response matches expected data structure:
app/Services/BorisStaticDataClient.php:141
private function isChampionPayload(mixed $payload): bool
{
    if (! is_array($payload) || $payload === []) {
        return false;
    }
    
    $firstChampion = reset($payload);
    return is_array($firstChampion) && isset($firstChampion['id']);
}
3

Fall Back to Meraki

If Boris fails or returns invalid data, fetch from Meraki:
app/Services/BorisStaticDataClient.php:66
$payload = $this->fetchFromMeraki($merakiUrl);

if ($validator($payload)) {
    Log::warning('Using Meraki static data fallback.');
    return $payload;
}
4

Throw Exception

If both sources fail, throw a descriptive exception:
app/Services/BorisStaticDataClient.php:88
throw new RuntimeException(
    sprintf('Unable to fetch static data from Boris or Meraki for [%s].', $borisEndpoint)
);

Error Logging

All failures are logged with context for debugging:
app/Services/BorisStaticDataClient.php:52
Log::warning('Boris static data request failed.', [
    'source' => 'boris',
    'endpoint' => $borisEndpoint,
    'status' => $exception->getCode() ?: null,
    'message' => $exception->getMessage(),
]);
Monitor your logs for frequent fallbacks to Meraki, which may indicate Boris API issues.

Community Dragon Integration

Community Dragon provides high-quality champion assets directly from game files.

Champion Images

Images are accessed via model accessors:
app/Models/Champion.php:108
public function getChampionSquareImageAttribute(): string
{
    return 'https://raw.communitydragon.org/pbe/plugins/rcp-be-lol-game-data/global/default/v1/champion-icons/'
        . $this->champion_id . '.png';
}

Ability Icons

Each ability has a dedicated accessor:
public function getChampionAbilityIconQAttribute(): string
{
    return 'https://cdn.communitydragon.org/latest/champion/'
        . $this->champion_id . '/ability-icon/q';
}

Usage in Views

<img src="{{ $champion->champion_square_image }}" alt="{{ $champion->name }}">
<img src="{{ $champion->champion_ability_icon_q }}" alt="Q Ability">

Data Normalization

The service normalizes data structures between sources:
app/Services/BorisStaticDataClient.php:176
private function normalizeChampionPayload(array $payload): array
{
    // Meraki returns keyed array, Boris returns list
    if (array_is_list($payload)) {
        return $payload;
    }
    
    return array_values($payload);
}
This ensures consistent data structure regardless of source.

Payload Validation

Champion Payload

Validates champion data structure:
app/Services/BorisStaticDataClient.php:141
private function isChampionPayload(mixed $payload): bool
{
    if (! is_array($payload) || $payload === []) {
        return false;
    }
    
    if (array_is_list($payload)) {
        return isset($payload[0]['id']);
    }
    
    $firstChampion = reset($payload);
    return is_array($firstChampion) && isset($firstChampion['id']);
}

Champion Rates Payload

Validates rate data structure:
app/Services/BorisStaticDataClient.php:156
private function isChampionRatesPayload(mixed $payload): bool
{
    return is_array($payload) && isset($payload['data']) && is_array($payload['data']);
}

Environment Configuration

Required environment variables for data sources:
.env.example:20
BORIS_URL=https://boris.heimerdinger.lol
BORIS_API_KEY=your-api-key-here

# Optional: Riot API for additional features
RGAPI_KEY="RGAPI-00000000-0000-0000-0000-000000000000"
USER_AGENT="Heimerdinger/1.0 (Heimerdinger.lol) PHP"
The Riot API key (RGAPI_KEY) is optional for core functionality but required for live sale rotation data.

Best Practices

Always cache API responses to reduce external requests:
$champions = Cache::remember('champions_data', 3600, function() {
    return app(BorisStaticDataClient::class)->getChampions();
});
Wrap data fetching in try-catch blocks:
try {
    $champions = $client->getChampions();
} catch (RuntimeException $e) {
    Log::error('Failed to fetch champions', ['error' => $e->getMessage()]);
    // Return cached data or show error message
}
Respect API rate limits by implementing appropriate caching and request throttling.

Testing Data Sources

Test the Boris client in Tinker:
php artisan tinker
$client = app(\App\Services\BorisStaticDataClient::class);
$champions = $client->getChampions();
count($champions); // Should return ~160+ champions

Next Steps

Architecture

Understand the overall application architecture

Artisan Commands

Learn about available CLI commands for data management

Build docs developers (and LLMs) love