People don’t seem to be looking for content management systems anymore. There are so many blogging services already that allow them to just write. Personalization and monetization don’t seem to be the main concerns anymore in this era of big data. Or, if monetization is their only concern, they will tend to leave it up to the services they use, sacrificing their freedom to pick a web design that fits their personality.
This project is actually an internal feature of my content management system, Mecha, but I decided to make it a stand-alone project now so that other people can use it too. People seem to have a tendency to look for PHP YAML parsers, far more than their tendency to look for content management systems. So, this project is also my attempt to drive people who need a PHP YAML parser to my content management system project that I’m proud of (which is apparently not very popular since people seem to be more interested in static site generators these days).
Why should you choose my YAML parser over any other similar YAML parser out there?
- mustangostang/spyc consists of one PHP file which is 35.1 KB in size and contains a total of 1186 lines of code since the time of writing. It is out of date (only supports YAML 1.0 and is buggy in various cases) and is still comparatively bigger than my YAML parser.
- symfony/yaml prioritizes reliability and stability for use in large-scale applications. This library contains a lot of dependencies that will make your application overly bloated if your main goal is simply to convert YAML syntax to PHP data.
- yaml requires that your server allows you to install the PHP extension. In terms of conversion speed, it should be faster because it uses C, but it’s not guaranteed to be available on all PHP servers in the world that you can rent, considering that this PHP extension is not bundled with PHP by default.
asdf-1: &asdf 1
asdf-2: *asdf
asdf-3: *asdf
asdf-1: &asdf
a: asdf
b: asdf
c: asdf
asdf-2: *asdf
asdf-3: *asdf
Note
Unlike the official PHP YAML extension, this anchor feature only duplicates the values and does not perform proper memory management by linking the anchored values to their aliases as references, for simplicity 1.
- asdf
- asdf
- asdf
[ asdf, asdf, asdf ]
a: asdf
b: asdf
c: asdf
{ a: asdf, b: asdf, c: asdf }
# This is a comment.
# Case insensitive
false
# Case insensitive
true
# Case insensitive
.INF
# Case insensitive
.NAN
2023-09-25
2023-09-25 20:22:42
# Case insensitive
2023-09-25T20:22:42.025Z
# Case insensitive
2023-09-25T20:22:42+07:00
0.5
.5
# Case insensitive
1.2e+34
12
# Case insensitive
0xC
# Case insensitive
0o14
014
# Case insensitive
null
~
> # Clip (default)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
>4 # Clip and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
>+ # Keep
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
>+4 # Keep and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
>- # Strip
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
>-4 # Strip and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
| # Clip (default)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
|4 # Clip and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
|+ # Keep
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
|+4 # Keep and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
|- # Strip
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
|-4 # Strip and indent with 2 space(s)
asdf asdf asdf asdf
asdf asdf asdf asdf
asdf asdf asdf asdf
"asdf asdf \"asdf\" asdf"
'asdf asdf ''asdf'' asdf'
asdf asdf 'asdf' asdf
These built-in tags are supported:
!!binary
!!bool
!!float
!!int
!!map
!!null
!!seq
!!str
!!timestamp
Users who want to add their own custom tags can define them in the $lot
parameter of the from()
function as a
closure. Note that this parameter is provided as a live reference, so you cannot put an array of tag definitions
directly into it. Instead, you must put it into a temporary variable:
// <https://symfony.com/doc/7.0/reference/formats/yaml.html#symfony-specific-features>
$lot = [
'!php/const' => static function ($value) {
if (is_string($value) && defined($value)) {
return constant($value);
}
return null;
},
'!php/enum' => static function ($value) {
if (!is_string($value)) {
return null;
}
[$a, $b] = explode('::', $value, 2);
if ('->value' === substr($b, -7)) {
return (new ReflectionEnumBackedCase($a, substr($b, 0, -7)))->getBackingValue();
}
return (new ReflectionEnumBackedCase($a, $b))->getValue();
},
'!php/object' => static function ($value) {
return is_string($value) ? unserialize($value) : null;
}
];
$value = from_yaml($value, false, $lot);
// Here, the `$lot` variable will probably contain anchors as well. Anchor data will have a key started with ‘&’.
var_dump($lot, $value);
This converter can be installed using Composer, but it
doesn’t need any other dependencies and just uses Composer’s ability to automatically include files. Those of you who
don’t use Composer should be able to include the from.php
and to.php
files directly into your application without
any problems.
From the command line interface, navigate to your project folder then run this command:
composer require taufik-nurrohman/y-a-m-l
Require the generated auto-loader file in your application:
<?php
use function x\y_a_m_l\from as from_yaml;
use function x\y_a_m_l\to as to_yaml;
require 'vendor/autoload.php';
var_export(from_yaml('asdf: asdf')); // Returns `(object) ['asdf' => 'asdf']`
Require the from.php
and to.php
files in your application:
<?php
use function x\y_a_m_l\from as from_yaml;
use function x\y_a_m_l\to as to_yaml;
require 'from.php';
require 'to.php';
var_export(from_yaml('asdf: asdf')); // Returns `(object) ['asdf' => 'asdf']`
/**
* Convert YAML string to PHP data.
*
* @param null|string $value Your YAML string.
* @param bool $array If this option is set to `true`, PHP object will becomes associative array.
* @param array $lot Currently used to store anchor(s) and custom tag(s)
* @return mixed
*/
from(?string $value, bool $array = false, array &$lot = []): mixed;
/**
* Convert PHP data to YAML string.
*
* @param mixed $value Your PHP data.
* @param bool|int|string $dent Specify the indent size or character(s).
* @return null|string
*/
to(mixed $value, bool|int|string $dent = true): ?string;
Clone this repository into the root of your web server that supports PHP and then you can open the test/from.php
and
test/to.php
file with your browser to see the result and the performance of this converter in various cases.
Your YAML content is represented as variable $value
. If you modify the content before the function from_yaml()
is
called, it means that you modify the YAML content before it is converted. If you modify the content after the function
from_yaml()
is called, it means that you modify the results of the YAML conversion.
To make from_yaml()
and to_yaml()
functions reusable globally, use this method:
<?php
require 'from.php';
require 'to.php';
// Or, if you are using Composer…
// require 'vendor/autoload.php';
function from_yaml(...$v) {
return x\y_a_m_l\from(...$v);
}
function to_yaml(...$v) {
return x\y_a_m_l\to(...$v);
}
This converter does not support multiple document feature in one YAML file, but can be supported with a little effort:
// Ensure line break after `---` and `...`
$value = preg_replace('/^(-{3}|[.]{3})\s+/m', '$1' . "\n", $value);
// Remove `---\n` prefix if any
if (0 === strpos($value, "---\n")) {
$value = substr($value, 4);
}
$values = [];
foreach (explode("\n---\n", $value . "\n") as $v) {
// Remove everything after `...`
$v = explode("\n...\n", $v . "\n", 2)[0];
$values[] = from_yaml($v);
}
var_dump($values);
There are several ways to declare variables in YAML, and all of them are not standard. The most common are variables
with a format like {{ var }}
. To add a variable feature, you need to convert the variable to a YAML value before
parsing the data:
$variables = [
'var_1' => 'asdf',
'var_2' => true,
'var_3' => 1,
'var_4' => 1.5
];
if (false !== strpos($value, '{{')) {
$value = preg_replace_callback('/"\{\{\s*[a-z]\w*\s*\}\}"|\'\{\{\s*[a-z]\w*\s*\}\}\'|\{\{\s*[a-z]\w*\s*\}\}/', static function ($m) use ($variables) {
$variable = $m[0];
// `"{{ var }}"`
if ('"' === $variable[0] && '"' === substr($variable, -1)) {
$variable = substr($variable, 1, -1);
}
// `'{{ var }}'`
if ("'" === $variable[0] && "'" === substr($variable, -1)) {
$variable = substr($variable, 1, -1);
}
// Trim variable from `{{` and `}}`
$variable = trim(substr($variable, 2, -2));
// Get the variable value if available, default to `null`
$variable = $variables[$variable] ?? null;
// Return the variable value as YAML string
return to_yaml($variable);
}, $value);
}
$value = from_yaml($value);
var_dump($value);
This library is licensed under the MIT License. Please consider donating 💰 if you benefit financially from this library.