<?php namespace im\Primitive\Container;

use \InvalidArgumentException;

/**
 * Class ContainerFactory
 *
 * @package im\Primitive\Container
 * @author Igor Krimerman <i.m.krimerman@gmail.com>
 */
class ContainerFactory {

    /**
     * Simple Container Type
     * @var int
     */
    const SIMPLE = 1;

    /**
     * Revertable Container Type
     * @var int
     */
    const REVERTABLE = 2;

    /**
     * Return constructed instance of Container Type
     *
     * @param mixed $from
     * @param int|null   $type
     * @return \im\Primitive\Container\Container|\im\Primitive\Container\RevertableContainer
     */
    public function make($from, $type)
    {
        $type = $type ?: static::SIMPLE;

        switch ($type)
        {
            case static::SIMPLE:
                return new Container($from);
            case static::REVERTABLE:
                return new RevertableContainer($from);
            default:
                throw new InvalidArgumentException('Argument 2 is invalid');
        }
    }

    /**
     * Create Container Factory Instance
     *
     * @return static
     */
    public static function create()
    {
        return new static;
    }
}


<?php

/*
 * This file is part of Mandango.
 *
 * (c) Pablo Díez <pablodip@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Mandango\Mondator;

use Mandango\Mondator\Definition\Method;
use Mandango\Mondator\Definition\Property;

/**
 * ClassExtension is the base class for class extensions.
 *
 * @author Pablo Díez <pablodip@gmail.com>
 *
 * @api
 */
abstract class ClassExtension
{
    private $options;
    private $requiredOptions;

    protected $definitions;

    protected $class;
    protected $configClasses;
    protected $configClass;

    protected $newClassExtensions;
    protected $newConfigClasses;

    protected $twig;
    protected $twigTempDir;

    /**
     * Constructor.
     *
     * @param array $options An array of options.
     *
     * @api
     */
    public function __construct(array $options = array())
    {
        $this->options = array();
        $this->requiredOptions = array();

        $this->setUp();

        foreach ($options as $name => $value) {
            $this->setOption($name, $value);
        }

        // required options
        if ($diff = array_diff($this->requiredOptions, array_keys($options))) {
            throw new \RuntimeException(sprintf('%s requires the options: "%s".', get_class($this), implode(', ', $diff)));
        }
    }

    /**
     * Set up the extension.
     *
     * @api
     */
    protected function setUp()
    {
    }

    /**
     * Add an option.
     *
     * @param string $name         The option name.
     * @param mixed  $defaultValue The default value (optional, null by default).
     *
     * @api
     */
    protected function addOption($name, $defaultValue = null)
    {
        $this->options[$name] = $defaultValue;
    }

    /**
     * Add options.
     *
     * @param array $options An array with options (name as key and default value as value).
     *
     * @api
     */
    protected function addOptions(array $options)
    {
        foreach ($options as $name => $defaultValue) {
            $this->addOption($name, $defaultValue);
        }
    }

    /**
     * Add a required option.
     *
     * @param string $name The option name.
     *
     * @api
     */
    protected function addRequiredOption($name)
    {
        $this->addOption($name);

        $this->requiredOptions[] = $name;
    }

    /**
     * Add required options.
     *
     * @param array $options An array with the name of the required option as value.
     *
     * @api
     */
    protected function addRequiredOptions(array $options)
    {
        foreach ($options as $name) {
            $this->addRequiredOption($name);
        }
    }

    /**
     * Returns if exists an option.
     *
     * @param string $name The name.
     *
     * @return bool Returns true if the option exists, false otherwise.
     *
     * @api
     */
    public function hasOption($name)
    {
        return array_key_exists($name, $this->options);
    }

    /**
     * Set an option.
     *
     * @param string $name  The name.
     * @param mixed  $value The value.
     *
     * @throws \InvalidArgumentException If the option does not exists.
     *
     * @api
     */
    public function setOption($name, $value)
    {
        if (!$this->hasOption($name)) {
            throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name));
        }

        $this->options[$name] = $value;
    }

    /**
     * Returns the options.
     *
     * @return array The options.
     *
     * @api
     */
    public function getOptions()
    {
        return $this->options;
    }

    /**
     * Return an option.
     *
     * @param string $name The name.
     *
     * @return mixed The value of the option.
     *
     * @throws \InvalidArgumentException If the options does not exists.
     *
     * @api
     */
    public function getOption($name)
    {
        if (!$this->hasOption($name)) {
            throw new \InvalidArgumentException(sprintf('The option "%s" does not exists.', $name));
        }

        return $this->options[$name];
    }

    /**
     * New class extensions process.
     *
     * @param string       $class              The class.
     * @param \ArrayObject $configClasses      The config classes.
     * @param \ArrayObject $newClassExtensions The new class extensions.
     *
     * @api
     */
    public function newClassExtensionsProcess($class, \ArrayObject $configClasses, \ArrayObject $newClassExtensions)
    {
        $this->class = $class;
        $this->configClasses = $configClasses;
        $this->configClass = $configClasses[$class];
        $this->newClassExtensions = $newClassExtensions;

        $this->doNewClassExtensionsProcess();

        $this->class = null;
        $this->configClasses = null;
        $this->configClass = null;
        $this->newClassExtensions = null;
    }

    /**
     * Do the new class extensions process.
     *
     * Here you can add new class extensions.
     *
     * @api
     */
    protected function doNewClassExtensionsProcess()
    {
    }

    /**
     * New config classes process.
     *
     * @param string       $class            The class.
     * @param \ArrayObject $configClasses    The config classes.
     * @param \ArrayObject $newConfigClasses The new config classes.
     *
     * @api
     */
    public function newConfigClassesProcess($class, \ArrayObject $configClasses, \ArrayObject $newConfigClasses)
    {
        $this->class = $class;
        $this->configClasses = $configClasses;
        $this->configClass = $configClasses[$class];
        $this->newConfigClasses = $newConfigClasses;

        $this->doNewConfigClassesProcess();

        $this->class = null;
        $this->configClasses = null;
        $this->configClass = null;
        $this->newConfigClasses = null;
    }

    /**
     * Do the new config classes process.
     *
     * Here you can add new config classes, and change the config classes
     * if it is necessary to build the new config classes.
     *
     * @api
     */
    protected function doNewConfigClassesProcess()
    {
    }

    /**
     * Process the config class.
     *
     * @param string       $class         The class.
     * @param \ArrayObject $configClasses The config classes.
     *
     * @api
     */
    public function configClassProcess($class, \ArrayObject $configClasses)
    {
        $this->class = $class;
        $this->configClasses = $configClasses;
        $this->configClass = $configClasses[$class];

        $this->doConfigClassProcess();

        $this->class = null;
        $this->configClasses = null;
        $this->configClass = null;
    }

    /**
     * Do the config class process.
     *
     * Here you can modify the config class.
     *
     * @api
     */
    protected function doConfigClassProcess()
    {
    }

    /**
     * Process the class.
     *
     * @param string                      $class         The class.
     * @param \ArrayObject                $configClasses The config classes.
     * @param Mandango\Mondator\Container $container     The container.
     *
     * @api
     */
    public function classProcess($class, \ArrayObject $configClasses, Container $container)
    {
        $this->class = $class;
        $this->configClasses = $configClasses;
        $this->configClass = $configClasses[$class];
        $this->definitions = $container;

        $this->doClassProcess();

        $this->class = null;
        $this->configClasses = null;
        $this->configClass = null;
        $this->definitions = null;
    }

    /**
     * Do the class process.
     *
     * @api
     */
    protected function doClassProcess()
    {
    }

    /**
     * Twig.
     */
    protected function processTemplate(Definition $definition, $name, array $variables = array())
    {
        $twig = $this->getTwig();

        $variables['extension'] = $this;
        $variables['options'] = $this->options;
        $variables['class'] = $this->class;
        $variables['config_class'] = $this->configClass;
        $variables['config_classes'] = $this->configClasses;

        $result = $twig->loadTemplate($name)->render($variables);

        // properties
        $expression = '/
            (?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n?
             \ \ \ \ (?P<static>static\ )?
            (?P<visibility>public|protected|private)
            \s
            \$
            (?P<name>[a-zA-Z0-9_]+)
            ;
        /xU';
        preg_match_all($expression, $result, $matches);

        for ($i = 0; $i <= count($matches[0]) - 1; $i++) {
            $property = new Property($matches['visibility'][$i], $matches['name'][$i], null);
            if ($matches['static'][$i]) {
                $property->setStatic(true);
            }
            if ($matches['docComment'][$i]) {
                $property->setDocComment($matches['docComment'][$i]);
            }
            $definition->addProperty($property);
        }

        // methods
        $expression = '/
            (?P<docComment>\ \ \ \ \/\*\*\n[\s\S]*\ \ \ \ \ \*\/)?\n
            \ \ \ \ (?P<static>static\ )?
            (?P<visibility>public|protected|private)
            \s
            function
            \s
            (?P<name>[a-zA-Z0-9_]+)
            \((?P<arguments>[$a-zA-Z0-9_\\\=\(\), ]*)\)
            \n
            \ \ \ \ \{
                (?P<code>[\s\S]*)
            \n\ \ \ \ \}
        /xU';
        preg_match_all($expression, $result, $matches);

        for ($i = 0; $i <= count($matches[0]) - 1; $i++) {
            $code = trim($matches['code'][$i], "\n");
            $method = new Method($matches['visibility'][$i], $matches['name'][$i], $matches['arguments'][$i], $code);
            if ($matches['static'][$i]) {
                $method->setStatic(true);
            }
            if ($matches['docComment'][$i]) {
                $method->setDocComment($matches['docComment'][$i]);
            }
            $definition->addMethod($method);
        }
    }

    public function getTwig()
    {
        if (null === $this->twig) {
            if (!class_exists('Twig_Environment')) {
                throw new \RuntimeException('Twig is required to use templates.');
            }

            $loader = new \Twig_Loader_String();
            $twig = new \Twig_Environment($loader, array(
                'autoescape'       => false,
                'strict_variables' => true,
                'debug'            => true,
                'cache'            => $this->twigTempDir = sys_get_temp_dir().'/'.uniqid('mondator_'),
            ));

            $this->configureTwig($twig);

            $this->twig = $twig;
        }

        return $this->twig;
    }

    protected function configureTwig(\Twig_Environment $twig)
    {
    }

    /*
     * Tools.
     */
    protected function createClassExtensionFromArray(array $data)
    {
        if (!isset($data['class'])) {
            throw new \InvalidArgumentException(sprintf('The extension does not have class.'));
        }

        return new $data['class'](isset($data['options']) ? $data['options'] : array());
    }

    private function removeDir($target)
    {
        $fp = opendir($target);
        while (false !== $file = readdir($fp)) {
            if (in_array($file, array('.', '..'))) {
                continue;
            }

            if (is_dir($target.'/'.$file)) {
                self::removeDir($target.'/'.$file);
            } else {
                unlink($target.'/'.$file);
            }
        }
        closedir($fp);
        rmdir($target);
    }

    public function __destruct()
    {
        if ($this->twigTempDir && is_dir($this->twigTempDir)) {
            $this->removeDir($this->twigTempDir);
        }
    }
}


<?php

namespace Sonic\Routes;

/**
 * DefaultRoute
 *
 * The default route to execute for Sonic content
 *
 * @package    Sonic
 * @subpackage Route
 * @author     Hassan Khan <contact@hassankhan.me>
 * @link       https://github.com/hassankhan/Sonic
 * @license    MIT
 * @since      0.7
 */
class DefaultRoute extends \Sonic\Routing\Route
{

    /**
     * Initializes the route by creating a regex pattern from the provided URL,
     * and assigns the callable function for this route
     *
     * @param string $url
     * @codeCoverageIgnore
     */
    public function __construct($url = '')
    {
        parent::__construct($url, array($this, 'build_route'));
    }

    /**
     * Builds and returns the rendered HTML
     *
     * @return string
     */
    public function build_route()
    {
        // Get reference to Sonic
        $sonic = \Sonic\Sonic::instance();

        // Get resource URL
        $resource_url = $sonic->app['router']->request()->getPathInfo();

        // Get file path
        $path = rtrim($sonic->app['settings']['sonic.content_dir'] . $resource_url, '/');

        // Check if file(s) exist
        $contents = $sonic->app['filesystem']->listContents($path);

        // Load file
        $loaded_file = empty($contents) === TRUE
            ? $sonic->app['filesystem']->parse($path . '.md')
            : $sonic->app['filesystem']->parse(rtrim($path, '/') . '/' . 'index.md');

        // Load in any extra stuffs
        $sonic->app['extra'] = isset($sonic->app['extra']) === TRUE ? $sonic->app['extra'] : array();

        // Merge Twig options and content into one array
        $options = array_merge($loaded_file, $sonic->app['extra']);

        // Get template name from file, if not set, then use default
        $template_name = array_key_exists('template', $loaded_file['meta']) === true
            ? $loaded_file['meta']['template']
            : $sonic->app['settings']['sonic.default_template'];

        // Render template with Twig
        try {
            return $sonic->app['twig']->render($template_name, $options);
        }
        catch (\Exception $e) {
            return $sonic->app['router']->error($e);
        }
    }

}


<?php

/*
 * This file is part of Mandango.
 *
 * (c) Pablo Díez <pablodip@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Mandango\Mondator;

/**
 * Container of definitions.
 *
 * @author Pablo Díez <pablodip@gmail.com>
 *
 * @api
 */
class Container implements \ArrayAccess, \Countable, \IteratorAggregate
{
    private $definitions;

    /**
     * Constructor.
     *
     * @api
     */
    public function __construct()
    {
        $this->definitions = array();
    }

    /**
     * Returns if a definition name exists.
     *
     * @param string $name The definition name.
     *
     * @return bool Returns if the definition name exists.
     *
     * @api
     */
    public function hasDefinition($name)
    {
        return isset($this->definitions[$name]);
    }

    /**
     * Set a definition.
     *
     * @param string                       $name       The definition name.
     * @param Mandango\Mondator\Definition $definition The definition.
     *
     * @api
     */
    public function setDefinition($name, Definition $definition)
    {
        $this->definitions[$name] = $definition;
    }

    /**
     * Set the definitions.
     *
     * @param array $definitions An array of definitions.
     *
     * @api
     */
    public function setDefinitions(array $definitions)
    {
        $this->definitions = array();
        foreach ($definitions as $name => $definition) {
            $this->setDefinition($name, $definition);
        }
    }

    /**
     * Returns a definition by name.
     *
     * @param string $name The definition name.
     *
     * @return Mandango\Mondator\Definition The definition.
     *
     * @throws \InvalidArgumentException If the definition does not exists.
     *
     * @api
     */
    public function getDefinition($name)
    {
        if (!$this->hasDefinition($name)) {
            throw new \InvalidArgumentException(sprintf('The definition "%s" does not exists.', $name));
        }

        return $this->definitions[$name];
    }

    /**
     * Returns the definitions.
     *
     * @return arary The definitions.
     *
     * @api
     */
    public function getDefinitions()
    {
        return $this->definitions;
    }

    /**
     * Remove a definition
     *
     * @param string $name The definition name
     *
     * @throws \InvalidArgumentException If the definition does not exists.
     *
     * @api
     */
    public function removeDefinition($name)
    {
        if (!$this->hasDefinition($name)) {
            throw new \InvalidArgumentException(sprintf('The definition "%s" does not exists.', $name));
        }

        unset($this->definitions[$name]);
    }

    /**
     * Clear the definitions.
     *
     * @api
     */
    public function clearDefinitions()
    {
        $this->definitions = array();
    }

    /*
     * \ArrayAccess interface.
     */
    public function offsetExists($name)
    {
        return $this->hasDefinition($name);
    }

    public function offsetSet($name, $definition)
    {
        $this->setDefinition($name, $definition);
    }

    public function offsetGet($name)
    {
        return $this->getDefinition($name);
    }

    public function offsetUnset($name)
    {
        $this->removeDefinition($name);
    }

    /**
     * Returns the number of definitions (implements the \Countable interface).
     *
     * @return integer The number of definitions.
     *
     * @api
     */
    public function count()
    {
        return count($this->definitions);
    }

    /**
     * Returns an \ArrayIterator with the definitions (implements \IteratorAggregate interface).
     *
     * @return \ArrayIterator An \ArrayIterator with the definitions.
     *
     * @api
     */
    public function getIterator()
    {
        return new \ArrayIterator($this->definitions);
    }
}


<?php

/*
 * This file is part of Mandango.
 *
 * (c) Pablo Díez <pablodip@gmail.com>
 *
 * This source file is subject to the MIT license that is bundled
 * with this source code in the file LICENSE.
 */

namespace Mandango\Mondator;

/**
 * Mondator.
 *
 * @author Pablo Díez <pablodip@gmail.com>
 *
 * @api
 */
class Mondator
{
    private $configClasses;
    private $extensions;
    private $outputs;

    /**
     * Constructor.
     *
     * @api
     */
    public function __construct()
    {
        $this->configClasses = array();
        $this->extensions = array();
        $this->options = array();
    }

    /**
     * Set a config class.
     *
     * @param string $class       The class.
     * @param array  $configClass The config class.
     *
     * @api
     */
    public function setConfigClass($class, array $configClass)
    {
        $this->configClasses[$class] = $configClass;
    }

    /**
     * Set the config classes.
     *
     * @param array $configClasses An array of config classes (class as key and config class as value).
     *
     * @api
     */
    public function setConfigClasses(array $configClasses)
    {
        $this->configClasses = array();
        foreach ($configClasses as $class => $configClass) {
            $this->setConfigClass($class, $configClass);
        }
    }

    /**
     * Returns if a config class exists.
     *
     * @param string $class The class.
     *
     * @return bool Returns if the config class exists.
     *
     * @api
     */
    public function hasConfigClass($class)
    {
        return array_key_exists($class, $this->configClasses);
    }

    /**
     * Returns the config classes.
     *
     * @return array The config classes.
     *
     * @api
     */
    public function getConfigClasses()
    {
        return $this->configClasses;
    }

    /**
     * Returns a config class.
     *
     * @param string $class The class.
     *
     * @return array The config class.
     *
     * @throws \InvalidArgumentException If the config class does not exists.
     *
     * @api
     */
    public function getConfigClass($class)
    {
        if (!$this->hasConfigClass($class)) {
            throw new \InvalidArgumentException(sprintf('The config class "%s" does not exists.', $class));
        }

        return $this->configClasses[$class];
    }

    /**
     * Add a extension.
     *
     * @param Mandango\Mondator\Extension $extension The extension.
     *
     * @api
     */
    public function addExtension(Extension $extension)
    {
        $this->extensions[] = $extension;
    }

    /**
     * Set the extensions.
     *
     * @param array $extensions An array of extensions.
     *
     * @api
     */
    public function setExtensions(array $extensions)
    {
        $this->extensions = array();
        foreach ($extensions as $extension) {
            $this->addExtension($extension);
        }
    }

    /**
     * Returns the extensions.
     *
     * @return array The extensions.
     *
     * @api
     */
    public function getExtensions()
    {
        return $this->extensions;
    }

    /**
     * Generate the containers.
     *
     * @return array The containers.
     *
     * @api
     */
    public function generateContainers()
    {
        $containers = array();
        $containers['_global'] = new Container();

        // global extensions
        $globalExtensions = $this->getExtensions();

        // configClasses
        $configClasses = new \ArrayObject();
        foreach ($this->getConfigClasses() as $class => $configClass) {
            $configClasses[$class] = new \ArrayObject($configClass);
        }

        // classes extensions
        $classesExtensions = new \ArrayObject();
        $this->generateContainersClassesExtensions($globalExtensions, $classesExtensions, $configClasses);

        // config class process
        foreach ($classesExtensions as $class => $classExtensions) {
            foreach ($classExtensions as $classExtension) {
                $classExtension->configClassProcess($class, $configClasses);
            }
        }

        // pre global process
        foreach ($globalExtensions as $globalExtension) {
            $globalExtension->preGlobalProcess($configClasses, $containers['_global']);
        }

        // class process
        foreach ($classesExtensions as $class => $classExtensions) {
            $containers[$class] = $container = new Container();
            foreach ($classExtensions as $classExtension) {
                $classExtension->classProcess($class, $configClasses, $container);
            }
        }

        // post global process
        foreach ($globalExtensions as $globalExtension) {
            $globalExtension->postGlobalProcess($configClasses, $containers['_global']);
        }

        return $containers;
    }

    private function generateContainersClassesExtensions($globalExtensions, $classesExtensions, $configClasses)
    {
        foreach ($configClasses as $class => $configClass) {
            if (isset($classesExtensions[$class])) {
                continue;
            }

            $classesExtensions[$class] = $classExtensions = new \ArrayObject($globalExtensions);
            $this->generateContainersNewClassExtensions($class, $classExtensions, $configClasses);

            foreach ($classExtensions as $classExtension) {
                $newConfigClasses = new \ArrayObject();
                $classExtension->newConfigClassesProcess($class, $configClasses, $newConfigClasses);

                foreach ($newConfigClasses as $newClass => $newConfigClass) {
                    if (isset($classesExtensions[$newClass])) {
                        throw new \RuntimeException(sprintf('The class "%s" has several config classes.', $class));
                    }
                    $configClasses[$newClass] = new \ArrayObject($newConfigClass);
                }

                $this->generateContainersClassesExtensions($globalExtensions, $classesExtensions, $configClasses);
            }
        }
    }

    private function generateContainersNewClassExtensions($class, $classExtensions, $configClasses, $extensions = null)
    {
        if (null === $extensions) {
            $extensions = $classExtensions;
        }

        foreach ($extensions as $extension) {
            $newClassExtensions = new \ArrayObject();
            $extension->newClassExtensionsProcess($class, $configClasses, $newClassExtensions);

            foreach ($newClassExtensions as $newClassExtension) {
                if (!$newClassExtension instanceof ClassExtension) {
                    throw new \RuntimeException(sprintf('Some class extension of the class "%s" in the extension "%s" is not an instance of ClassExtension.', $class, get_class($extension)));
                }
                if ($newClassExtension instanceof Extension) {
                    throw new \RuntimeException(sprintf('Some class extension of the class "%s" in the extension "%s" is a instance of Extension, and it can be only a instance of ClassExtension.', $class, get_class($extension)));
                }

                $classExtensions[] = $newClassExtension;
                $this->generateContainersNewClassExtensions($class, $classExtensions, $configClasses, $newClassExtension);
            }
        }
    }

    /**
     * Dump containers.
     *
     * @param array $containers An array of containers.
     *
     * @api
     */
    public function dumpContainers(array $containers)
    {
        // directories
        foreach ($containers as $container) {
            foreach ($container->getDefinitions() as $name => $definition) {
                $output = $definition->getOutput();
                $dir    = $output->getDir();

                if (!file_exists($dir) && false === @mkdir($dir, 0777, true)) {
                    throw new \RuntimeException(sprintf('Unable to create the %s directory (%s).', $name, $dir));
                }

                if (!is_writable($dir)) {
                    throw new \RuntimeException(sprintf('Unable to write in the %s directory (%s).', $name, $dir));
                }
            }
        }

        // output
        foreach ($containers as $container) {
            foreach ($container->getDefinitions() as $name => $definition) {
                $output = $definition->getOutput($name);
                $dir    = $output->getDir();

                $file = $dir.DIRECTORY_SEPARATOR.$definition->getClassName().'.php';

                if (!file_exists($file) || $output->getOverride()) {
                    $dumper  = new Dumper($definition);
                    $content = $dumper->dump();

                    $tmpFile = tempnam(dirname($file), basename($file));
                    if (false === @file_put_contents($tmpFile, $content) || !@rename($tmpFile, $file)) {
                        throw new \RuntimeException(sprintf('Failed to write the file "%s".', $file));
                    }
                    chmod($file, 0644);
                }
            }
        }
    }

    /**
     * Generate and dump the containers.
     *
     * @api
     */
    public function process()
    {
        $this->dumpContainers($this->generateContainers());
    }
}


<?php namespace spec\im\Primitive\Bool;

use Prophecy\Argument;
use PhpSpec\ObjectBehavior;
use im\Primitive\Support\Traits\RetrievableTrait;

class BoolSpec extends ObjectBehavior
{
    use RetrievableTrait;

    protected $init;

    function let()
    {
        date_default_timezone_set('UTC');

        $this->init = true;

        $this->beConstructedWith($this->init);
    }

    function it_is_initializable()
    {
        $this->shouldHaveType('im\Primitive\Bool\Bool');
    }

    function it_should_construct_from_int()
    {
        $this->beConstructedWith(1);

        $this->value()->shouldBeEqualTo(true);
    }

    function it_should_construct_from_float()
    {
        $this->beConstructedWith(1.1);

        $this->value()->shouldBeEqualTo(true);
    }

    function it_should_construct_from_String_Type()
    {
        $this->beConstructedWith(string('false'));

        $this->value()->shouldBeEqualTo(false);
    }

    function it_should_construct_from_Int_Type()
    {
        $this->beConstructedWith(int(0));

        $this->value()->shouldBeEqualTo(false);
    }

    function it_should_construct_from_Float_Type()
    {
        $this->beConstructedWith(float(0.0));

        $this->value()->shouldBeEqualTo(false);
    }

    function it_should_construct_from_Bool_Type()
    {
        $this->beConstructedWith(bool('false'));

        $this->value()->shouldBeEqualTo(false);
    }

    function it_should_set_right_bool_from_string()
    {
        foreach ($this->getGrammar() as $rule => $bool)
        {
            $this->set($rule);

            $this->value()->shouldBeEqualTo($bool);
        }
    }

    function it_should_call_method_if_it_looks_looks_variable()
    {
        $this->value->shouldBe($this->init);

        $this->default->shouldBe(false);
    }

    function it_should_convert_to_Int_Type()
    {
        $this->toInt()->shouldHaveType('im\Primitive\Int\Int');
    }

    function it_should_convert_to_Float_Type()
    {
        $this->toFloat()->shouldHaveType('im\Primitive\Float\Float');
    }

    function it_should_convert_to_String_Type()
    {
        $this->toString()->shouldHaveType('im\Primitive\String\String');
    }

    function it_should_convert_to_Container_Type()
    {
        $this->toContainer()->shouldHaveType('im\Primitive\Container\Container');
    }

    function it_should_return_true_if_isTrue_called()
    {
        $result = $this->init ?: false;

        $this->isTrue()->shouldBe($result);
    }

    function it_should_return_false_if_isFalse_called()
    {
        $result = $this->init ? false: true;

        $this->isFalse()->shouldBe($result);
    }

    function it_should_return_right_string_representation()
    {
        $result = $this->init ? 'true' : 'false';

        $this->__toString()->shouldBe($result);
    }

    /**
     * Abstract Type methods
     */
    function it_should_return_inner_value()
    {
        $this->value()->shouldBeBool();
        $this->value()->shouldBeEqualTo($this->init);

        $this->get()->shouldBeBool();
        $this->get()->shouldBeEqualTo($this->init);
    }

    function it_should_return_value_if_call_instance_like_function()
    {
        $this()->shouldBe($this->init);
    }

    function it_should_serialize()
    {
        $match = serialize($this->init);

        $this->serialize()->shouldBe($match);
    }

    function it_should_unserialize()
    {
        $serialized = serialize($this->init);

        $this->unserialize($serialized);
        $this->value()->shouldBe($this->init);
    }

    function it_should_make_new_instance()
    {
        $match = $this->init;

        $this->make($match)->value()->shouldBe($match);
    }

    function it_should_create_new_instance_statically()
    {
        $match = $this->init;

        static::create($match)->value()->shouldBe($match);
    }

    function it_should_have_helper_function_and_create_new_instance()
    {
        $match = $this->init;

        $this->set(bool($match))->value()->shouldBe($match);
    }
}


<?php

namespace Timiki\RpcClient;

use GuzzleHttp\Client as HttpClient;
use GuzzleHttp\Psr7\Request as HttpRequest;
use Symfony\Component\EventDispatcher\EventDispatcherInterface;
use Timiki\RpcCommon\JsonRequest;
use Timiki\RpcCommon\JsonResponse;

/**
 * Light Http RPC client.
 */
class Client
{
    const VERSION = '3.1.0';

    /**
     * Server address.
     *
     * @var array
     */
    private $address = [];

    /**
     * Http client.
     *
     * @var HttpClient
     */
    private $httpClient;

    /**
     * Event dispatcher.
     *
     * @var null|EventDispatcherInterface
     */
    private $eventDispatcher;

    /**
     * Options.
     *
     * @var array
     */
    private $options = [
        'attempts_on_connection_error' => '10',
    ];

    /**
     * Client constructor.
     *
     * @param array|string                  $address         RPC server address string or array
     * @param null|EventDispatcherInterface $eventDispatcher
     * @param array                         $options
     */
    public function __construct($address, EventDispatcherInterface $eventDispatcher = null, $options = [])
    {
        $this->setAddress($address);
        $this->setEventDispatcher($eventDispatcher);
        $this->setOptions($options);
        $this->httpClient = new HttpClient(['cookies' => true, 'verify' => false]);
    }

    /**
     * Set options.
     *
     * @param array $options
     *
     * @return \Timiki\RpcClient\Client
     */
    public function setOptions($options = [])
    {
        foreach ($this->options as $option => $value) {
            if (isset($options[$option])) {
                $this->options[$option] = $options[$option];
            }
        }

        return $this;
    }

    /**
     * Set RPC server address.
     *
     * @param array|string $address One or array of RPC server address for request
     *
     * @return $this
     */
    public function setAddress($address)
    {
        $this->address = \is_string($address) ? \explode(',', $address) : $address;

        return $this;
    }

    /**
     * Set event dispatcher.
     *
     * @param null|EventDispatcherInterface $eventDispatcher
     *
     * @return $this
     */
    public function setEventDispatcher(EventDispatcherInterface $eventDispatcher = null)
    {
        $this->eventDispatcher = $eventDispatcher;

        return $this;
    }

    /**
     * Get RPC server address list.
     *
     * @return array
     */
    public function getAddress()
    {
        return $this->address;
    }

    /**
     * Execute one RPC method without response.
     *
     * @param string $method
     * @param array  $params
     * @param array  $headers
     */
    public function notice($method, array $params = [], array $headers = [])
    {
        $request = new JsonRequest($method, $params);
        $request->headers()->add($headers);

        try {
            $this->execute($request);
        } catch (\Exception $e) {
            // Nothing
        }
    }

    /**
     * Execute one RPC method.
     *
     * @param string $method
     * @param array  $params
     * @param array  $headers
     *
     * @return null|JsonResponse
     */
    public function call($method, array $params = [], array $headers = [])
    {
        $request = new JsonRequest($method, $params, \uniqid(\gethostname().'.', true));
        $request->headers()->add($headers);

        try {
            return $this->execute($request);
        } catch (\Exception $e) {
            return $this->createResponseFromException($e, $request);
        }
    }

    /**
     * Create response from exception and request.
     *
     * @param \Exception  $e
     * @param JsonRequest $request
     *
     * @return JsonResponse
     */
    protected function createResponseFromException(\Exception $e, JsonRequest $request)
    {
        $response = new JsonResponse();
        $response->setId($request->getId());

        $response->setErrorMessage($e->getMessage());
        $response->setErrorCode($e->getCode());

        return $response;
    }

    /**
     * Parser http response.
     *
     * @param string $http
     *
     * @throws Exceptions\InvalidResponseException
     *
     * @return JsonResponse|JsonResponse[]
     */
    protected function parserHttp($http)
    {
        $response = [];

        $json = \json_decode($http, true);

        if (JSON_ERROR_NONE !== \json_last_error() || empty($http)) {
            throw new Exceptions\InvalidResponseException('Invalid response from RPC server, must be valid json.');
        }

        /**
         * Create new JsonResponse.
         *
         * @param mixed $json
         *
         * @return JsonResponse
         */
        $createJsonResponse = function ($json) {
            $id = null;
            $result = null;
            $error = [
                'code' => null,
                'message' => null,
                'data' => null,
            ];

            if (
                \is_array($json)
                & \array_key_exists('id', $json)
                & (
                    \array_key_exists('result', $json)
                    || \array_key_exists('error', $json)
                )
            ) {
                $id = $json['id'];
                $result = \array_key_exists('result', $json) ? $json['result'] : null;

                if (\array_key_exists('error', $json)) {
                    $error['code'] = \array_key_exists('code', $json['error']) ? $json['error']['code'] : null;
                    $error['message'] = \array_key_exists('message', $json['error']) ? $json['error']['message'] : null;
                    $error['data'] = \array_key_exists('data', $json['error']) ? $json['error']['data'] : null;
                }

                $response = new JsonResponse();

                $response->setId($id);
                $response->setResult($result);
                $response->setErrorCode($error['code']);
                $response->setErrorMessage($error['message']);
                $response->setErrorData($error['data']);

                return $response;
            }
            throw new Exceptions\InvalidResponseException('Invalid response format from RPC server.');
        };

        if (\array_keys($json) === \range(0, \count($json) - 1)) {
            foreach ($json as $part) {
                $response[] = $createJsonResponse($part);
            }
        } else {
            $response = $createJsonResponse($json);
        }

        return $response;
    }

    /**
     * Execute json RPC request or batch requests.
     *
     * @param JsonRequest|JsonRequest[] $request
     * @param int                       $attempt
     *
     * @throws \Exception
     *
     * @return null|JsonResponse|JsonResponse[]
     */
    public function execute($request, $attempt = 1)
    {
        // Check request
        if (\is_array($request)) {
            foreach ($request as $value) {
                if (!$value instanceof JsonRequest) {
                    throw new Exceptions\InvalidRequestException('$request must be array of JsonRequest objects');
                }
            }
        } elseif (!$request instanceof JsonRequest) {
            throw new Exceptions\InvalidRequestException('$request must instance of JsonRequest');
        }

        $address = 1 === \count($this->address) ? $this->address[0] : $this->address[\rand(0, \count($this->address))];
        $isNeedResponse = true;

        if ($this->eventDispatcher) {
            $event = $this->eventDispatcher->dispatch(
                Event\JsonRequestEvent::EVENT,
                new Event\JsonRequestEvent($request, $address)
            );

            if ($event->isPropagationStopped()) {
                return null;
            }
        }

        if (\is_array($request)) {
            $requestHeaders = [];

            foreach ($request as $value) {
                $requestHeaders = \array_merge($requestHeaders, $value->headers()->all());
                $isNeedResponse = empty($value->getId()) ? false : true;
            }
        } else {
            $requestHeaders = $request->headers()->all();
            $isNeedResponse = empty($request->getId()) ? false : true;
        }

        // Default requestHeaders
        $requestHeaders['user-agent'] = \array_key_exists('user-agent', $requestHeaders)
            ? $requestHeaders['user-agent'] : 'JSON-RPC client/'.self::VERSION.'/'.PHP_VERSION;

        $requestHeaders['content-type'] = 'application/json';
        $requestHeaders['accept'] = 'application/json';
        $requestHeaders['cache-control'] = 'no-cache';
        $requestHeaders['connection'] = 'close';

        if (empty($this->address)) {
            throw new Exceptions\ConnectionException('Must be set rpc server address');
        }

        $httpRequest = new HttpRequest(
            'POST',
            \trim($address),
            $requestHeaders,
            \json_encode($request, JSON_UNESCAPED_UNICODE)
        );

        try {
            $httpResponse = $this->httpClient->send($httpRequest);
            $response = $this->parserHttp($httpResponse->getBody()->getContents());
        } catch (\Exception $e) {
            if ($attempt <= $this->options['attempts_on_connection_error']) {
                return $this->execute($request, ++$attempt);
            }
            throw new Exceptions\ConnectionException($e->getMessage());
        }

        if (!$isNeedResponse) {
            return null;
        }

        // Set response headers
        if (\is_array($response)) {
            foreach ($response as $value) {
                $value->headers()->add($httpResponse->getHeaders());
            }
        } else {
            $response->headers()->add($httpResponse->getHeaders());
        }

        // Request <-> Response
        if (\is_array($response)) {
            foreach ($response as $res) {
                foreach ($request as $req) {
                    if ($req->getId() === $res->getId()) {
                        $req->setResponse($res);
                    }
                }
            }
        } else {
            $request->setResponse($response);
        }

        if ($this->eventDispatcher) {
            $this->eventDispatcher->dispatch(
                Event\JsonResponseEvent::EVENT,
                new Event\JsonResponseEvent($response, $address)
            );
        }

        return $response;
    }
}


<?php
/**
 * QueryLanguage file class for cxense api
 */

namespace Bonnier\WP\Cxense\Services;

/**
 * QueryLanguage class
 */
class QueryLanguage
{
    /**
     * Get query
     * @param $orgPrefix
     * @param $strQuery
     * @return string
     */
    public static function getQuery($orgPrefix, $strQuery)
    {
        //Default search field priority
        $strFieldPriority = 'title^3, '.$orgPrefix.'-taxo-cat^2, description^2, '.$orgPrefix.'-taxo-cat-top^1, body^1';
        $strQuery = '"' . stripslashes($strQuery) . '"';
        $strQuery = $strFieldPriority . ':' . $strQuery;
        $strQuery = 'query(' . $strQuery . ')';

        return $strQuery;
    }
}


<?php namespace im\Primitive\Support\Abstracts;

use Countable;
use ArrayAccess;
use IteratorAggregate;
use im\Primitive\Support\Contracts\ArrayableContract;
use im\Primitive\Support\Contracts\FileableContract;
use im\Primitive\Support\Contracts\JsonableContract;
use im\Primitive\Support\Exceptions\OffsetNotExistsException;
use im\Primitive\Support\Iterators\RecursiveContainerIterator;

/**
 * Class ComplexType
 *
 * @package im\Primitive\Support\Abstracts
 * @author Igor Krimerman
 */
abstract class ComplexType extends Type implements
    FileableContract, JsonableContract, ArrayableContract, ArrayAccess, IteratorAggregate, Countable {

    /**
     * Write json representation of type value to file
     * You can specify second argument to call json_encode with params
     *
     * @param $file
     * @param int|IntegerContract $jsonOptions
     * @return bool
     */
    public function toFile($file, $jsonOptions = 0)
    {
        if (is_dir(pathinfo($file, PATHINFO_DIRNAME)))
        {
            return (bool) file_put_contents($file, $this->toJson($this->getIntegerable($jsonOptions)));
        }

        return false;
    }

    /**
     * Construct Type from file
     *
     * @param string|StringContract $file
     * @return mixed
     */
    abstract public function fromFile($file);

    /**
     * Return value converted to Json
     *
     * @param int|IntegerContract $options
     * @return string
     */
    public function toJson($options = 0)
    {
        return json_encode($this->value(), $this->getIntegerable($options));
    }

    /**
     * Construct Type from json.
     *
     * @param string|StringContract $json
     * @return mixed
     */
    abstract public function fromJson($json);

    /**
     * Return converted Type to array
     *
     * @return array
     */
    public function toArray()
    {
        return array_map(function ($item)
        {
            return $this->getArrayable($item, $item);

        }, $this->value());
    }

    /**
     * Construct from array
     *
     * @param array $array
     * @return $this
     */
    public function fromArray(array $array = [])
    {
        return $this->initialize($array);
    }

    /**
     * Get value by specified $key.
     *
     * @param string|StringContract $key
     * @param null|mixed $default
     * @return mixed
     */
    abstract public function get($key, $default = null);

    /**
     * Check if Type has specified $key.
     *
     * @param string|StringContract $key
     * @return mixed
     */
    abstract public function has($key);

    /**
     * Forget value by specified $key.
     *
     * @param string|StringContract $key
     * @return mixed
     */
    abstract public function forget($key);

    /**
     * {@inheritdoc}
     */
    abstract public function length();

    /*
    |--------------------------------------------------------------------------
    | ArrayAccess
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)

     * Whether a offset exists
     * @link http://php.net/manual/en/arrayaccess.offsetexists.php
     *
     * @param mixed $offset An offset to check for.
     * @return boolean true on success or false on failure.
     * The return value will be casted to boolean if non-boolean was returned.
     */
    public function offsetExists($offset)
    {
        return $this->has($offset);
    }

    /**
     * (PHP 5 >= 5.0.0)

     * Offset to retrieve
     * @link http://php.net/manual/en/arrayaccess.offsetget.php
     *
     * @param mixed $offset The offset to retrieve.
     * @return mixed Can return all value types.
     * @throws \im\Primitive\Support\Exceptions\OffsetNotExistsException
     */
    public function offsetGet($offset)
    {
        if ($this->has($offset))
        {
            return $this->get($offset);
        }

        throw new OffsetNotExistsException('Offset: ' . $offset . ' not exists.');
    }

    /**
     * (PHP 5 >= 5.0.0)

     * Offset to set
     *
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
     *
     * @param mixed $offset The offset to assign the value to.
     * @param mixed $value The value to set.
     * @return void
     */
    abstract public function offsetSet($offset, $value);

    /**
     * (PHP 5 >= 5.0.0)

     * Offset to unset
     * @link http://php.net/manual/en/arrayaccess.offsetunset.php
     *
     * @param mixed $offset The offset to unset.
     * @return void
     */
    public function offsetUnset($offset)
    {
        $this->forget($offset);
    }

    /*
    |--------------------------------------------------------------------------
    | IteratorAggregate
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)

     * Retrieve an external iterator
     * @link http://php.net/manual/en/iteratoraggregate.getiterator.php
     *
     * @return Traversable An instance of an object implementing Iterator or
     */
    public function getIterator()
    {
        return new RecursiveContainerIterator($this->value());
    }

    /*
    |--------------------------------------------------------------------------
    | Countable
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.1.0)

     * Count elements of a Type
     * @link http://php.net/manual/en/countable.count.php
     *
     * @return int The custom count as an integer.
     * The return value is cast to an integer.
     */
    public function count()
    {
        return $this->length();
    }
}


<?php namespace im\Primitive\Support;

use Stringy\StaticStringy;

/**
 * Class Str
 *
 * @package im\Primitive\Support
 * @author Taylor Otwell | refactored Igor Krimerman <i.m.krimerman@gmail.com>
 */
class Str {

    /**
     * The cache of snake-cased words.
     *
     * @var array
     */
    protected static $snakeCache = [];

    /**
     * The cache of camel-cased words.
     *
     * @var array
     */
    protected static $camelCache = [];

    /**
     * The cache of studly-cased words.
     *
     * @var array
     */
    protected static $studlyCache = [];

    /**
     * The cache of dashed-cased words.
     *
     * @var array
     */
    protected static $dashedCache = [];

    /**
     * Transliterate a UTF-8 value to ASCII.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function ascii($value)
    {
        return StaticStringy::toAscii($value);
    }

    /**
     * Convert a value to camel case.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function camel($value)
    {
        if (isset(static::$camelCache[$value]))
        {
            return static::$camelCache[$value];
        }

        return static::$camelCache[$value] = lcfirst(static::studly($value));
    }

    /**
     * Determine if a given string contains a given substring.
     *
     * @param string       $haystack
     * @param string|array $needles
     * @param bool         $caseSensitive
     *
     * @return bool
     */
    public static function contains($haystack, $needles, $caseSensitive = false)
    {
        $function = 'mb_stripos';

        if ($caseSensitive)
        {
            $function = 'mb_strpos';
        }

        foreach ((array) $needles as $needle)
        {
            if ($needle != '' && $function($haystack, $needle) !== false) return true;
        }

        return false;
    }

    /**
     * Convert value to dashed case
     *
     * @param $value
     *
     * @return string
     */
    public static function dashed($value)
    {
        if (isset(static::$dashedCache[$value]))
        {
            return static::$dashedCache[$value];
        }

        return static::$dashedCache[$value] = StaticStringy::dasherize($value);
    }

    /**
     * Determine if a given string ends with a given substring.
     *
     * @param  string       $haystack
     * @param  string|array $needles
     *
     * @return bool
     */
    public static function endsWith($haystack, $needles)
    {
        foreach ((array) $needles as $needle)
        {
            if ((string) $needle === mb_substr($haystack, -strlen($needle))) return true;
        }

        return false;
    }

    /**
     * Cap a string with a single instance of a given value.
     *
     * @param  string $value
     * @param  string $cap
     *
     * @return string
     */
    public static function finish($value, $cap)
    {
        $quoted = preg_quote($cap, '/');

        return preg_replace('/(?:' . $quoted . ')+$/', '', $value) . $cap;
    }

    /**
     * Determine if a given string matches a given pattern.
     *
     * @param  string $pattern
     * @param  string $value
     *
     * @return bool
     */
    public static function is($pattern, $value)
    {
        if ($pattern == $value) return true;

        $pattern = preg_quote($pattern, '#');

        // Asterisks are translated into zero-or-more regular expression wildcards
        // to make it convenient to check if the strings starts with the given
        // pattern such as "library/*", making any string check convenient.
        $pattern = str_replace('\*', '.*', $pattern) . '\z';

        return (bool) preg_match('#^' . $pattern . '#', $value);
    }

    /**
     * Returns true if $str matches the supplied pattern, false otherwise.
     *
     * @param  string $pattern Regex pattern to match against
     *
     * @param         $string  string to match
     *
     * @return bool Whether or not $str matches the pattern
     */
    public static function matches($pattern, $string)
    {
        $regexEncoding = mb_regex_encoding();
        mb_regex_encoding(mb_internal_encoding());

        $match = mb_ereg_match($pattern, $string);
        mb_regex_encoding($regexEncoding);

        return $match;
    }

    /**
     * Return the length of the given string.
     *
     * @param  string $value
     *
     * @return int
     */
    public static function length($value)
    {
        return mb_strlen($value);
    }

    /**
     * Limit the number of characters in a string.
     *
     * @param  string $value
     * @param  int    $limit
     * @param  string $end
     *
     * @return string
     */
    public static function limit($value, $limit = 100, $end = '...')
    {
        if (mb_strlen($value) <= $limit) return $value;

        return rtrim(mb_substr($value, 0, $limit, 'UTF-8')) . $end;
    }

    /**
     * Convert the given string to lower-case.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function lower($value)
    {
        return mb_strtolower($value);
    }

    /**
     * Limit the number of words in a string.
     *
     * @param  string $value
     * @param  int    $words
     * @param  string $end
     *
     * @return string
     */
    public static function words($value, $words = 100, $end = '...')
    {
        preg_match('/^\s*+(?:\S++\s*+){1,' . $words . '}/u', $value, $matches);

        if ( ! isset($matches[0]) || strlen($value) === strlen($matches[0])) return $value;

        return rtrim($matches[0]) . $end;
    }

    /**
     * Parse a Class@method style callback into class and method.
     *
     * @param  string $callback
     * @param  string $default
     *
     * @return array
     */
    public static function parseCallback($callback, $default)
    {
        return static::contains($callback, '@') ? explode('@', $callback, 2) : [$callback, $default];
    }

    /**
     * Generate a more truly "random" alpha-numeric string.
     *
     * @param  int $length
     *
     * @return string
     *
     * @throws \RuntimeException
     */
    public static function random($length = 16)
    {
        if (function_exists('openssl_random_pseudo_bytes'))
        {
            $bytes = openssl_random_pseudo_bytes($length * 2);

            if ($bytes === false)
            {
                throw new \RuntimeException('Unable to generate random string.');
            }

            return substr(str_replace(['/', '+', '='], '', base64_encode($bytes)), 0, $length);
        }

        return static::quickRandom($length);
    }

    /**
     * Generate a "random" alpha-numeric string.
     *
     * Should not be considered sufficient for cryptography, etc.
     *
     * @param  int $length
     *
     * @return string
     */
    public static function quickRandom($length = 16)
    {
        $pool = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';

        return substr(str_shuffle(str_repeat($pool, $length)), 0, $length);
    }

    /**
     * Convert the given string to upper-case.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function upper($value)
    {
        return mb_strtoupper($value);
    }

    /**
     * Convert the given string to title case.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function title($value)
    {
        return mb_convert_case($value, MB_CASE_TITLE, 'UTF-8');
    }

    /**
     * Generate a URL friendly "slug" from a given string.
     *
     * @param  string $title
     * @param  string $separator
     *
     * @return string
     */
    public static function slug($title, $separator = '-')
    {
        $title = static::ascii($title);

        // Convert all dashes/underscores into separator
        $flip = $separator == '-' ? '_' : '-';

        $title = preg_replace('![' . preg_quote($flip) . ']+!u', $separator, $title);

        // Remove all characters that are not the separator, letters, numbers, or whitespace.
        $title = preg_replace('![^' . preg_quote($separator) . '\pL\pN\s]+!u', '', mb_strtolower($title));

        // Replace all separator characters and whitespace by a single separator
        $title = preg_replace('![' . preg_quote($separator) . '\s]+!u', $separator, $title);

        return trim($title, $separator);
    }

    /**
     * Convert a string to snake case.
     *
     * @param  string $value
     * @param  string $delimiter
     *
     * @return string
     */
    public static function snake($value, $delimiter = '_')
    {
        if (isset(static::$snakeCache[$value . $delimiter]))
        {
            return static::$snakeCache[$value . $delimiter];
        }

        return static::$snakeCache[$value . $delimiter] = StaticStringy::underscored($value);;
    }

    /**
     * Determine if a given string starts with a given substring.
     *
     * @param  string       $haystack
     * @param  string|array $needles
     * @param bool          $caseSensitive
     *
     * @return bool
     */
    public static function startsWith($haystack, $needles, $caseSensitive = true)
    {
        $function = 'mb_stripos';

        if ($caseSensitive)
        {
            $function = 'mb_strpos';
        }

        foreach ((array) $needles as $needle)
        {
            if ($needle != '' && $function($haystack, $needle) === 0) return true;
        }

        return false;
    }

    /**
     * Convert a value to studly caps case.
     *
     * @param  string $value
     *
     * @return string
     */
    public static function studly($value)
    {
        if (isset(static::$studlyCache[$value]))
        {
            return static::$studlyCache[$value];
        }

        $value = ucwords(str_replace(['-', '_'], ' ', $value));

        return static::$studlyCache[$value] = str_replace(' ', '', $value);
    }

    /**
     * Check if string is readable file
     *
     * @param string $string
     *
     * @return bool
     */
    public static function isFile($string)
    {
        return is_file($string) && is_readable($string);
    }

    /**
     * Checks if given string is Json
     *
     * @param $string
     *
     * @return bool
     */
    public static function isJson($string)
    {
        if (is_string($string))
        {
            return is_array(json_decode($string, true));
        }

        return false;
    }

    /**
     * Checks if given string is serialized
     *
     * @param $string
     *
     * @return bool
     */
    public static function isSerialized($string)
    {
        return $string === 'b:0;' || @unserialize($string) !== false;
    }
}


<?php namespace im\Primitive\Support\Traits;

use InvalidArgumentException;

/**
 * Class SliceableTrait
 *
 * @package im\Primitive\String
 * @author Daniel St Jules | refactored Igor Krimerman <i.m.krimerman@gmail.com>
 */
trait SliceableTrait {

    /**
     * Implements python-like string slices. Slices the string if the offset
     * contains at least a single colon. Slice notation follow the format
     * "start:stop:step". If no colon is present, returns the character at the
     * given index. Offsets may be negative to count from the last character in
     * the string. Throws an exception if the index does not exist, more than 3
     * slice args are given, or the step is 0.
     *
     * @param mixed $args The index from which to retrieve the char, or a
     *                    string with colons to return a slice
     *
     * @return static                    The string corresponding to the index
     *                                   or slice
     * @throws \OutOfBoundsException     If a positive or negative offset does
     *                                   not exist
     * @throws InvalidArgumentException If more than 3 slice arguments are
     *                                   given, or step is 0
     */
    public function slice($args)
    {
        if ( ! is_string($args) || strpos($args, ':') === false)
        {
            throw new InvalidArgumentException('Arguments should be string and slice `:` operator should present');
        }

        $args = explode(':', $args);

        // Too many colons, invalid slice syntax
        if (count($args) > 3)
        {
            throw new InvalidArgumentException('Too many slice arguments');
        }

        // Get slice arguments
        for ($i = 0; $i < 3; $i++)
        {
            if (isset($args[$i]) && $args[$i] !== '')
            {
                $args[$i] = (int) $args[$i];
            }
            else
            {
                $args[$i] = null;
            }
        }

        return call_user_func_array([$this, 'getSlice'], $args);
    }

    /**
     * Returns a new SliceableStringy instance given start, stop and step
     * arguments for the desired slice. Start, which indicates the starting
     * index of the slice, defaults to the first character in the string if
     * step is positive, and the last character if negative. Stop, which
     * indicates the exclusive boundary of the range, defaults to the length
     * of the string if step is positive, and before the first character
     * if negative. Step allows the user to include only every nth character
     * in the result, with its sign determining the direction in which indices
     * are sampled. Throws an exception if step is equal to 0.
     *
     * @param int|null $start Optional start index of the slice
     * @param int|null $stop  Optional boundary for the slice
     * @param int|null $step  Optional rate at which to include characters
     *
     * @return static                   A new instance containing the slice
     * @throws InvalidArgumentException If step is equal to 0
     */
    protected function getSlice($start, $stop, $step)
    {
        $length = $this->length();
        $step = (isset($step)) ? $step : 1;

        if ($step === 0)
        {
            throw new InvalidArgumentException('Slice step cannot be 0');
        }

        if (isset($start))
        {
            $start = $this->adjustBoundary($length, $start, $step);
        }
        else
        {
            $start = ($step > 0) ? 0 : $length - 1;
        }

        if (isset($stop))
        {
            $stop = $this->adjustBoundary($length, $stop, $step);
        }
        else
        {
            $stop = ($step > 0) ? $length : -1;
        }

        // Return an empty string if the set of indices would be empty
        if (($step > 0 && $start >= $stop) || ($step < 0 && $start <= $stop))
        {
            return new static('');
        }

        // Return the substring if step is 1
        if ($step === 1)
        {
            return $this->cut($start, $stop - $start);
        }

        // Otherwise iterate over the slice indices
        $str = '';
        foreach ($this->getIndices($start, $stop, $step) as $index)
        {
            $str .= (isset($this[$index])) ? $this[$index] : '';
        }

        return new static($str);
    }

    /**
     * Adjusts the start or stop boundary based on the provided length and step.
     * The logic here uses CPython's PySlice_GetIndices as a reference. See:
     * https://github.com/python-git/python/blob/master/Objects/sliceobject.c
     *
     * @param int $length   The length of the string
     * @param int $boundary Start or stop value to adjust
     * @param int $step     The step to be used with the slice
     *
     * @return int An adjusted boundary value
     */
    protected function adjustBoundary($length, $boundary, $step)
    {
        if ($boundary < 0)
        {
            $boundary += $length;

            if ($boundary < 0)
            {
                $boundary = ($step < 0) ? -1 : 0;
            }
        }
        elseif ($boundary >= $length)
        {
            $boundary = ($step < 0) ? $length - 1 : $length;
        }

        return $boundary;
    }

    /**
     * Returns an array of indices to be included in the slice.
     *
     * @param int $start Start index of the slice
     * @param int $stop  Boundary for the slice
     * @param int $step  Rate at which to include characters
     *
     * @return array An array of indices in the string
     */
    protected function getIndices($start, $stop, $step)
    {
        $indices = [];

        if ($step > 0)
        {
            for ($i = $start; $i < $stop; $i += $step)
            {
                $indices[] = $i;
            }
        }
        else
        {
            for ($i = $start; $i > $stop; $i += $step)
            {
                $indices[] = $i;
            }
        }

        return $indices;
    }

    /**
     * Slice part of string
     *
     * @param int|IntegerContract $start
     * @param null|int|IntegerContract $length
     * @param string|StringContract $encoding
     * @return mixed
     */
    abstract public function cut($start, $length = null, $encoding = 'UTF-8');

    /**
     * Return length of string
     *
     * @return int
     */
    abstract public function length();
}


<?php namespace im\Primitive\Support\Abstracts;

use InvalidArgumentException;
use JsonSerializable;
use Serializable;
use im\Primitive\Support\Dump\Dumper;
use im\Primitive\Support\Contracts\TypeContract;
use im\Primitive\Support\Traits\RetrievableTrait;

/**
 * Class Type
 *
 * @package im\Primitive\Support\Abstracts
 * @author Igor Krimerman
 */
abstract class Type implements TypeContract, Serializable, JsonSerializable {

    use RetrievableTrait;

    /**
     * Initialize inner value with given $value.
     *
     * @param mixed $value
     * @return $this
     */
    abstract protected function initialize($value);

    /**
     * Return inner value.
     *
     * @return mixed
     */
    abstract public function value();

    /**
     * Retrieve needed type from given $value.
     *
     * @param mixed $value
     * @return mixed
     */
    abstract protected function retrieveValue($value);

    /**
     * Magic method to auto convert Type to string.
     *
     * @return string
     */
    abstract public function __toString();

    /**
     * Return default Type value.
     *
     * @return mixed
     */
    abstract protected function getDefault();

    /**
     * Magic method.
     * Used to call methods without parameters as variables.
     * Throws InvalidArgumentException if method not exists.
     *
     * @param mixed $value
     * @return mixed
     */
    public function __get($value)
    {
        $value = $this->getStringable($value);

        if (method_exists($this, $value))
        {
            return $this->{$value}();
        }

        throw new InvalidArgumentException('Argument 1 is invalid, offset not exists.');
    }

    /**
     * (PHP 5 >= 5.1.0)

     * String representation of type.
     * @link http://php.net/manual/en/serializable.serialize.php
     * @return string the string representation of the object or null
     */
    public function serialize()
    {
        return serialize($this->value());
    }

    /**
     * (PHP 5 >= 5.1.0)

     * Constructs the Type
     * @link http://php.net/manual/en/serializable.unserialize.php
     *
     * @param string $serialized The string representation of the object.
     * @return void
     */
    public function unserialize($serialized)
    {
        $this->initialize(unserialize($serialized));
    }

    /**
     * Dump Type.
     * If $die is specified, it will end the script.
     *
     * @param bool $die
     */
    public function dump($die = false)
    {
        (new Dumper())->dump($this);

        if ($this->getBoolable($die)) die;
    }

    /**
     * Make new Type instance with given $value. Useful for chaining.
     *
     * @param mixed $value
     * @return static
     */
    public function make($value)
    {
        return new static($value);
    }

    /**
     * Statically create new Type Instance. Useful for chaining.
     *
     * @param mixed $value
     * @return static
     */
    public static function create($value)
    {
        return new static($value);
    }

    /**
     * Magic method to retrieve Type inner value.
     *
     * @return number
     */
    public function __invoke()
    {
        return $this->value();
    }

    /*
    |--------------------------------------------------------------------------
    | JsonSerializable
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.4.0)
     * Specify data which should be serialized to JSON
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     *
     * @return mixed data which can be serialized by json_encode,
     * which is a value of any type other than a resource.
     */
    public function jsonSerialize()
    {
        return $this->value();
    }
}


<?php namespace im\Primitive\Object;

use im\Primitive\String\String;
use im\Primitive\Container\Container;
use im\Primitive\Support\Arr;
use im\Primitive\Support\Abstracts\ComplexType;
use im\Primitive\Support\Contracts\ObjectContract;

/**
 * Class Object
 *
 * @package im\Primitive\Object
 * @author Igor Krimerman
 */
class Object extends ComplexType implements ObjectContract {

    /**
     * Construct Object Type.
     *
     * @param mixed $from
     */
    public function __construct($from = [])
    {
        $this->initialize($from);
    }

    /**
     * Magic set method. Dynamically sets value.
     *
     * @param string|StringContract $property
     * @param mixed $value
     * @return \im\Primitive\Object\Object
     */
    public function __set($property, $value)
    {
        return $this->set($property, $value);
    }

    /**
     * Magic get method. Dynamically gets value.
     *
     * @param string|StringContract $property
     * @return mixed|null
     */
    public function __get($property)
    {
        return $this->get($property);
    }

    /**
     * Getter for value. Supports dot notation.
     * Can retrieve value from objects, arrays, Containers, instances of ArrayableContract and ObjectContract.
     *
     * @param string|StringContract $property
     * @param mixed $default
     * @return mixed|null
     */
    public function get($property, $default = null)
    {
        $property = $this->getStringable($property);

        if ($this->has($property))
        {
            return $this->retrieveValue($property);
        }

        return $default;
    }

    /**
     * Setter for value. Supports dot notation.
     * Can set value to objects, arrays, Containers, instances of ArrayableContract and ObjectContract
     * or create nested arrays (if dot notation is used) if was not set.
     *
     * @param string|StringContract $property
     * @param mixed $value
     * @return $this
     */
    public function set($property, $value)
    {
        Arr::set($this, $this->getStringable($property), $value);

        return $this;
    }

    /**
     * Check if Object has specified property. Supports dot notation.
     * Can check in objects, arrays, Containers, instances of ArrayableContract and ObjectContract.
     *
     * @param string|StringContract $property
     * @return bool
     */
    public function has($property)
    {
        return Arr::has($this, $this->getStringable($property));
    }

    /**
     * Forget specified property. Supports dot notation.
     * Can unset it in objects (public properties), arrays, Containers,
     * instances of ArrayableContract and ObjectContract.
     *
     * @param string|StringContract $property
     * @return $this
     */
    public function forget($property)
    {
        Arr::forget($this, $property);

        return $this;
    }

    /**
     * {@inheritdoc}
     * Can be constructed like Container Type.
     *
     * @param mixed $value
     * @return $this
     */
    protected function initialize($value)
    {
        $array = (new Container($value))->notNumericKeys();

        foreach ($array as $property => $value)
        {
            $this->{$property} = $value;
        }

        return $this;
    }

    /**
     * {@inheritdoc}
     *
     * @return array
     */
    public function value()
    {
        return get_object_vars($this);
    }

    /**
     * {@inheritdoc}
     *
     * @return int
     */
    public function length()
    {
        return $this->toContainer()->length();
    }

    /**
     * Return string representation of Object Type (json).
     *
     * @return string
     */
    public function __toString()
    {
        return $this->toJson();
    }

    /**
     * Convert Object Type to String Type.
     *
     * @return \im\Primitive\String\String
     */
    public function toString()
    {
        return new String($this->toJson());
    }

    /**
     * Convert Object Type to Container Type.
     *
     * @return \im\Primitive\Container\Container
     */
    public function toContainer()
    {
        return new Container($this->value());
    }

    /**
     * Construct Object from json.
     *
     * @param $json
     * @return \im\Primitive\Object\Object
     */
    public function fromJson($json)
    {
        return $this->initialize($json);
    }

    /**
     * Construct Object from file.
     *
     * @param $file
     * @return $this
     */
    public function fromFile($file)
    {
        return $this->initialize($file);
    }

    /**
     * {@inheritdoc}
     * Retrieves with dot notation.
     *
     * @param string|StringContract $property
     * @return mixed
     */
    protected function retrieveValue($property)
    {
        return _data_get($this, $property, $this->getDefault());
    }

    /**
     * {@inheritdoc}
     *
     * @return null
     */
    protected function getDefault()
    {
        return null;
    }

    /*
    |--------------------------------------------------------------------------
    | JsonSerializable
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.4.0)

     * Specify data which should be serialized to JSON
     *
     * @link http://php.net/manual/en/jsonserializable.jsonserialize.php
     * @return mixed data which can be serialized by json_encode,
     *       which is a value of any type other than a resource.
     */
    function jsonSerialize()
    {
        return $this->toContainer()->jsonSerialize();
    }

    /*
    |--------------------------------------------------------------------------
    | ArrayAccess
    |--------------------------------------------------------------------------
    */
    /**
     * (PHP 5 >= 5.0.0)

     * Offset to set
     *
     * @link http://php.net/manual/en/arrayaccess.offsetset.php
     *
     * @param mixed $offset
     *      The offset to assign the value to.
     * @param mixed $value
     *      The value to set.
     *
     * @return void
     */
    public function offsetSet($offset, $value)
    {
        $this->set($offset, $value);
    }
}