Przejdź do głównej treści
Grafika przedstawia ukryty obrazek

FriendlyConfig – prosty, czytelny format konfiguracji z szybkim cache w PHP

Zdjecie zwiazane z FriendlyConfig  prosty czytelny format konfiguracji z szybkim cache w PHP

W wielu projektach konfiguracja kończy jako:

  • rozbudowany YAML (wrażliwy na wcięcia),
  • zagnieżdżona tablica PHP (mało przyjazna dla nietechnicznych),
  • albo JSON (czytelny, ale mało ergonomiczny do ręcznej edycji).

Ten artykuł pokazuje kompletne rozwiązanie:

FriendlyConfig (.fcfg) – liniowy, maksymalnie czytelny format + szybki loader z cache do natywnego PHP.

1. Założenia formatu

Projektując format, przyjęliśmy:

  • jedna linia = jedna deklaracja
  • brak wcięć zależnych od poziomu
  • brak przecinków
  • brak nawiasów klamrowych
  • czytelność dla osób nietechnicznych
  • łatwy parser

2. Składnia FriendlyConfig

Podstawowy przykład

# Aplikacja
app.name = Moja Aplikacja
app.env = production
app.debug = false
app.port = 8080

# Baza danych
database.host = localhost
database.port = 3306
database.user = root
database.password = "tajne hasło"

# Lista administratorów
admins[] = jan
admins[] = anna
admins[] = piotr

Reguły

1. Klucze z kropkami = struktura

database.host = localhost

[
  'database' => [
    'host' => 'localhost'
  ]
]

2. Listy przez []

admins[] = jan
admins[] = anna

[
  'admins' => ['jan', 'anna']
]

3. Automatyczne typowanie

Wartość Typ
true bool
false bool
null null
123 int
12.5 float
"tekst" string
bez cudzysłowów string

3. Implementacja parsera w PHP

Parser składa się z trzech części:

  1. Parsowanie linii
  2. Rozpoznawanie typów
  3. Budowanie struktury z kluczy kropkowych

Kompletna implementacja

<?php

class FriendlyConfig
{
    public static function load(string $path): array
    {
        $cacheFile = $path . '.cache.php';

        if (self::isCacheValid($path, $cacheFile)) {
            return require $cacheFile;
        }

        $config = self::parseFile($path);
        self::writeCache($cacheFile, $config);

        return $config;
    }

    private static function isCacheValid(string $source, string $cache): bool
    {
        if (!is_file($cache)) {
            return false;
        }

        return filemtime($cache) >= filemtime($source);
    }

    private static function writeCache(string $cacheFile, array $config): void
    {
        $export = var_export($config, true);

        $content = <<<PHP
<?php
// Auto-generated cache file. Do not edit.

return {$export};

PHP;

        file_put_contents($cacheFile, $content, LOCK_EX);
    }

    public static function parseFile(string $path): array
    {
        if (!is_file($path)) {
            throw new InvalidArgumentException("Config file not found: {$path}");
        }

        return self::parseString(file_get_contents($path));
    }

    public static function parseString(string $content): array
    {
        $lines = preg_split('/\r\n|\r|\n/', $content);
        $config = [];

        foreach ($lines as $lineNumber => $line) {
            $line = trim($line);

            if ($line === '' || str_starts_with($line, '#')) {
                continue;
            }

            if (!str_contains($line, '=')) {
                throw new RuntimeException("Invalid syntax on line " . ($lineNumber + 1));
            }

            [$key, $value] = array_map('trim', explode('=', $line, 2));

            $value = self::parseValue($value);
            self::setValue($config, $key, $value);
        }

        return $config;
    }

    private static function parseValue(string $value): mixed
    {
        if (
            (str_starts_with($value, '"') && str_ends_with($value, '"')) ||
            (str_starts_with($value, "'") && str_ends_with($value, "'"))
        ) {
            return substr($value, 1, -1);
        }

        if ($value === 'true') return true;
        if ($value === 'false') return false;
        if ($value === 'null') return null;

        if (ctype_digit($value)) return (int)$value;
        if (is_numeric($value)) return (float)$value;

        return $value;
    }

    private static function setValue(array &$config, string $key, mixed $value): void
    {
        $isArray = str_ends_with($key, '[]');
        $key = $isArray ? substr($key, 0, -2) : $key;

        $segments = explode('.', $key);
        $current = &$config;

        foreach ($segments as $index => $segment) {
            $isLast = $index === array_key_last($segments);

            if ($isLast) {
                if ($isArray) {
                    if (!isset($current[$segment]) || !is_array($current[$segment])) {
                        $current[$segment] = [];
                    }
                    $current[$segment][] = $value;
                } else {
                    $current[$segment] = $value;
                }
            } else {
                if (!isset($current[$segment]) || !is_array($current[$segment])) {
                    $current[$segment] = [];
                }
                $current = &$current[$segment];
            }
        }
    }
}

4. Mechanizm cache – dlaczego to szybkie?

Pierwsze uruchomienie

  1. Parsowanie .fcfg
  2. Generacja pliku:

    config.fcfg.cache.php
  3. Zapis:

    return [ ... ];

Kolejne uruchomienia

  • require cache.php
  • brak parsowania
  • OPcache może dodatkowo zoptymalizować plik

Efekt:

Wydajność praktycznie jak natywny plik konfiguracyjny PHP.

5. Architektura rozwiązania

config.fcfg
       ↓
FriendlyConfig::load()
       ↓
Jeśli cache aktualny → require
Jeśli nie → parse + rebuild cache

To wzorzec stosowany m.in. w systemach kompilujących konfigurację do kodu wykonywalnego.

6. Zalety rozwiązania

Czytelność dla ludzi

Format liniowy jest intuicyjny.

Brak problemów z wcięciami

W przeciwieństwie do YAML.

Brak przecinków i nawiasów

Zero syntaktycznych pułapek.

Wydajność produkcyjna

Po kompilacji to zwykłe return [...].

Prosty parser

~150 linii czystego PHP.

7. Możliwe rozszerzenia (production-grade)

Jeśli projekt urośnie, można dodać:

  • include = base.fcfg
  • ${ENV:DB_HOST}
  • walidację schematu
  • strict mode (zakaz nadpisywania kluczy)
  • immutable Config object
  • preload kompatybilny z OPcache
  • wsparcie wielu plików środowiskowych

8. Podsumowanie

FriendlyConfig to kompromis między:

  • prostotą .env
  • strukturą YAML
  • wydajnością natywnego PHP

Daje:

czytelność dla człowieka
prostotę implementacji
wydajność produkcyjną

23 lutego 2026 7

Kategorie

programowanie

Tagi

php

Dziękujemy!
()

Powiązane wpisy

Grafika przedstawia Automatyczny motyw Bootstrap 53 na bazie pory dnia
4 stycznia 2025 6 min 71

Automatyczny motyw Bootstrap 5.3 na bazie pory dnia

php
Czytaj więcej
Obraz ilustrujacy Long Polling vs Short Polling Porwnanie
21 stycznia 2025 5 min 26

Long Polling vs. Short Polling: Porównanie

php
Czytaj więcej
Obraz ilustrujacy Wyraenia regularne i ich obsuga w PHP oraz JavaScript
1 lutego 2025 5 min 19

Wyrażenia regularne i ich obsługa w PHP oraz JavaScript

php
Czytaj więcej
Wymiana doświadczeń

Masz podobne doświadczenia?

Chętnie poznam Twoją perspektywę i porozmawiam o tym temacie szerzej.

Napisz do mnie

Każda perspektywa może wnieść coś wartościowego do dyskusji.

Twoja prywatność i pliki cookies

  1. Ta strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania – m.in. do poprawnego wyświetlania treści, zapamiętania podstawowych ustawień przeglądarki oraz zapewnienia stabilności serwisu.
  2. Nie stosuję plików cookies w celach marketingowych, reklamowych ani analitycznych.
  3. Strona ma charakter wyłącznie informacyjny i nie zawiera formularzy kontaktowych, rejestracyjnych ani zakupowych, przez które dane mogłyby być przesyłane na serwer.
  4. Nie zbieram danych osobowych podczas zwykłego korzystania z witryny.
  5. Serwis nie korzysta z certyfikatu SSL, jednak ze względu na informacyjny charakter strony nie jest wymagane przesyłanie poufnych danych. Zalecam jednak, aby nigdy nie wpisywać haseł ani danych osobowych na stronach bez szyfrowanego połączenia.
  6. Korzystając z tej strony, wyrażasz zgodę na używanie wyłącznie niezbędnych plików cookies.

Więcej informacji znajdziesz w mojej polityce prywatności.