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

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

Obraz ilustrujacy 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 3

Kategorie

programowanie

Tagi

php

Dziękujemy!
()

Powiązane wpisy


Informacja o cookies

Moja strona internetowa wykorzystuje wyłącznie niezbędne pliki cookies, które są wymagane do jej prawidłowego działania. Nie używam ciasteczek w celach marketingowych ani analitycznych. Korzystając z mojej strony, wyrażasz zgodę na stosowanie tych plików. Możesz dowiedzieć się więcej w mojej polityce prywatności.