Skip to main content

Application Architecture

Heimdinger.lol is built on Laravel 11 with a modern, maintainable MVC (Model-View-Controller) architecture. The application follows Laravel best practices and conventions to provide a clean separation of concerns.

Technology Stack

  • Framework: Laravel 11 (PHP 8.2+)
  • Database: MySQL
  • Frontend: Blade Templates, TailwindCSS, Vite
  • Server: Laravel Octane (Swoole/RoadRunner)
  • Caching: File-based cache with support for Redis
  • Monitoring: Laravel Pulse

Directory Structure

The application follows Laravel’s standard directory structure with domain-specific organization:
app/
├── Console/
│   └── Commands/          # Artisan commands
├── Enums/                 # Enumeration classes
├── Helpers/               # Helper functions
├── Http/
│   ├── Controllers/       # Request handlers
│   └── Requests/          # Form request validation
├── Models/                # Eloquent models
├── Policies/              # Authorization policies
├── Providers/             # Service providers
├── Services/              # Business logic services
└── View/
    └── Components/        # Blade components

Key Directories

Controllers handle HTTP requests and coordinate between models and views. Each controller focuses on a specific resource:
  • ChampionController.php - Champion listing and details
  • ChampionSkinController.php - Skin browsing and filtering
  • SummonerIconController.php - Summoner icon catalog
  • PostsController.php - Blog posts from Sheets
  • SaleController.php - Sale rotation data
Eloquent models represent database tables and relationships:
  • Champion - Champion data with skins, lanes, and streamers
  • ChampionSkin - Skin information and chromas
  • ChampionRoles - Champion lane data
  • SummonerIcon - Summoner icon assets
  • SummonerEmote - Emote assets
  • Streamer - Featured streamers per champion
Services encapsulate complex business logic:
  • BorisStaticDataClient - Fetches champion data from Boris API with Meraki Analytics fallback
Blade components for reusable UI elements organized by feature:
  • Champions/ - Champion-related components
  • Skins/ - Skin display components
  • Icons/ - Icon grid components
  • Posts/ - Blog post components
  • home/ - Homepage components

MVC Pattern

Request Flow

1

Route Definition

Routes are defined in routes/web.php using Laravel’s expressive routing:
routes/web.php
Route::get('champion/{champion}', 
  static fn (Champion $champion) => (new ChampionController)->show($champion)
)->name('champions.show');
2

Controller Processing

Controllers receive the request, interact with models, and return views:
app/Http/Controllers/ChampionController.php:28
public function show(Champion $champion)
{
    $threeDaysInSeconds = 60 * 60 * 24 * 3;
    
    $champion = Cache::remember(
        'championShowCache' . $champion->slug, 
        $threeDaysInSeconds, 
        static fn() => $champion->load('streamers', 'skins', 'lanes')
    );
    
    return view('champions.show', ['champion' => $champion]);
}
3

Model Interaction

Eloquent models handle database queries and relationships:
app/Models/Champion.php:82
public function skins(): HasMany
{
    return $this->hasMany(ChampionSkin::class, 'champion_id', 'champion_id');
}

public function lanes(): HasOne
{
    return $this->hasOne(ChampionRoles::class, 'champion_id', 'champion_id');
}
4

View Rendering

Blade templates render the final HTML with the provided data:
<h1>{{ $champion->name }}</h1>
<p>{{ $champion->title }}</p>

Data Flow

Champion Data Pipeline

Caching Strategy

The application uses aggressive caching to minimize database queries:
$champions = Cache::remember('championsListAllCache', $eightHoursInSeconds, 
    static fn() => Champion::orderBy('name')->get()
);
Cache keys are scoped per resource to allow selective invalidation when data updates.

Model Relationships

The application uses Eloquent relationships to model data connections:
Champion
├── HasMany ChampionSkin
   └── HasMany SkinChroma
├── HasOne ChampionRoles
└── HasMany Streamer

Example: Loading Champion with Relationships

app/Http/Controllers/ChampionController.php:32
$champion->load('streamers', 'skins', 'lanes');
This eager loads all related data in a single query to avoid N+1 problems.

Route Model Binding

Laravel’s route model binding automatically resolves models using slugs:
app/Models/Champion.php:77
public function getRouteKeyName(): string
{
    return 'slug';
}
This allows clean URLs like /champion/aatrox instead of /champion/1.

Service Provider Pattern

The AppServiceProvider bootstraps application services:
app/Providers/AppServiceProvider.php:38
public function boot(): void
{
    if (config('app.env') === 'production') {
        URL::forceScheme('https');
    }
    
    $this->bootAuth();
    $this->bootRoute();
}

Key Registrations

  • Authorization: Pulse access control for admin users
  • Rate Limiting: API throttling (60 requests/minute)
  • Route Bindings: Custom model resolution for blog posts via Sheets
When modifying service providers, remember to clear the config cache:
php artisan config:clear
php artisan octane:reload

Next Steps

Data Sources

Learn how the app integrates with Riot API, DataDragon, and Meraki Analytics

Artisan Commands

Explore available CLI commands for maintenance and deployment

Build docs developers (and LLMs) love