@*--<?php

class PlaceHoldersStorage
{
    private $_storage;
    private $_index;
    private $_stack = array();

    public $parent;

    public function __construct($placeholders = null)
    {
        if (!isset($placeholders)) {
            $this->parent = null;
            $this->_storage = array();
        } else if (is_callable($placeholders) || (is_object($placeholders)
                    && 'PlaceHoldersStorage' === get_class($placeholders)
                    && method_exists($placeholders,'get'))) {
            $this->parent = $placeholders;
            $this->_storage = array();
        } else {
            $this->parent = null;
            $this->_storage = $placeholders;
        }

        $this->_index = 1;
    }

    public function __call($method, $args)
    {
        if ( $this->{$method} instanceof Closure ) {
            return call_user_func_array($this->{$method},$args);
        }
    }
    /**
     * Creates the unique placeholder with the specified value.
     *
     * @param {Object} value
     * @returns {String}
     */
    public function create($value)
    {
        $key = 'var' . $this->_index;
        $this->_index += 1;
        $this->set($key, $value);
        return '{' . $key . '}';
    }

    /**
     * Sets the placeholder value.
     *
     * @param {String} key
     * @param {Object} value
     */
    public function set($key, $value = '')
    {
        if (is_array($key)) {
            foreach ($key as $k => $v){
                $this->set($k, $v);
            }
            return;
        }
        if ($this->_validate($key)) {
            $this->_storage[$key] = $value;
        } else {
            exit('The "' . $key . '" placeholder is not added to' +
                ' the collection because of unsupported name.');
        }
    }

    /**
     * Returns the value for the specified placeholder.
     *
     * Returns empty string when the placeholder value is not set.
     *
     * @param {String} key
     * @returns {String}
     */
    public function get($key)
    {
        if (array_key_exists($key, $this->_storage)) {
            $value = $this->_storage[$key];
        } else {
           $value = (null === $this->parent) ? '' :
                (is_callable($this->parent) ? $this->parent($key) : $this->parent->get($key));
        }
        return $value;
    }

    /**
     * Removes a placeholder with the associated value.
     *
     * @param {String} key
     */
    public function remove($key)
    {
        if (isset($this->_storage[$key])) {
            unset($this->_storage[$key]);
        }
    }

    public function getStorage()
    {
        return $this->_storage;
    }

    public function replace($text)
    {
        return $this->replacer($text);
    }

    public function replacer($text)
    {
        return preg_replace_callback('/\{([a-z0-9_]+)\}/i', array(&$this, '_processContent'), $text);
    }

    private function _processContent($matches)
    {
        $key = $matches[1];
        if (in_array($key, $this->_stack)) {
            throw new Exception('Circular reference in placeholder values.');
        }

        $result;
        array_push($this->_stack, $key);
        $result = $this->replacer($this->get($key));
        array_pop($this->_stack);

        return $result;
    }

    private function _validate($name)
    {
        return preg_match('/^[\w\d_\-]+$/', $name);
    }
}--*@