- URL: https://www.laruence.com/en/2015/06/12/3051.html
- Please include attribution when republishing.
This project isn't really a new idea. It started life as a little tool I whipped up on the side during my first optimization project after joining Weibo — back then it was called Weibo_Conf. But because Weibo_Conf belonged to the Weibo extension and bundled a few other features that were purpose-built for Weibo, it wasn't suitable for direct open-sourcing.
Along with the release of PHP 7 came support for a lot of new persistent types, such as IS_IMMUTABLE_ARRAY, so I redeveloped it as Yaconf under PHP 7 and released it open source, for everyone to use.
The project
The code is already up on GitHub: Yaconf sources. Issues are very welcome.
You can also install it directly from PECL, and download Windows DLLs there: Yaconf package.
Introduction
First, what is this thing for?
- I've seen a lot of projects that use PHP files for configuration. A config directory might hold a dozen — or even dozens — of .php config files, all stuffed with various arrays, and some even go so far as to put dictionary files (say, a Chinese/English lookup table) into the config as well. This makes parsing the config a real performance drain (yes, opcache helps, but there's still an execution step in practice).
- Beyond PHP, there are also projects using json or yaml. What they all have in common is fairly poor readability. On top of that, they all still have to be parsed at runtime.
- The config directory usually lives alongside the code. First, that's a security risk (configs often contain sensitive information). Second, if config and code belong to the same project, then a config change has to go through the whole code-release pipeline.
- Some resource configs — for example, mysql/memcache connection info — ought to be transparent to developers, with ops owning them directly. But once they sit inside the code, an ops-initiated change also requires developer involvement to modify the config file and ship it.
So Yaconf is a tool born precisely to solve these problems.
- It uses a separate config directory (specified via yaconf.directory), not living alongside the code.
- At PHP startup, it processes all the configs it needs to, and those configs then become memory-resident, living and dying with PHP itself. This avoids parsing config files on every request.
- All config content is immutable. This lets it take advantage of fork's copy-on-write to keep memory usage down, and reading a config requires almost no memory copying at all, nor any pointless reference-count juggling.
- Most importantly, once the config directory is separated from the code, you can pair it with a config-management backend to achieve centralized, unified config management.
- It supports reloading configs on change (for non-ZTS builds). That is, if a config changes (I strongly recommend using mv to change configs, not cp), it will reload — no restart needed (the check frequency is controlled by yaconf.check_delay).
- It supports a rich set of config types, including strings, arrays, sections, section inheritance — and you can even write PHP constants and environment variables directly inside a config.
- And most importantly, it's simple.
API
Yaconf provides just two methods:
-
mixed Yaconf::get(string $name, mixed $default = NULL)
This fetches a config. The name is the config's name. Generally, if you have an ini file called foo.ini, then using foo as $name returns all the contents of that file as an array. default is the value returned when the config doesn't exist.
-
bool Yaconf::has(string $name)
This checks whether a config exists.
Yaconf configuration options
- yaconf.directory
- yaconf.check_delay
The config file directory. This cannot be set via ini_set, because it must be determined at PHP startup.
How often (in seconds) to check for file changes. If it's 0, no checking happens — meaning, with 0, a file change can only be reloaded by restarting PHP.
Config format
Yaconf uses ini files as its config files, because I've always felt ini is the best fit for configuration: key-value format, clear and readable.
A simple config looks like this (everything below assumes the ini file is named test):
foo="bar"
phpversion=PHP_VERSION
env=${HOME}
As shown above, we quote ordinary config values. Anything left unquoted is interpreted as a PHP constant — that is, you can write PHP constants directly in your config.
You can also see that we can write environment variables directly in a config, like env above:
Yaconf::get("test.env"); //test is the config file name
//string(16) "/home/huixinchen"
As shown above, you can see that, assuming the value of foo, you can access it with code like this:
Yaconf::get("test.foo"); //test is the config file name
Yaconf also supports array-type configs, written like this:
arr.0=1 arr.1=2
For a contiguous array, you can also just write:
arr[]=1 arr[]=2
To get the array value, use code like this:
Yaconf::get("test.arr");
This fetches the arr array from the test config file. Of course you can also grab one specific element of the array — say, element 0 of arr in the test config file:
$arr = Yaconf::get("test.arr.0");
Yaconf also supports map-type configs, written like this:
map.foo=bar map.bar=foo ;you can use a semicolon to write comments map2.foo.name=yaconf map2.foo.year=2015
The name value of foo's sub-map within map2 can be accessed like this:
Yaconf::get("test.map2.foo.name"); //test is the config file name
And, config files can also use sections, plus section inheritance:
[parent] parent="base" children="NULL" [children : parent] children="children"
Note the syntax for section inheritance: children:(colon)parent. This means the children section inherits all of base's config items. Any config you define in the children section with the same name as one in the parent section overrides what was defined in parent.
The value of the children config in the children section can be accessed like this:
Yaconf::get("test.children.children"); //test is the config file name
Example
First, suppose all our config files live under /tmp/yaconf. Then we need to add the following to php.ini:
yaconf.directory=/tmp/yaconf
With that, at PHP startup Yaconf will look for all *.ini files in that directory and try to process them. Note that multi-level directories aren't supported — Yaconf only handles the *.ini files directly inside yaconf.directory, not ones in subdirectories. (This is mainly for the sake of simplicity: because of sections, you can define one ini file per project.)
Suppose /tmp/yaconf holds two ini files:
foo.ini
name="yaconf" year=2015 features[]="fast" features.1="light" features.plus="zero-copy" features.constant=PHP_VERSION
bar.ini
[base] parent="yaconf" children="NULL" [children:base] children="set"
Then for foo's contents:
php7 -r 'var_dump(Yaconf::get("foo"));'
/*
array(3) {
["name"]=>
string(6) "yaconf"
["year"]=>
string(4) "2015"
["features"]=>
array(4) {
[0]=>
string(4) "fast"
[1]=>
string(5) "light"
["plus"]=>
string(9) "zero-copy"
["constant"]=>
string(9) "7.0.0-dev"
}
}
*/
For bar's contents:
php7 -r 'var_dump(Yaconf::get("bar"));'
/*
array(2) {
["base"]=>
array(2) {
["parent"]=>
string(6) "yaconf"
["children"]=>
string(4) "NULL"
}
["children"]=>
array(2) {
["parent"]=>
string(6) "yaconf"
["children"]=>
string(3) "set"
}
}
*/
Of course, you can use the (.) chaining syntax to precisely access any single value.
Finally
My Ya-series of extensions has gained a new member. Along with the earlier Yaf (PHP framework), Yar (PHP RPC framework), and Yac (PHP single-machine cache), you now have everything you need to easily put together a high-performance LAMP application solution.
Last note: Yaconf requires PHP 7 to use 🙂
Finally, the source code links: Yaconf at Github
PECL link: Yaconf at PECL
Be First to Comment