Formats
Applications receive structured data in many forms. A service may read its settings from INI, an API may answer in JSON or XML, and a deployment tool may write YAML. Each format has its own library and its own quirks. The data package hides these differences behind one registry. The FormatRegistry class knows how to parse a string into an array and how to dump an array back into a string. Collection uses the registry for its load(), loadFile(), and toString() methods, so the same calls work for every format.
Loading and Dumping
Pass the format name to Collection::from(), load(), or toString(). When you omit the format, JSON is used:
use Windwalker\Data\Collection;
$data = Collection::from('{"flower":"sakura"}', 'json');
echo $data->toString('xml');
echo $data->toString(); // JSONShowcase
The following example reads a database settings file in INI format and writes it out as JSON. The mode option keeps numbers as integers. In the default mode, every INI value is a string:
use Windwalker\Data\Collection;
$ini = <<<INI
[database]
host = "localhost"
port = 3306
debug = false
INI;
$settings = Collection::from(
$ini,
'ini',
[
'process_section' => true,
'mode' => INI_SCANNER_TYPED,
]
);
echo $settings->toString('json');
// {"database":{"host":"localhost","port":3306,"debug":false}}
echo $settings->getDeep('database.port') + 1; // 3307The process_section option turns [database] into a nested array. Without it, the section header is ignored and the keys are top-level.
Built-in Formats
The registry includes these formats:
json: usesjson_encode()andjson_decode(). Options aredepth,options, andassoc.xml: uses SimpleXML. Options arenamefor the root element andnodeNamefor each item. A dumped value is written withnameandtypeattributes:phpecho Collection::from(['a' => 1])->toXml(); // <Data><node name="a" type="integer">1</node></Data>ini: usesparse_ini_string(). Options areprocess_sectionandmode, wheremodeaccepts aINI_SCANNER_*constant.php: dumps an array as areturn [...]statement. Options areheader,return,strict, andclosingtag. It cannot parse, soCollection::from($string, 'php')throws aLogicException.phpSerialize: usesserialize()andunserialize(). The format name is matched case-insensitively, sophpserializealso works.yaml(also available asyml): needssymfony/yaml. Options areinline,indent, andflagsfor dumping, andflagsfor parsing.toml: needsyosymfony/toml. The optionresultAsObjectreturns objects instead of arrays.hjson: needslaktak/hjson.
INFO
The YAML, TOML, and HJSON libraries are not required by windwalker/data. Without the library, calling that format throws an Error such as Class "Symfony\Component\Yaml\Yaml" not found. Install the library in your project before you use the format.
Loading Files
loadFile() reads a file. It chooses the format from the file extension unless you pass one. The extension map turns yml into yaml:
$settings = new Collection();
$settings->loadFile(__DIR__ . '/config/app.yml');
$settings->loadFile(__DIR__ . '/config/app.json', 'json');WARNING
Do not load a .php file from an untrusted location. loadFile() runs the file with require, so the file's code runs in your application.
WARNING
load(), from(), and loadFile() treat a string as a file name when it is the path of an existing file. If the string comes from user input, a request can make your code read any file it is allowed to read. Check the input before you pass it on:
$input = $request->get('data');
// Unsafe: the string is read as a file if it names one.
$data = Collection::from($input, 'json');Pass the input as a parsed value, or validate it so that it cannot name a file.
WARNING
The phpSerialize format calls unserialize(). Unserializing data from an untrusted source can instantiate arbitrary classes. Use it only for data that your application wrote itself. Pass an allowed_classes option to restrict the classes, because the options array is passed directly to unserialize():
$data = Collection::from(
$serialized,
'phpSerialize',
['allowed_classes' => false],
);Prefer json for data that crosses a trust boundary.
Registering Formats
Register a custom format with registerFormat(). It takes a name, a parser, and a dumper. The parser receives the string and the options, and the dumper receives the data and the options. The example below adds a simple comma-separated format:
use Windwalker\Data\Collection;
use Windwalker\Data\Format\FormatRegistry;
$registry = new FormatRegistry();
$registry->registerFormat(
'csv',
fn(string $input): array => explode(',', $input),
fn(array $data, array $options = []): string => implode(',', $data),
);
$flowers = new Collection();
$flowers->setFormatRegistry($registry);
$flowers->load('rose,sakura', 'csv');
echo $flowers->toString('csv'); // rose,sakuraFor a larger handler, implement FormatInterface and pass the object to registerFormat() instead of two callables. The interface needs parse() and dump() methods.
TIP
A Collection creates its own FormatRegistry the first time it needs one. Formats registered on one collection are not visible to another. Create one registry at the start of your application and pass it to every collection with setFormatRegistry().
Aliases and Extension Maps
Use alias() to give a format a second name, and extMap() to map a file extension to a format:
$registry->alias('cfg', 'ini');
$registry->extMap('conf', 'ini');
$settings = (new Collection())->setFormatRegistry($registry);
$settings->loadFile('/etc/app/app.conf');The registry already maps the alias yml to yaml and the extension yml to yaml, so you do not need to register them.