NEON pro PHP

Balíček nette/neon čte a zapisuje NEON v PHP. Díky němu:

  • dekódujete konfigurační soubor do hodnot PHP a hodnoty zakódujete zpět do NEONu
  • upravíte soubor tak, že každý komentář a mezera, na které nesáhnete, zůstanou na svém místě
  • dáte některým hodnotám vlastní význam, třeba konstantám nebo parametrům
  • zkontrolujete soubory pomocí neon-lint v editoru, v CI nebo po AI agentovi

Instalace

Knihovnu stáhnete a nainstalujete pomocí nástroje Composer:

composer require nette/neon

Vyžaduje PHP 8.4 až 8.6 s rozšířeními json a mbstring.

Dekódování

Dejme tomu, že vaše aplikace má nastavení v souboru config.neon:

# database connection
database:
	host: localhost
	port: 3306

services:
	- App\Model\ArticleFacade

Statická třída Nette\Neon\Neon ho načte do obyčejného pole PHP:

use Nette\Neon\Neon;

$config = Neon::decodeFile('config.neon');
// ['database' => ['host' => 'localhost', 'port' => 3306], 'services' => ['App\Model\ArticleFacade']]

Metoda decode() dělá totéž s řetězcem:

$value = Neon::decode('hello: world'); // ['hello' => 'world']

Hodnoty formátu se převedou na tyto hodnoty PHP:

NEON PHP
null, true, yes … null, bool
celé číslo int; nad PHP_INT_MAX řetězec string z jeho číslic
desetinné číslo float
řetězec string
datum DateTimeImmutable
entita, řetěz Nette\Neon\Entity
sekvence, mapování array

PHP ukládá sekvence i mapování do polí, takže prázdné [] i prázdné {} se stanou []. Datum bez časové zóny dostane výchozí časovou zónu PHP.

Kódování

Metoda encode() zapíše hodnotu PHP jako NEON. Ve výchozím stavu zapíše vše na jeden řádek; pokud jako druhý argument předáte true, dostanete bloky:

Neon::encode(['roles' => ['guest', 'admin']]);
// '{roles: [guest, admin]}'

Neon::encode(['roles' => ['guest', 'admin']], true);
// "roles:\n\t- guest\n\t- admin\n\n"

Místo true můžete předat funkci, která rozhodne u každého pole zvlášť. Dostane pole a seznam klíčů, které k němu vedou, a pro blok vrátí true. Tahle zapíše bloky jen v první úrovni:

Neon::encode($config, fn($value, $path) => !$path);
// "database: {host: localhost, port: 3306}\nservices: [App\\Model\\ArticleFacade]\n"

Třetí argument určuje odsazení, výchozí je tabulátor. Řetězec dostane uvozovky, jen když je potřebuje, a víceřádkový řetězec se zapíše jako víceřádkový řetězec.

Některé hodnoty jako NEON bez ztráty zapsat nejde: řetězec, který není platné UTF-8, INF a NAN, resource, closure nebo struktura, která obsahuje sama sebe. Pro ně encode() vyhodí Nette\Neon\Exception.

Chyby

Když vstup není platný NEON, decode() a decodeFile() vyhodí Nette\Neon\Exception s pozicí problému:

try {
	$config = Neon::decode("database:\n\thost: localhost\n\thost: db");
} catch (Nette\Neon\Exception $e) {
	echo $e->getMessage();     // Duplicated key 'host' on line 3 at column 2
	echo $e->getDescription(); // Duplicated key 'host'
}

Sloupec se počítá ve znacích, ne v bajtech. Když chybu zobrazujete po svém, třeba u pole formuláře, metoda getDescription() vrátí hlášku bez pozice.

Vlastní hodnoty: Evaluator

Neon::decode() převádí syntaktický strom na hodnoty pomocí třídy Nette\Neon\Evaluator. Její potomek může některým hodnotám dát vlastní význam. Tento převádí názvy konstant PHP na jejich hodnoty:

use Nette\Neon\Evaluator;
use Nette\Neon\Neon;
use Nette\Neon\Nodes\LiteralNode;
use Nette\Neon\Place;

final class ConstantEvaluator extends Evaluator
{
	protected function evaluateLiteral(LiteralNode $node, Place $place): mixed
	{
		$value = parent::evaluateLiteral($node, $place);
		return $place === Place::Value && is_string($value) && defined($value)
			? constant($value)
			: $value;
	}
}

$document = Neon::parse('errorLevel: E_ALL');
$config = new ConstantEvaluator()->evaluate($document->value);
// ['errorLevel' => 30719]

Metody, které můžete přepsat, jsou evaluateLiteral(), evaluateString(), evaluateEntity(), evaluateChain() a evaluateItem(). Place říká, kde hodnota stojí: jako hodnota, jako klíč, jako jméno entity nebo jako článek řetězu. Díky tomu příklad výše nechá na pokoji klíč, který se jmenuje jako konstanta. Takto čte svou konfiguraci DI kontejner Nette.

Kontrola souborů: neon-lint

Balíček obsahuje příkaz neon-lint. Předáte mu soubory nebo adresáře a on vypíše jeden řádek pro každý problém:

vendor/bin/neon-lint config/
config/local.neon:3:2: Duplicated key 'user'

Když jsou všechny soubory platné, nevypíše nic. V adresáři hledá soubory *.neon a vynechá vendor, node_modules a skryté adresáře, - čte standardní vstup. Jeho chování mění tyto volby:

Volba Účinek
--check ověří navíc, že se soubor načte zpět bajt po bajtu
--json vypíše nálezy jako JSON, pro nástroje
--quiet nevypíše nic, jen nastaví návratový kód
--verbose vypíše počet souborů, když jsou všechny platné

Návratový kód je 0, když jsou všechny soubory platné, 1 při nálezu a 2 při chybném volání. Příkaz, jeho výstup i návratové kódy jsou stejné v knihovnách pro JavaScript a Python, takže cokoliv, co ho spouští, funguje se všemi.

Proto se hodí i pro AI agenty: když ho agent spustí po každé změně souboru .neon, dozví se o rozbitém souboru hned a opraví ho dřív, než pokračuje. Pluginy Nette pro AI agenty to dělají za vás.

Co dál