Subdir-safe Apps in PHP – Leitfaden

Version 1.0 · © 2025 Johannes Teitge · teitge.de · Lizenz: GPL-3.0-or-later

Dieses Dokument erklärt, warum in vielen Hostings Pfade/URLs brechen, sobald eine App in einen Unterordner wandert (z. B. /public/test), und zeigt eine robuste, leicht wiederverwendbare Lösung mit Bootstrap-Konstanten und Helper-Funktionen.

1) Problem: Apps im Unterordner

In vielen Projekten werden Pfade/Links „hart“ geschrieben (z. B. /assets/app.css oder Includes wie require '/var/www/...'). Verschiebt man die App in einen Unterordner (z. B. von / nach /test), brechen:

Hosting-Varianten (Plesk, Shared Hosting) liefern unterschiedliche $_SERVER-Werte. Darauf darf man sich nicht blind verlassen.

2) Ziele der Lösung

3) Architektur & Konzepte

3.1 Bootstrap-Konstanten (aus _boot.php)

3.2 Helper-Funktionen

Alle Includes, Links, Asset-Einbindungen nutzen diese Helper – damit ist die App subdir-sicher.

4) Installation (Schritt für Schritt)

  1. Lege die App unter /public/<app> an
    Beispiel: /public/test mit Dateien:
    /public/test/
      _boot.php
      index.php
      config.app.php        ← optional
      includes/theme.php    ← optional
      assets/
        test.css
        js/test.js
        images/test.webp
    
  2. Nutze die gelieferte _boot.php
    Sie setzt die Konstanten und definiert die Helper (siehe API-Anhang unten).
  3. (Optional) config.app.php anlegen
    Nur wenn du etwas übersteuern willst (Subpfad, Asset-Ordner):
    <?php // config.app.php (optional)
    define('APP_URL_BASE', '/test');   // festen Subpfad erzwingen
    define('APP_ASSETS_DIR', 'assets'); // alternativ z. B. 'static'
    
  4. In Templates nur noch Helper nutzen
    Statt /assets/app.css bitte:
    <link rel="stylesheet" href="<?= app_asset_url_v('app.css') ?>">
    <img src="<?= app_asset_url('images/logo.webp') ?>" alt="Logo">
    <script src="<?= app_asset_url_v('js/app.js') ?>" defer></script>
    
  5. Includes immer relativ zu APP_FS_BASE
    require app_path('includes/something.php');

5) Optionale Konfiguration (config.app.php)

Wenn der Hoster/Proxy „komische“ $_SERVER-Werte liefert, kannst du mit APP_URL_BASE hart den Subpfad festlegen – die Helper übernehmen den Rest.

6) Assets & Cache-Busting

Für Assets gibt es drei zentrale Helper:

<link rel="stylesheet" href="<?= app_asset_url_v('test.css') ?>">
<script src="<?= app_asset_url_v('js/app.js') ?>" defer></script>

Wenn die Asset-Checks „nein“ anzeigen, liegen die Dateien meistens nicht unter {APP_FS_BASE}/<ASSET_DIR>/…. Pfad prüfen!

7) Optionales Theme

Wenn vorhanden, wird includes/theme.php geladen. Es stellt eine kleine API bereit:

<?php
declare(strict_types=1);
/**
 * Theme-API (optional)
 * (c) 2025 Johannes Teitge · johannes@teitge.de · teitge.de
 * Lizenz: GPL-3.0-or-later
 *
 * Liefert Branding-Farben, die im Template zu CSS-Variablen werden können.
 */
function theme_load(): array {
  return [
    'brand'  => '#003A77',
    'accent' => '#E2A900',
    'ts'     => date('c'),
  ];
}

Im Template kannst du die Variablen direkt nutzen:

<?php $t = function_exists('theme_load') ? theme_load() : [];
$brand  = $t['brand']  ?? '#003A77';
$accent = $t['accent'] ?? '#E2A900';
?>
<style>
:root{ --brand: <?= $brand ?>; --accent: <?= $accent ?>; }
a{ color: var(--accent) } a:hover{ color: var(--brand) }
</style>

8) Mehrere Instanzen (z. B. /docs/doc1, /docs/doc2)

Lege einfach mehrere Unterordner unter /public/docs an:

/public/docs/doc1/_boot.php, index.php, assets/…
/public/docs/doc2/_boot.php, index.php, assets/…
/public/docs/doc3/_boot.php, index.php, assets/…

Jede Instanz ermittelt ihre eigene APP_FS_BASE und APP_URL_BASE. Die Helper arbeiten in jedem Ordner gleich – keine Codeänderungen nötig.

9) Migration & Checkliste

  1. Alle harten Links /… durch app_url() bzw. app_asset_url() ersetzen
  2. Alle Includes auf app_path() umstellen
  3. JS/CSS mit app_asset_url_v() laden (Cache-Busting)
  4. (Optional) config.app.php ergänzen (Subpfad/Asset-Ordner)
  5. Test mit der mitgelieferten Path-Tester-Seite (zeigt Konstanten & Asset-Checks)

10) Troubleshooting

Assets werden 404 / „Existiert? = nein“

Subpfad wird falsch erkannt

Erzwinge den Pfad in config.app.php:

define('APP_URL_BASE', '/test');

Theme wird nicht geladen

Warum nicht das HTML-<base>-Tag?

Es beeinflusst alle relativen URLs (auch externe), kann Form-Targets & Skript-Ladepfade unerwartet ändern. Der Helper-Ansatz ist lokaler und kontrollierter.

11) FAQ

Funktioniert das hinter einem Reverse Proxy?

Ja. Falls der Proxy den Pfad nicht sauber durchreicht, setze APP_URL_BASE in config.app.php.

Kann ich globales theme.php teilen?

Ja. Du kannst zuerst APP_ROOT/includes/theme.php prüfen und dann app-lokal – so lässt sich Branding zentralisieren.

Ist CLI-Betrieb möglich?

Im CLI fehlen einige $_SERVER-Werte. Nutze in Skripten define('APP_URL_BASE','/test') oder arbeite nur mit FS-Helpern.

12) API-Anhang: Konstanten & Helper

Konstanten

Helper (Signaturen)

h(string $s): string
public_path(string $rel=''): string
app_path(string $rel=''): string
app_url(string $rel=''): string
asset_path(string $rel): string              // Alias auf app_path('assets/…') – historisch
asset_url(string $rel): string               // Alias auf app_url('assets/…') – historisch
asset_url_v(string $rel): string             // wie oben + ?v=mtime

app_asset_path(string $rel): string          // eindeutig App-lokal
app_asset_url(string $rel): string
app_asset_url_v(string $rel): string

Beispiele

// Includes
require app_path('includes/common.php');

// Links
<a href="<?= app_url('admin/') ?>">Admin</a>

// Assets
<link rel="stylesheet" href="<?= app_asset_url_v('main.css') ?>">
<img src="<?= app_asset_url('images/logo.webp') ?>" alt="Logo">
<script src="<?= app_asset_url_v('js/app.js') ?>" defer></script>

Lizenz & Urheber

© 2025 Johannes Teitge · johannes@teitge.de · teitge.de
Dieses Dokument steht unter der GNU GPL v3 oder neuer (GPL-3.0-or-later).