+<?php
+
+/**
+ * merry\Config
+ *
+ * @author Michael Wallner <mike@php.net>
+ */
+namespace merry;
+
+/**
+ * A merry config container
+ *
+ * @see https://github.com/m6w6/merry
+ * @package merry\Config
+ */
+class Config implements \ArrayAccess, \RecursiveIterator
+{
+ /**
+ * Index for a numerically indexed array
+ * @internal
+ * @var int
+ */
+ private $index = 0;
+
+ /**
+ * Container
+ * @internal
+ * @var stdClass
+ */
+ private $props;
+
+ /**
+ * State for the RecursiveIterator
+ * @internal
+ * @var array
+ */
+ private $riter;
+
+ /**
+ * Create a new configuration container
+ * @param array $array the configuration array
+ * @param string $section the section to use (i.e. first level key)
+ * @param string $section_sep a separator for section extension
+ * @param string $key_sep a separator for key traversal
+ */
+ public function __construct(array $array = null, $section = null, $section_sep = ":", $key_sep = ".") {
+ $this->props = new \stdClass;
+
+ if (isset($section) && strlen($section_sep)) {
+ $array = $this->combine($array, $section_sep)[$section];
+ }
+ if ($array) {
+ $config = array();
+
+ if (strlen($key_sep)) {
+ foreach ($array as $key => $val) {
+ $this->walk($config, $key, $val, $key_sep);
+ }
+ }
+
+ foreach ($config as $property => $value) {
+ $this->__set($property, $value);
+ }
+ }
+ }
+
+ /**
+ * Combine individual sections with their parent section
+ * @param array $array the config array
+ * @param string $section_sep the section extension separator
+ * @return array merged sections
+ */
+ protected function combine($array, $section_sep) {
+ foreach ($array as $section_spec => $settings) {
+ $section_spec = array_map("trim", explode($section_sep, $section_spec));
+ if (count($section_spec) > 1) {
+ $sections[$section_spec[0]] = array_merge(
+ $sections[$section_spec[1]],
+ $settings
+ );
+ } else {
+ $sections[$section_spec[0]] = $settings;
+ }
+ }
+ return $sections;
+ }
+
+ /**
+ * Walk a key split by the key separator into an array up and set the
+ * respective value on the leaf
+ * @param mixed $ptr current leaf pointer in the array
+ * @param string $key the array key
+ * @param mixed $val the value to set
+ * @param string $key_sep the key separator for traversal
+ */
+ protected function walk(&$ptr, $key, $val, $key_sep) {
+ foreach (explode($key_sep, $key) as $sub) {
+ $ptr = &$ptr[$sub];
+ }
+ $ptr = $val;
+ }
+
+ /**
+ * Recursively turn a Config instance and its childs into an array
+ * @param \merry\Config $o the Config instance to convert to an array
+ * @return array
+ */
+ protected function arrayify(Config $o) {
+ $a = [];
+
+ foreach ($o->props as $k => $v) {
+ if ($v instanceof Config) {
+ $a[$k] = $this->arrayify($v);
+ } else {
+ $a[$k] = $v;
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * Apply one or mor modifier callbacks
+ * @param mixed $modifier
+ * @return \merry\Config
+ */
+ public function apply($modifier) {
+ if (is_callable($modifier)) {
+ foreach ($this->props as $prop => $value) {
+ $this->__set($prop, $modifier($value, $prop));
+ }
+ } else {
+ foreach ($modifier as $key => $mod) {
+ if (is_callable($mod)) {
+ $this->props->$key = $mod(isset($this->props->$key) ? $this->props->$key : null, $key);
+ } elseif (is_array($mod)) {
+ $this->props->$key->apply($mod);
+ } else {
+ /* */
+ }
+ }
+ }
+ return $this;
+ }
+
+ /**
+ * Return the complete config as array
+ * @return array
+ */
+ function toArray() {
+ return $this->arrayify($this);
+ }
+
+ /**
+ * @ignore
+ */
+ function __get($prop) {
+ return $this->props->$prop;
+ }
+
+ /**
+ * @ignore
+ */
+ function __set($prop, $value) {
+ if (isset($value) && !is_scalar($value) && !($value instanceof Config)) {
+ $value = new static((array) $value);
+ }
+ if (!strlen($prop)) {
+ $prop = $this->index++;
+ } elseif (is_numeric($prop) && !strcmp($prop, (int) $prop)) {
+ /* update internal index */
+ if ($prop >= $this->index) {
+ $this->index = $prop + 1;
+ }
+ }
+
+ $this->props->$prop = $value;
+ }
+
+ /**
+ * @ignore
+ */
+ function __isset($prop) {
+ return isset($this->props->$prop);
+ }
+
+ /**
+ * @ignore
+ */
+ function __unset($prop) {
+ unset($this->props->$prop);
+ }
+
+ /**
+ * @ignore
+ */
+ function offsetGet($o) {
+ return $this->props->$o;
+ }
+
+ /**
+ * @ignore
+ */
+ function offsetSet($o, $v) {
+ $this->__set($o, $v);
+ }
+
+ /**
+ * @ignore
+ */
+ function offsetExists($o) {
+ return isset($this->props->$o);
+ }
+
+ /**
+ * @ignore
+ */
+ function offsetUnset($o) {
+ unset($this->props->$o);
+ }
+
+ /**
+ * @ignore
+ */
+ function rewind() {
+ $this->riter = (array) $this->props;
+ reset($this->riter);
+ }
+
+ /**
+ * @ignore
+ */
+ function valid() {
+ return NULL !== key($this->riter);
+ }
+
+ /**
+ * @ignore
+ */
+ function next() {
+ next($this->riter);
+ }
+
+ /**
+ * @ignore
+ */
+ function key() {
+ return key($this->riter);
+ }
+
+ /**
+ * @ignore
+ */
+ function current() {
+ return current($this->riter);
+ }
+
+ /**
+ * @ignore
+ */
+ function hasChildren() {
+ return current($this->riter) instanceof Config;
+ }
+
+ /**
+ * @ignore
+ */
+ function getChildren() {
+ return current($this->riter);
+ }
+}