code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency;

/**
 * Class Hoa\Consistency\Autoloader.
 *
 * This class is a PSR-4 compliant autoloader.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Autoloader
{
    /**
     * Namespace prefixes to base directories.
     *
     * @var array
     */
    protected $_namespacePrefixesToBaseDirectories = [];

    /**
     * Add a base directory for a namespace prefix.
     *
     * @param   string  $prefix           Namespace prefix.
     * @param   string  $baseDirectory    Base directory for this prefix.
     * @param   bool    $prepend          Whether the prefix is prepended or
     *                                    appended to the prefix' stack.
     * @return  void
     */
    public function addNamespace($prefix, $baseDirectory, $prepend = false)
    {
        $prefix        = trim($prefix, '\\') . '\\';
        $baseDirectory = rtrim($baseDirectory, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;

        if (false === isset($this->_namespacePrefixesToBaseDirectories[$prefix])) {
            $this->_namespacePrefixesToBaseDirectories[$prefix] = [];
        }

        if (true === $prepend) {
            array_unshift(
                $this->_namespacePrefixesToBaseDirectories[$prefix],
                $baseDirectory
            );
        } else {
            array_push(
                $this->_namespacePrefixesToBaseDirectories[$prefix],
                $baseDirectory
            );
        }

        return;
    }

    /**
     * Try to load the entity file for a given entity name.
     *
     * @param   string  $entity    Entity name to load.
     * @return  bool
     */
    public function load($entity)
    {
        $entityPrefix     = $entity;
        $hasBaseDirectory = false;

        while (false !== $pos = strrpos($entityPrefix, '\\')) {
            $currentEntityPrefix = substr($entity, 0, $pos + 1);
            $entityPrefix        = rtrim($currentEntityPrefix, '\\');
            $entitySuffix        = substr($entity, $pos + 1);
            $entitySuffixAsPath  = str_replace('\\', '/', $entitySuffix);

            if (false === $this->hasBaseDirectory($currentEntityPrefix)) {
                continue;
            }

            $hasBaseDirectory = true;

            foreach ($this->getBaseDirectories($currentEntityPrefix) as $baseDirectory) {
                $file = $baseDirectory . $entitySuffixAsPath . '.php';

                if (false !== $this->requireFile($file)) {
                    return $file;
                }
            }
        }

        if (true    === $hasBaseDirectory &&
            $entity === Consistency::getEntityShortestName($entity) &&
            false   !== $pos = strrpos($entity, '\\')) {
            return $this->runAutoloaderStack(
                $entity . '\\' . substr($entity, $pos + 1)
            );
        }

        return null;
    }

    /**
     * Require a file if exists.
     *
     * @param   string  $filename    File name.
     * @return  bool
     */
    public function requireFile($filename)
    {
        if (false === file_exists($filename)) {
            return false;
        }

        require $filename;

        return true;
    }

    /**
     * Check whether at least one base directory exists for a namespace prefix.
     *
     * @param   string  $namespacePrefix    Namespace prefix.
     * @return  bool
     */
    public function hasBaseDirectory($namespacePrefix)
    {
        return isset($this->_namespacePrefixesToBaseDirectories[$namespacePrefix]);
    }

    /**
     * Get declared base directories for a namespace prefix.
     *
     * @param   string  $namespacePrefix    Namespace prefix.
     * @return  array
     */
    public function getBaseDirectories($namespacePrefix)
    {
        if (false === $this->hasBaseDirectory($namespacePrefix)) {
            return [];
        }

        return $this->_namespacePrefixesToBaseDirectories[$namespacePrefix];
    }

    /**
     * Get loaded classes.
     *
     * @return  array
     */
    public static function getLoadedClasses()
    {
        return get_declared_classes();
    }

    /**
     * Run the entire autoloader stack with a specific entity.
     *
     * @param   string  $entity    Entity name to load.
     * @return  void
     */
    public function runAutoloaderStack($entity)
    {
        return spl_autoload_call($entity);
    }

    /**
     * Register the autoloader.
     *
     * @param   bool  $prepend    Prepend this autoloader to the stack or not.
     * @return  bool
     */
    public function register($prepend = false)
    {
        return spl_autoload_register([$this, 'load'], true, $prepend);
    }

    /**
     * Unregister the autoloader.
     *
     * @return  bool
     */
    public function unregister()
    {
        return spl_autoload_unregister([$this, 'load']);
    }

    /**
     * Get all registered autoloaders (not only from this library).
     *
     * @return  array
     */
    public function getRegisteredAutoloaders()
    {
        return spl_autoload_functions();
    }

    /**
     * Dynamic new, a simple factory.
     * It loads and constructs a class, with provided arguments.
     *
     * @param   bool     $classname    Classname.
     * @param   array    $arguments    Arguments for the constructor.
     * @return  object
     */
    public static function dnew($classname, array $arguments = [])
    {
        $classname = ltrim($classname, '\\');

        if (false === Consistency::entityExists($classname, false)) {
            spl_autoload_call($classname);
        }

        $class = new \ReflectionClass($classname);

        if (empty($arguments) || false === $class->hasMethod('__construct')) {
            return $class->newInstance();
        }

        return $class->newInstanceArgs($arguments);
    }
}

/**
 * Autoloader.
 */
$autoloader = new Autoloader();
$autoloader->addNamespace('Hoa', dirname(__DIR__));
$autoloader->register();


code:
# 1.17.05.02

  * CI: Set up Travis. (Ivan Enderlin, 2017-03-08T09:52:17+01:00)
  * Prelude: Remove the `(unset)` cast. (Ivan Enderlin, 2017-03-07T16:55:13+01:00)

# 1.17.01.10

  * Quality: Happy new year! (Alexis von Glasow, 2017-01-09T21:38:10+01:00)
  * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-19T16:27:31+02:00)
  * Documentation: Fix `docs` and `source` links. (Ivan Enderlin, 2016-10-05T20:26:20+02:00)
  * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T15:56:01+02:00)
  * Consistency: `void` is a reserved keyword now. (Ivan Enderlin, 2016-09-02T11:06:18+02:00)
  * Consistency: Remove `trait_exists` polyfill. (Ivan Enderlin, 2016-08-23T17:26:07+02:00)

# 1.16.03.03

  * Add `STREAM_CRYPTO_METHOD_*` constants on PHP 5.5. (Metalaka, 2016-02-29T21:10:14+01:00)
  * Composer: Fix `hoa/stream` dependency. (Ivan Enderlin, 2016-03-03T10:13:50+01:00)

# 1.16.01.14

  * Test: Write cases for flex entity in autoloader. (Ivan Enderlin, 2016-01-14T10:45:08+01:00)
  * Autoloader: Restrict loads to mapped entities. (Ivan Enderlin, 2016-01-14T10:38:21+01:00)

# 1.16.01.11

  * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00)
  * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T08:58:31+01:00)
  * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:03:33+01:00)

# 0.16.01.06

  * Prelude: Introduce the prelude/preamble! (Ivan Enderlin, 2015-12-09T08:34:16+01:00)
  * Consistency: Import last methods from `Hoa\Core`. (Ivan Enderlin, 2015-12-09T08:24:02+01:00)
  * Quality: Fix CS. (Ivan Enderlin, 2015-12-09T06:43:22+01:00)
  * Add a `.gitignore` file. (Metalaka, 2015-12-03T13:21:11+01:00)
  * Autoloader: Propagate unknown entity on the stack. (Ivan Enderlin, 2015-12-03T11:04:57+01:00)
  * Autoloader: Auto-register to support flex entity. (Ivan Enderlin, 2015-12-03T10:01:23+01:00)
  * Composer: Force some files to load. (Ivan Enderlin, 2015-12-03T08:15:55+01:00)
  * Test: Simplify `case_register`. (Ivan Enderlin, 2015-12-03T08:15:19+01:00)
  * Consistency: Use a strict equality check on trait. (Ivan Enderlin, 2015-12-02T17:11:50+01:00)
  * README: First draft. (Ivan Enderlin, 2015-12-02T08:40:34+01:00)
  * Documentation: Update API documentation. (Ivan Enderlin, 2015-12-02T08:37:58+01:00)
  * Autoloader: Support flex entities. (Ivan Enderlin, 2015-12-02T08:18:39+01:00)
  * Test: Write test suite of `…nsistency\Autoloader`. (Ivan Enderlin, 2015-12-01T08:42:30+01:00)
  * Test: Write test suite of `…sistency\Consistency`. (Ivan Enderlin, 2015-11-25T22:10:30+01:00)
  * Test: Write test suite of `…onsistency\Xcallable`. (Ivan Enderlin, 2015-11-25T08:45:59+01:00)
  * Test: Write test suite of `…onsistency\Exception`. (Ivan Enderlin, 2015-11-24T16:59:18+01:00)
  * Split from `Hoa\Core`. (Ivan Enderlin, 2015-11-23T23:08:19+01:00)

(first snapshot)

repo_name: spookie/gnu

license: mit


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency
{

/**
 * Class Hoa\Consistency\Consistency.
 *
 * This class is a collection of tools to ensure foreward and backward
 * compatibility.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Consistency
{
    /**
     * Check if an entity exists (class, interface, trait…).
     *
     * @param   string  $entityName    Entity name.
     * @param   bool    $autoloader    Run autoloader if necessary.
     * @return  bool
     */
    public static function entityExists($entityName, $autoloader = false)
    {
        return
            class_exists($entityName, $autoloader) ||
            interface_exists($entityName, false)   ||
            trait_exists($entityName, false);
    }

    /**
     * Get the shortest name for an entity.
     *
     * @param   string  $entityName    Entity name.
     * @return  string
     */
    public static function getEntityShortestName($entityName)
    {
        $parts = explode('\\', $entityName);
        $count = count($parts);

        if (1 >= $count) {
            return $entityName;
        }

        if ($parts[$count - 2] === $parts[$count - 1]) {
            return implode('\\', array_slice($parts, 0, -1));
        }

        return $entityName;
    }

    /**
     * Declare a flex entity (for nested library).
     *
     * @param   string  $entityName    Entity name.
     * @return  bool
     */
    public static function flexEntity($entityName)
    {
        return class_alias(
            $entityName,
            static::getEntityShortestName($entityName),
            false
        );
    }

    /**
     * Whether a word is reserved or not.
     *
     * @param   string  $word    Word.
     * @return  bool
     */
    public static function isKeyword($word)
    {
        static $_list = [
            // PHP keywords.
            '__halt_compiler',
            'abstract',
            'and',
            'array',
            'as',
            'bool',
            'break',
            'callable',
            'case',
            'catch',
            'class',
            'clone',
            'const',
            'continue',
            'declare',
            'default',
            'die',
            'do',
            'echo',
            'else',
            'elseif',
            'empty',
            'enddeclare',
            'endfor',
            'endforeach',
            'endif',
            'endswitch',
            'endwhile',
            'eval',
            'exit',
            'extends',
            'false',
            'final',
            'float',
            'for',
            'foreach',
            'function',
            'global',
            'goto',
            'if',
            'implements',
            'include',
            'include_once',
            'instanceof',
            'insteadof',
            'int',
            'interface',
            'isset',
            'list',
            'mixed',
            'namespace',
            'new',
            'null',
            'numeric',
            'object',
            'or',
            'print',
            'private',
            'protected',
            'public',
            'require',
            'require_once',
            'resource',
            'return',
            'static',
            'string',
            'switch',
            'throw',
            'trait',
            'true',
            'try',
            'unset',
            'use',
            'var',
            'void',
            'while',
            'xor',
            'yield',

            // Compile-time constants.
            '__class__',
            '__dir__',
            '__file__',
            '__function__',
            '__line__',
            '__method__',
            '__namespace__',
            '__trait__'
        ];

        return in_array(strtolower($word), $_list);
    }

    /**
     * Whether an ID is a valid PHP identifier.
     *
     * @param   string  $id    ID.
     * @return  bool
     */
    public static function isIdentifier($id)
    {
        return 0 !== preg_match(
            '#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#',
            $id
        );
    }

    /**
     * Register a register shutdown function.
     * It may be analogous to a super static destructor.
     *
     * @param   callable  $callable    Callable.
     * @return  bool
     */
    public static function registerShutdownFunction($callable)
    {
        return register_shutdown_function($callable);
    }

    /**
     * Get PHP executable.
     *
     * @return  string
     */
    public static function getPHPBinary()
    {
        if (defined('PHP_BINARY')) {
            return PHP_BINARY;
        }

        if (isset($_SERVER['_'])) {
            return $_SERVER['_'];
        }

        foreach (['', '.exe'] as $extension) {
            if (file_exists($_ = PHP_BINDIR . DS . 'php' . $extension)) {
                return realpath($_);
            }
        }

        return null;
    }

    /**
     * Generate an Universal Unique Identifier (UUID).
     *
     * @return  string
     */
    public static function uuid()
    {
        return sprintf(
            '%04x%04x-%04x-%04x-%04x-%04x%04x%04x',
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0x0fff) | 0x4000,
            mt_rand(0, 0x3fff) | 0x8000,
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff),
            mt_rand(0, 0xffff)
        );
    }
}

}

namespace
{

if (70000 > PHP_VERSION_ID && false === interface_exists('Throwable', false)) {
    /**
     * Implement a fake Throwable class, introduced in PHP7.0.
     */
    interface Throwable
    {
        public function getMessage();
        public function getCode();
        public function getFile();
        public function getLine();
        public function getTrace();
        public function getPrevious();
        public function getTraceAsString();
        public function __toString();
    }
}

/**
 * Define TLSv* constants, introduced in PHP 5.5.
 */
if (50600 > PHP_VERSION_ID) {
    $define = function ($constantName, $constantValue, $case = false) {
        if (!defined($constantName)) {
            return define($constantName, $constantValue, $case);
        }

        return false;
    };

    $define('STREAM_CRYPTO_METHOD_TLSv1_0_SERVER', 8);
    $define('STREAM_CRYPTO_METHOD_TLSv1_1_SERVER', 16);
    $define('STREAM_CRYPTO_METHOD_TLSv1_2_SERVER', 32);
    $define('STREAM_CRYPTO_METHOD_ANY_SERVER', 62);

    $define('STREAM_CRYPTO_METHOD_TLSv1_0_CLIENT', 9);
    $define('STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT', 17);
    $define('STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT', 33);
    $define('STREAM_CRYPTO_METHOD_ANY_CLIENT', 63);
}

if (!function_exists('curry')) {
    /**
     * Curry.
     * Example:
     *     $c = curry('str_replace', …, …, 'foobar');
     *     var_dump($c('foo', 'baz')); // bazbar
     *     $c = curry('str_replace', 'foo', 'baz', …);
     *     var_dump($c('foobarbaz')); // bazbarbaz
     * Nested curries also work:
     *     $c1 = curry('str_replace', …, …, 'foobar');
     *     $c2 = curry($c1, 'foo', …);
     *     var_dump($c2('baz')); // bazbar
     * Obviously, as the first argument is a callable, we can combine this with
     * \Hoa\Consistency\Xcallable ;-).
     * The “…” character is the HORIZONTAL ELLIPSIS Unicode character (Unicode:
     * 2026, UTF-8: E2 80 A6).
     *
     * @param   mixed  $callable    Callable (two parts).
     * @param   ...    ...          Arguments.
     * @return  \Closure
     */
    function curry($callable)
    {
        $arguments = func_get_args();
        array_shift($arguments);
        $ii        = array_keys($arguments, …, true);

        return function () use ($callable, $arguments, $ii) {
            return call_user_func_array(
                $callable,
                array_replace($arguments, array_combine($ii, func_get_args()))
            );
        };
    }
}

/**
 * Flex entity.
 */
Hoa\Consistency\Consistency::flexEntity('Hoa\Consistency\Consistency');

}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency;

use Hoa\Exception as HoaException;

/**
 * Class \Hoa\Consistency\Exception.
 *
 * Extending the \Hoa\Exception\Exception class.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Exception extends HoaException
{
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

if (false === defined('HOA')) {
    define('HOA', true);
}

if (false === defined('PHP_VERSION_ID') || PHP_VERSION_ID < 50400) {
    throw new Exception(
        'Hoa needs at least PHP5.4 to work; you have ' . phpversion() . '.'
    );
}

require_once __DIR__ . DIRECTORY_SEPARATOR . 'Autoloader.php';
require_once __DIR__ . DIRECTORY_SEPARATOR . 'Consistency.php';

$define = function ($constantName, $constantValue, $case = false) {
    if (!defined($constantName)) {
        return define($constantName, $constantValue, $case);
    }

    return false;
};

$define('SUCCEED',        true);
$define('FAILED',         false);
$define('…',              '__hoa_core_fill');
$define('DS',             DIRECTORY_SEPARATOR);
$define('PS',             PATH_SEPARATOR);
$define('ROOT_SEPARATOR', ';');
$define('RS',             ROOT_SEPARATOR);
$define('CRLF',           "\r\n");
$define('OS_WIN',         defined('PHP_WINDOWS_VERSION_PLATFORM'));
$define('S_64_BITS',      PHP_INT_SIZE == 8);
$define('S_32_BITS',      !S_64_BITS);
$define('PHP_INT_MIN',    ~PHP_INT_MAX);
$define('PHP_FLOAT_MIN',  (float) PHP_INT_MIN);
$define('PHP_FLOAT_MAX',  (float) PHP_INT_MAX);
$define('π',              M_PI);
$define('nil',            null);
$define('_public',        1);
$define('_protected',     2);
$define('_private',       4);
$define('_static',        8);
$define('_abstract',      16);
$define('_pure',          32);
$define('_final',         64);
$define('_dynamic',       ~_static);
$define('_concrete',      ~_abstract);
$define('_overridable',   ~_final);
$define('WITH_COMPOSER',  class_exists('Composer\Autoload\ClassLoader', false) ||
                          ('cli' === PHP_SAPI &&
                          file_exists(__DIR__ . DS . '..' . DS . '..' . DS . 'autoload.php')));

/**
 * Alias of \Hoa\Consistency\Xcallable.
 *
 * @param   mixed   $call    First callable part.
 * @param   mixed   $able    Second callable part (if needed).
 * @return  mixed
 */
if (!function_exists('xcallable')) {
    function xcallable($call, $able = '')
    {
        if ($call instanceof Hoa\Consistency\Xcallable) {
            return $call;
        }

        return new Hoa\Consistency\Xcallable($call, $able);
    }
}


code:
---

  Hoa is a modular, extensible and
  structured set of PHP libraries.

  Moreover, Hoa aims at being a bridge between industrial and research worlds.

# Hoa\Consistency

[![Help on IRC](https://img.shields.io/badge/help-%23hoaproject-ff0066.svg)](https://webchat.freenode.net/?channels=#hoaproject)
[![Help on Gitter](https://img.shields.io/badge/help-gitter-ff0066.svg)](https://gitter.im/hoaproject/central)
[![Documentation](https://img.shields.io/badge/documentation-hack_book-ff0066.svg)](https://central.hoa-project.net/Documentation/Library/Consistency)
[![Board](https://img.shields.io/badge/organisation-board-ff0066.svg)](https://waffle.io/hoaproject/consistency)

This library provides a thin layer between PHP VMs and libraries to ensure
consistency accross VM versions and library versions.

[Learn more](https://central.hoa-project.net/Documentation/Library/Consistency).

## Installation

With [Composer](https://getcomposer.org/), to include this library into
your dependencies, you need to
require [`hoa/consistency`](https://packagist.org/packages/hoa/consistency):

```sh
$ composer require hoa/consistency '~1.0'
```

For more installation procedures, please read [the Source
page](https://hoa-project.net/Source.html).

## Testing

Before running the test suites, the development dependencies must be installed:

```sh
$ composer install
```

Then, to run all the test suites:

```sh
$ vendor/bin/hoa test:run
```

For more information, please read the [contributor
guide](https://hoa-project.net/Literature/Contributor/Guide.html).

## Quick usage

We propose a quick overview of how the consistency API ensures foreward and
backward compatibility, also an overview of the [PSR-4
autoloader](http://www.php-fig.org/psr/psr-4/) and the xcallable API.

### Foreward and backward compatibility

The `Hoa\Consistency\Consistency` class ensures foreward and backward
compatibility.

#### Example with keywords

The `Hoa\Consistency\Consistency::isKeyword` checks whether a specific word is
reserved by PHP or not. Let's say your current PHP version does not support the
`callable` keyword or type declarations such as `int`, `float`, `string` etc.,
the `isKeyword` method will tell you if they are reserved keywords: Not only
for your current PHP version, but maybe in an incoming version.

```php
$isKeyword = Hoa\Consistency\Consistency::isKeyword('yield');
```

It avoids to write algorithms that might break in the future or for your users
living on the edge.

#### Example with identifiers

PHP identifiers are defined by a regular expression. It might change in the
future. To prevent breaking your algorithms, you can use the
`Hoa\Consistency\Consistency::isIdentifier` method to check an identifier is
correct regarding current PHP version:

```php
$isValidIdentifier = Hoa\Consistency\Consistency::isIdentifier('foo');
```

#### Flexible entities

Flexible entities are very simple. If we declare `Foo\Bar\Bar` as a flexible
entity, we will be able to access it with the `Foo\Bar\Bar` name or `Foo\Bar`.
This is very useful if your architecture evolves but you want to keep the
backward compatibility. For instance, it often happens that you create a
`Foo\Bar\Exception` class in the `Foo/Bar/Exception.php` file. But after few
versions, you realise other exceptions need to be introduced, so you need an
`Exception` directory. In this case, `Foo\Bar\Exception` should move as
`Foo\Bar\Exception\Exception`. If this latter is declared as a flexible entity,
backward compatibility will be kept.

```php
Hoa\Consistency\Consistency::flexEntity('Foo\Bar\Exception\Exception');
```

Another example is the “entry-class” (informal naming).
`Hoa\Consistency\Consistency` is a good example. This is more convenient to
write `Hoa\Consistency` instead of `Hoa\Consistency\Consistency`. This is
possible because this is a flexible entity.

#### Throwable & co.

The `Throwable` interface has been introduced to represent a whole new exception
architecture in PHP. Thus, to be compatible with incoming PHP versions, you
might want to use this interface in some cases. Hopefully, the `Throwable`
interface will be created for you if it does not exists.

```php
try {
    …
} catch (Throwable $e) {
    …
}
```

### Autoloader

`Hoa\Consistency\Autoloader` is a [PSR-4
compatible](http://www.php-fig.org/psr/psr-4/) autoloader. It simply works as
follows:
  * `addNamespace` is used to map a namespace prefix to a directory,
  * `register` is used to register the autoloader.

The API also provides the `load` method to force the load of an entity,
`unregister` to unregister the autoloader, `getRegisteredAutoloaders` to get
a list of all registered autoloaders etc.

For instance, to map the `Foo\Bar` namespace to the `Source/` directory:

```php
$autoloader = new Hoa\Consistency\Autoloader();
$autoloader->addNamespace('Foo\Bar', 'Source');
$autoloader->register();

$baz = new Foo\Bar\Baz(); // automatically loaded!
```

### Xcallable

Xcallables are “extended callables”. It is a unified API to invoke callables of
any kinds, and also extends some Hoa's API (like
[`Hoa\Event`](https://central.hoa-project.net/Resource/Library/Event)
or
[`Hoa\Stream`](https://central.hoa-project.net/Resource/Library/Stream)). It
understands the following kinds:
  * `'function'` as a string,
  * `'class::method'` as a string,
  * `'class', 'method'` as 2 string arguments,
  * `$object, 'method'` as 2 arguments,
  * `$object, ''` as 2 arguments, the “able” is unknown,
  * `function (…) { … }` as a closure,
  * `['class', 'method']` as an array of strings,
  * `[$object, 'method']` as an array.

To use it, simply instanciate the `Hoa\Consistency\Xcallable` class and use it
as a function:

```php
$xcallable = new Hoa\Consistency\Xcallable('strtoupper');
var_dump($xcallable('foo'));

/**
 * Will output:
 *     string(3) "FOO"
 */
```

The `Hoa\Consistency\Xcallable::distributeArguments` method invokes the callable
but the arguments are passed as an array:

```php
$xcallable->distributeArguments(['foo']);
```

This is also possible to get a unique hash of the callable:

```php
var_dump($xcallable->getHash());

/**
 * Will output:
 *     string(19) "function#strtoupper"
 */
```

Finally, this is possible to get a reflection instance of the current callable
(can be of kind [`ReflectionFunction`](http://php.net/ReflectionFunction),
[`ReflectionClass`](http://php.net/ReflectionClass),
[`ReflectionMethod`](http://php.net/ReflectionMethod) or
[`ReflectionObject`](http://php.net/ReflectionObject)):

```php
var_dump($xcallable->getReflection());

/**
 * Will output:
 *     object(ReflectionFunction)#42 (1) {
 *       ["name"]=>
 *       string(10) "strtoupper"
 *     }
 */
```

When the object is set but not the method, the latter will be deduced if
possible. If the object is of kind
[`Hoa\Stream`](http://central.hoa-project.net/Resource/Library/Stream), then
according to the type of the arguments given to the callable, the
`writeInteger`, `writeString`, `writeArray` etc. method will be used. If the
argument is of kind `Hoa\Event\Bucket`, then the method name will be deduced
based on the data contained inside the event bucket. This is very handy. For
instance, the following example will work seamlessly:

```php
Hoa\Event\Event::getEvent('hoa://Event/Exception')
    ->attach(new Hoa\File\Write('Exceptions.log'));
```

The `attach` method on `Hoa\Event\Event` transforms its argument as an
xcallable. In this particular case, the method to call is unknown, we only have
an object (of kind `Hoa\File\Write`). However, because this is a stream, the
method will be deduced according to the data contained in the event bucket fired
on the `hoa://Event/Exception` event channel.

## Documentation

The
[hack book of `Hoa\Consistency`](https://central.hoa-project.net/Documentation/Library/Consistency)
contains detailed information about how to use this library and how it works.

To generate the documentation locally, execute the following commands:

```sh
$ composer require --dev hoa/devtools
$ vendor/bin/hoa devtools:documentation --open
```

More documentation can be found on the project's website:
[hoa-project.net](https://hoa-project.net/).

## Getting help

There are mainly two ways to get help:

  * On the [`#hoaproject`](https://webchat.freenode.net/?channels=#hoaproject)
    IRC channel,
  * On the forum at [users.hoa-project.net](https://users.hoa-project.net).

## Contribution

Do you want to contribute? Thanks! A detailed [contributor
guide](https://hoa-project.net/Literature/Contributor/Guide.html) explains
everything you need to know.

## License

Hoa is under the New BSD License (BSD-3-Clause). Please, see
[`LICENSE`](https://hoa-project.net/LICENSE) for details.


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency\Test\Unit;

use Hoa\Consistency\Autoloader as SUT;
use Hoa\Test;

/**
 * Class \Hoa\Consistency\Test\Unit\Autoloader.
 *
 * Test suite of the autoloader.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Autoloader extends Test\Unit\Suite
{
    public function case_add_namespace_prepend()
    {
        $this
            ->given(
                $autoloader     = new SUT(),
                $prefix         = 'Foo\Bar\\',
                $baseDirectoryA = 'Source/Foo/Bar/',
                $baseDirectoryB = 'Source/Foo/Bar/'
            )
            ->when(
                $autoloader->addNamespace($prefix, $baseDirectoryA),
                $result = $autoloader->addNamespace($prefix, $baseDirectoryB)
            )
            ->then
                ->boolean($autoloader->hasBaseDirectory($prefix))
                    ->isTrue()
                ->array($autoloader->getBaseDirectories($prefix))
                    ->isEqualTo([
                        $baseDirectoryB,
                        $baseDirectoryA
                    ]);
    }

    public function case_add_namespace_append()
    {
        $this
            ->given(
                $autoloader     = new SUT(),
                $prefix         = 'Foo\Bar\\',
                $baseDirectoryA = 'Source/Foo/Bar/',
                $baseDirectoryB = 'Source/Foo/Bar/'
            )
            ->when(
                $autoloader->addNamespace($prefix, $baseDirectoryA),
                $result = $autoloader->addNamespace($prefix, $baseDirectoryB)
            )
            ->then
                ->boolean($autoloader->hasBaseDirectory($prefix))
                    ->isTrue()
                ->array($autoloader->getBaseDirectories($prefix))
                    ->isEqualTo([
                        $baseDirectoryA,
                        $baseDirectoryB
                    ]);
    }

    public function case_add_namespace_with_invalid_prefix()
    {
        $this
            ->given(
                $autoloader    = new SUT(),
                $prefix        = '\\\\Foo\Bar',
                $baseDirectory = 'Source/Foo/Bar/'
            )
            ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
            ->then
                ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
                    ->isTrue()
                ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
                    ->isEqualTo([$baseDirectory]);
    }

    public function case_add_namespace_with_invalid_base_directory()
    {
        $this
            ->given(
                $autoloader    = new SUT(),
                $prefix        = 'Foo\Bar\\',
                $baseDirectory = 'Source/Foo/Bar'
            )
            ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
            ->then
                ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
                    ->isTrue()
                ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
                    ->isEqualTo(['Source/Foo/Bar/']);
    }

    public function case_add_namespace_with_crazy_invalid_base_directory()
    {
        $this
            ->given(
                $autoloader    = new SUT(),
                $prefix        = 'Foo\Bar\\',
                $baseDirectory = 'Source/Foo/Bar/////'
            )
            ->when($result = $autoloader->addNamespace($prefix, $baseDirectory))
            ->then
                ->boolean($autoloader->hasBaseDirectory('Foo\Bar\\'))
                    ->isTrue()
                ->array($autoloader->getBaseDirectories('Foo\Bar\\'))
                    ->isEqualTo(['Source/Foo/Bar/']);
    }

    public function case_load()
    {
        $this
            ->given(
                $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
                $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/Bar/'),
                $this->calling($autoloader)->requireFile = function ($file) {
                    return $file;
                }
            )
            ->when($result = $autoloader->load('Foo\Bar\Baz\Qux'))
            ->then
                ->string($result)
                    ->isEqualTo('Source/Foo/Bar/Baz/Qux.php');
    }

    public function case_load_invalid_entity()
    {
        $this
            ->given($autoloader = new SUT())
            ->when($result = $autoloader->load('Foo'))
            ->then
                ->variable($result)
                    ->isNull();
    }

    public function case_load_flex_entity()
    {
        $self = $this;

        $this
            ->given(
                $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
                $autoloader->addNamespace('Foo\Bar\\', 'Source/Foo/'),
                $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) {
                    $called = true;
                    $self
                        ->string($entity)
                            ->isEqualTo('Foo\Bar\Baz\Baz');

                    return;
                },
                $autoloader->register()
            )
            ->when($result = $autoloader->load('Foo\Bar\Baz'))
            ->then
                ->variable($result)
                    ->isNull()
                ->boolean($called)
                    ->isTrue();
    }

    public function case_load_unmapped_flex_entity()
    {
        $self = $this;

        $this
            ->given(
                $autoloader = new \Mock\Hoa\Consistency\Autoloader(),
                $this->calling($autoloader)->runAutoloaderStack = function ($entity) use ($self, &$called) {
                    $called = true;

                    return;
                },
                $autoloader->register()
            )
            ->when($result = $autoloader->load('Foo\Bar\Baz'))
            ->then
                ->variable($result)
                    ->isNull()
                ->variable($called)
                    ->isNull();
    }

    public function case_require_existing_file()
    {
        $this
            ->given(
                $autoloader = new SUT(),

                $this->function->file_exists = true,

                $constantName = 'HOA_TEST_' . uniqid(),
                $filename     = 'hoa://Test/Vfs/Foo?type=file',

                file_put_contents($filename, '<?php define("' . $constantName . '", "BAR");')
            )
            ->when($result = $autoloader->requireFile($filename))
            ->then
                ->boolean($result)
                    ->isTrue()
                ->string(constant($constantName))
                    ->isEqualTo('BAR');
    }

    public function case_require_not_existing_file()
    {
        $this
            ->given(
                $autoloader                  = new SUT(),
                $this->function->file_exists = false
            )
            ->when($result = $autoloader->requireFile('/hoa/flatland'))
            ->then
                ->boolean($result)
                    ->isFalse();
    }

    public function case_has_not_base_directory()
    {
        $this
            ->given($autoloader = new SUT())
            ->when($result = $autoloader->hasBaseDirectory('foo'))
            ->then
                ->boolean($result)
                    ->isFalse();
    }

    public function case_get_base_undeclared_namespace_prefix()
    {
        $this
            ->given($autoloader = new SUT())
            ->when($result = $autoloader->getBaseDirectories('foo'))
            ->then
                ->array($result)
                    ->isEmpty();
    }

    public function case_dnew()
    {
        $this
            ->given($classname = 'Hoa\Consistency\Autoloader')
            ->when($result = SUT::dnew($classname))
            ->then
                ->object($result)
                    ->isInstanceOf($classname);
    }

    public function case_dnew_unknown_class()
    {
        $this
            ->given($this->function->spl_autoload_call = null)
            ->exception(function () {
                SUT::dnew('Foo');
            })
                ->isInstanceOf('ReflectionException');
    }

    public function case_get_loaded_classes()
    {
        $this
            ->given(
                $declaredClasses                      = get_declared_classes(),
                $this->function->get_declared_classes = $declaredClasses
            )
            ->when($result = SUT::getLoadedClasses())
            ->then
                ->array($result)
                    ->isEqualTo($declaredClasses);
    }

    public function case_register()
    {
        $self = $this;

        $this
            ->given($autoloader = new SUT())
            ->when($result = $autoloader->register())
            ->then
                ->boolean($result)
                    ->isTrue()
                ->array($autoloader->getRegisteredAutoloaders())
                    ->isEqualTo(spl_autoload_functions());
    }

    public function case_unregister()
    {
        $this
            ->given(
                $autoloader               = new SUT(),
                $oldRegisteredAutoloaders = $autoloader->getRegisteredAutoloaders()
            )
            ->when($result = $autoloader->register())
            ->then
                ->boolean($result)
                    ->isTrue()
                ->integer(count($autoloader->getRegisteredAutoloaders()))
                    ->isEqualTo(count($oldRegisteredAutoloaders) + 1)

            ->when($result = $autoloader->unregister())
            ->then
                ->boolean($result)
                    ->isTrue()
                ->array($autoloader->getRegisteredAutoloaders())
                    ->isEqualTo($oldRegisteredAutoloaders);
    }
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency\Test\Unit;

use Hoa\Consistency\Consistency as SUT;
use Hoa\Test;

/**
 * Class \Hoa\Consistency\Test\Unit\Consistency.
 *
 * Test suite of the consistency class.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Consistency extends Test\Unit\Suite
{
    protected function _entity_exists_with_xxx($class, $interface, $trait)
    {
        $this
            ->given(
                $this->function->class_exists     = $class,
                $this->function->interface_exists = $interface,
                $this->function->trait_exists     = $trait
            )
            ->when($result = SUT::entityExists('foo'))
            ->then
                ->boolean($result)
                    ->isTrue();
    }

    public function case_entity_exists_with_class()
    {
        return $this->_entity_exists_with_xxx(true, false, false);
    }

    public function case_entity_exists_with_interface()
    {
        return $this->_entity_exists_with_xxx(false, true, false);
    }

    public function case_entity_exists_with_trait()
    {
        return $this->_entity_exists_with_xxx(false, false, true);
    }

    public function case_entity_does_not_exists()
    {
        $this
            ->given(
                $this->function->class_exists     = false,
                $this->function->interface_exists = false,
                $this->function->trait_exists     = false
            )
            ->when($result = SUT::entityExists('foo'))
            ->then
                ->boolean($result)
                    ->isFalse();
    }

    public function case_get_entity_shortest_name()
    {
        $this
            ->when($result = SUT::getEntityShortestName('Foo\Bar\Bar'))
            ->then
                ->string($result)
                    ->isEqualTo('Foo\Bar');
    }

    public function case_get_entity_shortest_name_with_already_the_shortest()
    {
        $this
            ->when($result = SUT::getEntityShortestName('Foo\Bar'))
            ->then
                ->string($result)
                    ->isEqualTo('Foo\Bar');
    }

    public function case_get_entity_shortest_name_with_no_namespace()
    {
        $this
            ->when($result = SUT::getEntityShortestName('Foo'))
            ->then
                ->string($result)
                    ->isEqualTo('Foo');
    }

    public function case_is_keyword()
    {
        $this
            ->given(
                $keywords = [
                    '__HALT_COMPILER',
                    'abstract',
                    'and',
                    'array',
                    'as',
                    'bool',
                    'break',
                    'callable',
                    'case',
                    'catch',
                    'class',
                    'clone',
                    'const',
                    'continue',
                    'declare',
                    'default',
                    'die',
                    'do',
                    'echo',
                    'else',
                    'elseif',
                    'empty',
                    'enddeclare',
                    'endfor',
                    'endforeach',
                    'endif',
                    'endswitch',
                    'endwhile',
                    'eval',
                    'exit',
                    'extends',
                    'false',
                    'final',
                    'float',
                    'for',
                    'foreach',
                    'function',
                    'global',
                    'goto',
                    'if',
                    'implements',
                    'include',
                    'include_once',
                    'instanceof',
                    'insteadof',
                    'int',
                    'interface',
                    'isset',
                    'list',
                    'mixed',
                    'namespace',
                    'new',
                    'null',
                    'numeric',
                    'object',
                    'or',
                    'print',
                    'private',
                    'protected',
                    'public',
                    'require',
                    'require_once',
                    'resource',
                    'return',
                    'static',
                    'string',
                    'switch',
                    'throw',
                    'trait',
                    'true',
                    'try',
                    'unset',
                    'use',
                    'var',
                    'void',
                    'while',
                    'xor',
                    'yield',
                    '__CLASS__',
                    '__DIR__',
                    '__FILE__',
                    '__FUNCTION__',
                    '__LINE__',
                    '__METHOD__',
                    '__NAMESPACE__',
                    '__TRAIT__'
                ]
            )
            ->when(function () use ($keywords) {
                foreach ($keywords as $keyword) {
                    $this
                        ->boolean(SUT::isKeyword($keyword))
                            ->isTrue();
                }
            });
    }

    public function case_is_identifier()
    {
        $this
            ->given($_identifier = $this->realdom->regex('#^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x80-\xff]*$#'))
            ->when(function () use ($_identifier) {
                foreach ($this->sampleMany($_identifier, 1000) as $identifier) {
                    $this
                        ->boolean(SUT::isIdentifier($identifier))
                            ->isTrue();
                }
            });
    }

    public function case_register_shutdown_function()
    {
        $self = $this;

        $this
            ->given(
                $callable = function () {
                },
                $this->function->register_shutdown_function = function ($_callable) use (&$called, $self, &$callable) {
                    $called = true;

                    $self
                        ->variable($_callable)
                            ->isEqualTo($callable);

                    return true;
                }
            )
            ->when($result = SUT::registerShutdownFunction($callable))
            ->then
                ->boolean($result)
                    ->isTrue();
    }

    public function case_get_php_binary_with_constant()
    {
        $this
            ->given($this->constant->PHP_BINARY = '/foo/php')
            ->when($result = SUT::getPHPBinary())
            ->then
                ->string($result)
                    ->isEqualTo('/foo/php');
    }

    public function case_get_php_binary_with_server()
    {
        $this
            ->given(
                $this->function->defined = false,
                $_SERVER['_'] = '/bar/php'
            )
            ->when($result = SUT::getPHPBinary())
            ->then
                ->string($result)
                    ->isEqualTo('/bar/php');
    }

    public function case_get_php_binary_with_bin_directory()
    {
        unset($_SERVER['_']);

        $this
            ->given(
                $this->function->defined = false,
                $this->function->file_exists = true,
                $this->function->realpath = '/baz/php'
            )
            ->when($result = SUT::getPHPBinary())
            ->then
                ->string($result)
                    ->isEqualTo('/baz/php');
    }

    public function case_uuid()
    {
        $this
            ->given($this->function->mt_rand = 42)
            ->when($result = SUT::uuid())
            ->then
                ->string($result)
                    ->isEqualTo('002a002a-002a-402a-802a-002a002a002a');
    }

    public function case_uuid_all_differents()
    {
        $this
            ->when(function () {
                $uuids = [];

                for ($i = 0; $i < 10000; ++$i) {
                    $uuids[] = SUT::uuid();
                }

                $this
                    ->integer(count($uuids))
                        ->isEqualTo(count(array_unique($uuids)));
            });
    }
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency\Test\Unit;

use Hoa\Consistency\Exception as SUT;
use Hoa\Test;

/**
 * Class \Hoa\Consistency\Test\Unit\Exception.
 *
 * Test suite of the exception.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Exception extends Test\Unit\Suite
{
    public function case_hoa_exception()
    {
        $this
            ->when($result = new SUT('foo', 0))
            ->then
                ->object($result)
                    ->isInstanceOf('Hoa\Exception\Exception');
    }
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency\Test\Unit;

use Hoa\Consistency\Xcallable as SUT;
use Hoa\Test;

/**
 * Class \Hoa\Consistency\Test\Unit\Xcallable.
 *
 * Test suite of the xcallable class.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Xcallable extends Test\Unit\Suite
{
    public function case_form_function()
    {
        $this
            ->when($result = new SUT('strtoupper'))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->string($result->getValidCallback())
                    ->isEqualTo('strtoupper')
                ->string($result->getHash())
                    ->isEqualTo('function#strtoupper')
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionFunction')
                ->string($reflection->getName())
                    ->isEqualTo('strtoupper');
    }

    public function case_form_class___method()
    {
        $this
            ->when($result = new SUT(__CLASS__ . '::strtoupper'))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->array($result->getValidCallback())
                    ->isEqualTo([__CLASS__, 'strtoupper'])
                ->string($result->getHash())
                    ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('strtoupper');
    }

    public function case_form_class_method()
    {
        $this
            ->when($result = new SUT(__CLASS__, 'strtoupper'))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->array($result->getValidCallback())
                    ->isEqualTo([__CLASS__, 'strtoupper'])
                ->string($result->getHash())
                    ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('strtoupper');
    }

    public function case_form_object_method()
    {
        $this
            ->when($result = new SUT($this, 'strtolower'))
            ->then
                ->string($result('FOO'))
                    ->isEqualTo('foo')
                ->array($result->getValidCallback())
                    ->isEqualTo([$this, 'strtolower'])
                ->string($result->getHash())
                    ->matches(
                        '/^object\([^:]+\)#' .
                        preg_quote(__CLASS__) .
                        '::strtolower$/'
                    )
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('strtolower');
    }

    public function case_form_object_invoke()
    {
        $this
            ->when($result = new SUT($this))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->array($result->getValidCallback())
                    ->isEqualTo([$this, '__invoke'])
                ->string($result->getHash())
                    ->matches(
                        '/^object\([^:]+\)#' .
                        preg_quote(__CLASS__) .
                        '::__invoke$/'
                    )
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('__invoke');
    }

    public function case_form_closure()
    {
        $this
            ->given(
                $closure = function ($string) {
                    return strtoupper($string);
                }
            )
            ->when($result = new SUT($closure))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->object($result->getValidCallback())
                    ->isIdenticalTo($closure)
                ->string($result->getHash())
                    ->matches('/^closure\([^:]+\)$/')
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionFunction')
                ->string($reflection->getName())
                    ->isEqualTo('Hoa\Consistency\Test\Unit\{closure}');
    }

    public function case_form_array_of_class_method()
    {
        $this
            ->when($result = new SUT([__CLASS__, 'strtoupper']))
            ->then
                ->string($result('foo'))
                    ->isEqualTo('FOO')
                ->array($result->getValidCallback())
                    ->isEqualTo([__CLASS__, 'strtoupper'])
                ->string($result->getHash())
                    ->isEqualTo('class#' . __CLASS__ . '::strtoupper')
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('strtoupper');
    }

    public function case_form_array_of_object_method()
    {
        $this
            ->when($result = new SUT([$this, 'strtolower']))
            ->then
                ->string($result('FOO'))
                    ->isEqualTo('foo')
                ->array($result->getValidCallback())
                    ->isEqualTo([$this, 'strtolower'])
                ->string($result->getHash())
                    ->matches(
                        '/^object\([^:]+\)#' .
                        preg_quote(__CLASS__) .
                        '::strtolower$/'
                    )
                    ->isEqualTo($result . '')
                ->object($reflection = $result->getReflection())
                    ->isInstanceOf('ReflectionMethod')
                ->string($reflection->getName())
                    ->isEqualTo('strtolower');
    }

    public function case_form_able_not_a_string()
    {
        $this
            ->exception(function () {
                new SUT(__CLASS__, 123);
            })
                ->isInstanceOf('Hoa\Consistency\Exception');
    }

    public function case_form_function_not_defined()
    {
        $this
            ->exception(function () {
                new SUT('__hoa_test_undefined_function__');
            })
                ->isInstanceOf('Hoa\Consistency\Exception');
    }

    public function case_form_able_cannot_be_deduced()
    {
        $this
            ->given($this->function->method_exists = false)
            ->exception(function () {
                new SUT($this);
            })
                ->isInstanceOf('Hoa\Consistency\Exception');
    }

    public function case_invoke()
    {
        $this
            ->given(
                $callable = new SUT(
                    function ($x, $y, $z) {
                        return [$x, $y, $z];
                    }
                )
            )
            ->when($result = $callable(7, [4.2], 'foo'))
            ->then
                ->array($result)
                    ->isEqualTo([7, [4.2], 'foo']);
    }

    public function case_distribute_arguments()
    {
        $this
            ->given(
                $callable = new SUT(
                    function ($x, $y, $z) {
                        return [$x, $y, $z];
                    }
                )
            )
            ->when($result = $callable->distributeArguments([7, [4.2], 'foo']))
            ->then
                ->array($result)
                    ->isEqualTo([7, [4.2], 'foo']);
    }

    protected function _get_valid_callback_stream_xxx($argument, $method)
    {
        $this
            ->given(
                $stream    = new \Mock\Hoa\Stream\IStream\Out(),
                $arguments = [$argument],
                $xcallable = new SUT($stream)
            )
            ->when($result = $xcallable->getValidCallback($arguments))
            ->then
                ->array($result)
                    ->isEqualTo([$stream, $method]);
    }

    public function case_get_valid_callback_stream_character()
    {
        return $this->_get_valid_callback_stream_xxx('f', 'writeCharacter');
    }

    public function case_get_valid_callback_stream_string()
    {
        return $this->_get_valid_callback_stream_xxx('foo', 'writeString');
    }

    public function case_get_valid_callback_stream_boolean()
    {
        return $this->_get_valid_callback_stream_xxx(true, 'writeBoolean');
    }

    public function case_get_valid_callback_stream_integer()
    {
        return $this->_get_valid_callback_stream_xxx(7, 'writeInteger');
    }

    public function case_get_valid_callback_stream_array()
    {
        return $this->_get_valid_callback_stream_xxx([4, 2], 'writeArray');
    }

    public function case_get_valid_callback_stream_float()
    {
        return $this->_get_valid_callback_stream_xxx(4.2, 'writeFloat');
    }

    public function case_get_valid_callback_stream_other()
    {
        return $this->_get_valid_callback_stream_xxx($this, 'writeAll');
    }

    public static function strtoupper($string)
    {
        return strtoupper($string);
    }

    public function strtolower($string)
    {
        return strtolower($string);
    }

    public function __invoke($string)
    {
        return strtoupper($string);
    }

    public function __toString()
    {
        return 'hello';
    }
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Consistency;

use Hoa\Event;
use Hoa\Stream;

/**
 * Class Hoa\Consistency\Xcallable.
 *
 * Build a callable object, i.e. function, class::method, object->method or
 * closure, they all have the same behaviour. This callable is an extension of
 * native PHP callable (aka callback) to integrate Hoa's structures.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Xcallable
{
    /**
     * Callback, with the PHP format.
     *
     * @var mixed
     */
    protected $_callback = null;

    /**
     * Callable hash.
     *
     * @var string
     */
    protected $_hash     = null;

    /**
     * Build a callback.
     * Accepted forms:
     *     * `'function'`,
     *     * `'class::method'`,
     *     * `'class', 'method'`,
     *     * `$object, 'method'`,
     *     * `$object, ''`,
     *     * `function (…) { … }`,
     *     * `['class', 'method']`,
     *     * `[$object, 'method']`.
     *
     * @param   mixed   $call    First callable part.
     * @param   mixed   $able    Second callable part (if needed).
     */
    public function __construct($call, $able = '')
    {
        if ($call instanceof \Closure) {
            $this->_callback = $call;

            return;
        }

        if (!is_string($able)) {
            throw new Exception(
                'Bad callback form; the able part must be a string.',
                0
            );
        }

        if ('' === $able) {
            if (is_string($call)) {
                if (false === strpos($call, '::')) {
                    if (!function_exists($call)) {
                        throw new Exception(
                            'Bad callback form; function %s does not exist.',
                            1,
                            $call
                        );
                    }

                    $this->_callback = $call;

                    return;
                }

                list($call, $able) = explode('::', $call);
            } elseif (is_object($call)) {
                if ($call instanceof Stream\IStream\Out) {
                    $able = null;
                } elseif (method_exists($call, '__invoke')) {
                    $able = '__invoke';
                } else {
                    throw new Exception(
                        'Bad callback form; an object but without a known ' .
                        'method.',
                        2
                    );
                }
            } elseif (is_array($call) && isset($call[0])) {
                if (!isset($call[1])) {
                    return $this->__construct($call[0]);
                }

                return $this->__construct($call[0], $call[1]);
            } else {
                throw new Exception(
                    'Bad callback form.',
                    3
                );
            }
        }

        $this->_callback = [$call, $able];

        return;
    }

    /**
     * Call the callable.
     *
     * @param   ...
     * @return  mixed
     */
    public function __invoke()
    {
        $arguments = func_get_args();
        $valid     = $this->getValidCallback($arguments);

        return call_user_func_array($valid, $arguments);
    }

    /**
     * Distribute arguments according to an array.
     *
     * @param   array  $arguments    Arguments.
     * @return  mixed
     */
    public function distributeArguments(array $arguments)
    {
        return call_user_func_array([$this, '__invoke'], $arguments);
    }

    /**
     * Get a valid callback in the PHP meaning.
     *
     * @param   array   &$arguments    Arguments (could determine method on an
     *                                 object if not precised).
     * @return  mixed
     */
    public function getValidCallback(array &$arguments = [])
    {
        $callback = $this->_callback;
        $head     = null;

        if (isset($arguments[0])) {
            $head = &$arguments[0];
        }

        // If method is undetermined, we find it (we understand event bucket and
        // stream).
        if (null !== $head &&
            is_array($callback) &&
            null === $callback[1]) {
            if ($head instanceof Event\Bucket) {
                $head = $head->getData();
            }

            switch ($type = gettype($head)) {
                case 'string':
                    if (1 === strlen($head)) {
                        $method = 'writeCharacter';
                    } else {
                        $method = 'writeString';
                    }

                    break;

                case 'boolean':
                case 'integer':
                case 'array':
                    $method = 'write' . ucfirst($type);

                    break;

                case 'double':
                    $method = 'writeFloat';

                    break;

                default:
                    $method = 'writeAll';
                    $head   = $head . "\n";
            }

            $callback[1] = $method;
        }

        return $callback;
    }

    /**
     * Get hash.
     * Will produce:
     *     * function#…;
     *     * class#…::…;
     *     * object(…)#…::…;
     *     * closure(…).
     *
     * @return  string
     */
    public function getHash()
    {
        if (null !== $this->_hash) {
            return $this->_hash;
        }

        $_ = &$this->_callback;

        if (is_string($_)) {
            return $this->_hash = 'function#' . $_;
        }

        if (is_array($_)) {
            return
                $this->_hash =
                    (is_object($_[0])
                        ? 'object(' . spl_object_hash($_[0]) . ')' .
                          '#' . get_class($_[0])
                        : 'class#' . $_[0]) .
                    '::' .
                    (null !== $_[1]
                        ? $_[1]
                        : '???');
        }

        return $this->_hash = 'closure(' . spl_object_hash($_) . ')';
    }

    /**
     * Get appropriated reflection instance.
     *
     * @param   ...
     * @return  \Reflector
     */
    public function getReflection()
    {
        $arguments = func_get_args();
        $valid     = $this->getValidCallback($arguments);

        if (is_string($valid)) {
            return new \ReflectionFunction($valid);
        }

        if ($valid instanceof \Closure) {
            return new \ReflectionFunction($valid);
        }

        if (is_array($valid)) {
            if (is_string($valid[0])) {
                if (false === method_exists($valid[0], $valid[1])) {
                    return new \ReflectionClass($valid[0]);
                }

                return new \ReflectionMethod($valid[0], $valid[1]);
            }

            $object = new \ReflectionObject($valid[0]);

            if (null === $valid[1]) {
                return $object;
            }

            return $object->getMethod($valid[1]);
        }
    }

    /**
     * Return the hash.
     *
     * @return  string
     */
    public function __toString()
    {
        return $this->getHash();
    }
}


hoa/consistency

description: The Hoa\Consistency library.

type: library

keywords: library consistency autoloader entity flex keyword callable


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Event;

/**
 * Class \Hoa\Event\Bucket.
 *
 * This class is the object which is transmit through event channels.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Bucket
{
    /**
     * Source object.
     *
     * @var \Hoa\Event\Source
     */
    protected $_source = null;

    /**
     * Data.
     *
     * @var mixed
     */
    protected $_data   = null;

    /**
     * Set data.
     *
     * @param   mixed   $data    Data.
     */
    public function __construct($data = null)
    {
        $this->setData($data);

        return;
    }

    /**
     * Send this object on the event channel.
     *
     * @param   string             $eventId    Event ID.
     * @param   \Hoa\Event\Source  $source     Source.
     * @return  void
     */
    public function send($eventId, Source $source)
    {
        return Event::notify($eventId, $source, $this);
    }

    /**
     * Set source.
     *
     * @param   \Hoa\Event\Source  $source    Source.
     * @return  \Hoa\Event\Source
     */
    public function setSource(Source $source)
    {
        $old           = $this->_source;
        $this->_source = $source;

        return $old;
    }

    /**
     * Get source.
     *
     * @return  \Hoa\Event\Source
     */
    public function getSource()
    {
        return $this->_source;
    }

    /**
     * Set data.
     *
     * @param   mixed   $data    Data.
     * @return  mixed
     */
    public function setData($data)
    {
        $old         = $this->_data;
        $this->_data = $data;

        return $old;
    }

    /**
     * Get data.
     *
     * @return  mixed
     */
    public function getData()
    {
        return $this->_data;
    }
}


code:
# 1.17.01.13

  * Quality: Happy new year! (Alexis von Glasow, 2017-01-11T23:09:35+01:00)

# 1.16.11.19

  * Documentation: New `README.md` file. (Ivan Enderlin, 2016-10-18T16:34:43+02:00)
  * Documentation: Update `support` properties. (Ivan Enderlin, 2016-10-05T20:36:30+02:00)

# 1.16.03.15

  * Fix a typo (Metalaka, 2016-01-28T09:05:40+01:00)
  * CHANGELOG: Add a missing newline. (Ivan Enderlin, 2016-01-11T09:37:32+01:00)

# 1.16.01.11

  * Quality: Drop PHP5.4. (Ivan Enderlin, 2016-01-11T09:15:26+01:00)
  * Quality: Run devtools:cs. (Ivan Enderlin, 2016-01-09T09:01:10+01:00)
  * Core: Remove `Hoa\Core`. (Ivan Enderlin, 2016-01-09T08:14:54+01:00)
  * Consistency: Use `Hoa\Consistency`. (Ivan Enderlin, 2015-12-08T11:10:00+01:00)
  * Documentation: Fix typos. (Ivan Enderlin, 2015-11-23T22:05:56+01:00)
  * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:40:34+01:00)

# 0.15.11.23

  * Documentation: Fix typos. (Ivan Enderlin, 2015-11-23T22:05:56+01:00)
  * Exception: Use `Hoa\Exception`. (Ivan Enderlin, 2015-11-20T07:40:34+01:00)
  * Test: Fix a conflict between 2 test suites. (Ivan Enderlin, 2015-11-23T21:29:28+01:00)
  * Test: Write test suite for `Hoa\Event\Listens`. (Ivan Enderlin, 2015-11-23T21:25:49+01:00)
  * Listener: Add the `Listens` trait. (Ivan Enderlin, 2015-11-20T06:45:11+01:00)
  * Documentation: Fix a typo. (Ivan Enderlin, 2015-11-23T13:01:56+01:00)
  * Improve readability by adding constants to registered events container. (Metalaka, 2015-11-20T19:53:32+01:00)
  * Add a `.gitignore` file. (Metalaka, 2015-11-20T19:37:52+01:00)
  * README: Complete description and add usage. (Ivan Enderlin, 2015-11-13T08:43:08+01:00)
  * Test: Write test suite for `Hoa\Event\Event`. (Ivan Enderlin, 2015-11-11T17:04:41+01:00)
  * Test: Write test suite for `Hoa\Event\Exception`. (Ivan Enderlin, 2015-11-11T16:46:12+01:00)
  * Composer: Add a keyword. (Ivan Enderlin, 2015-11-11T13:38:35+01:00)
  * Test: Write test suite for `Hoa\Event\Listener`. (Ivan Enderlin, 2015-11-11T13:27:40+01:00)
  * Test: Write test suite for `Hoa\Event\Source`. (Ivan Enderlin, 2015-11-10T21:56:37+01:00)
  * Test: Write test suite for `Hoa\Event\Listenable`. (Ivan Enderlin, 2015-11-10T21:56:24+01:00)
  * Test: Write test suite for `Hoa\Event\Bucket`. (Ivan Enderlin, 2015-11-10T21:55:45+01:00)
  * Split from `Hoa\Core`. (Ivan Enderlin, 2015-11-10T21:28:31+01:00)

(first snapshot)


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Event;

use Hoa\Consistency;

/**
 * Class \Hoa\Event\Event.
 *
 * Events are asynchronous at registration, anonymous at use (until we
 * receive a bucket) and useful to largely spread data through components
 * without any known connection between them.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Event
{
    /**
     * Event ID key.
     *
     * @const int
     */
    const KEY_EVENT  = 0;

    /**
     * Source object key.
     *
     * @const int
     */
    const KEY_SOURCE = 1;

    /**
     * Static register of all observable objects, i.e. \Hoa\Event\Source
     * object, i.e. object that can send event.
     *
     * @var array
     */
    private static $_register = [];

    /**
     * Callables, i.e. observer objects.
     *
     * @var array
     */
    protected $_callable      = [];

    /**
     * Privatize the constructor.
     *
     */
    private function __construct()
    {
        return;
    }

    /**
     * Manage multiton of events, with the principle of asynchronous
     * attachments.
     *
     * @param   string  $eventId    Event ID.
     * @return  \Hoa\Event\Event
     */
    public static function getEvent($eventId)
    {
        if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
            self::$_register[$eventId] = [
                self::KEY_EVENT  => new self(),
                self::KEY_SOURCE => null
            ];
        }

        return self::$_register[$eventId][self::KEY_EVENT];
    }

    /**
     * Declare a new object in the observable collection.
     * Note: Hoa's libraries use hoa://Event/AnID for their observable objects;
     *
     * @param   string                    $eventId    Event ID.
     * @param   \Hoa\Event\Source|string  $source     Observable object or class.
     * @return  void
     * @throws  \Hoa\Event\Exception
     */
    public static function register($eventId, $source)
    {
        if (true === self::eventExists($eventId)) {
            throw new Exception(
                'Cannot redeclare an event with the same ID, i.e. the event ' .
                'ID %s already exists.',
                0,
                $eventId
            );
        }

        if (is_object($source) && !($source instanceof Source)) {
            throw new Exception(
                'The source must implement \Hoa\Event\Source ' .
                'interface; given %s.',
                1,
                get_class($source)
            );
        } else {
            $reflection = new \ReflectionClass($source);

            if (false === $reflection->implementsInterface('\Hoa\Event\Source')) {
                throw new Exception(
                    'The source must implement \Hoa\Event\Source ' .
                    'interface; given %s.',
                    2,
                    $source
                );
            }
        }

        if (!isset(self::$_register[$eventId][self::KEY_EVENT])) {
            self::$_register[$eventId][self::KEY_EVENT] = new self();
        }

        self::$_register[$eventId][self::KEY_SOURCE] = $source;

        return;
    }

    /**
     * Undeclare an object in the observable collection.
     *
     * @param   string  $eventId    Event ID.
     * @param   bool    $hard       If false, just delete the source, else,
     *                              delete source and attached callables.
     * @return  void
     */
    public static function unregister($eventId, $hard = false)
    {
        if (false !== $hard) {
            unset(self::$_register[$eventId]);
        } else {
            self::$_register[$eventId][self::KEY_SOURCE] = null;
        }

        return;
    }

    /**
     * Attach an object to an event.
     * It can be a callable or an accepted callable form (please, see the
     * \Hoa\Consistency\Xcallable class).
     *
     * @param   mixed   $callable    Callable.
     * @return  \Hoa\Event\Event
     */
    public function attach($callable)
    {
        $callable                              = xcallable($callable);
        $this->_callable[$callable->getHash()] = $callable;

        return $this;
    }

    /**
     * Detach an object to an event.
     * Please see $this->attach() method.
     *
     * @param   mixed   $callable    Callable.
     * @return  \Hoa\Event\Event
     */
    public function detach($callable)
    {
        unset($this->_callable[xcallable($callable)->getHash()]);

        return $this;
    }

    /**
     * Check if at least one callable is attached to an event.
     *
     * @return  bool
     */
    public function isListened()
    {
        return !empty($this->_callable);
    }

    /**
     * Notify, i.e. send data to observers.
     *
     * @param   string             $eventId    Event ID.
     * @param   \Hoa\Event\Source  $source     Source.
     * @param   \Hoa\Event\Bucket  $data       Data.
     * @return  void
     * @throws  \Hoa\Event\Exception
     */
    public static function notify($eventId, Source $source, Bucket $data)
    {
        if (false === self::eventExists($eventId)) {
            throw new Exception(
                'Event ID %s does not exist, cannot send notification.',
                3,
                $eventId
            );
        }

        $data->setSource($source);
        $event = self::getEvent($eventId);

        foreach ($event->_callable as $callable) {
            $callable($data);
        }

        return;
    }

    /**
     * Check whether an event exists.
     *
     * @param   string  $eventId    Event ID.
     * @return  bool
     */
    public static function eventExists($eventId)
    {
        return
            array_key_exists($eventId, self::$_register) &&
            self::$_register[$eventId][self::KEY_SOURCE] !== null;
    }
}

/**
 * Flex entity.
 */
Consistency::flexEntity('Hoa\Event\Event');


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Event;

use Hoa\Exception as HoaException;

/**
 * Class \Hoa\Event\Exception.
 *
 * Extending the \Hoa\Exception\Exception class.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
class Exception extends HoaException
{
}


code:
<?php

/**
 * Hoa
 *
 *
 * @license
 *
 * New BSD License
 *
 * Copyright © 2007-2017, Hoa community. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *     * Neither the name of the Hoa nor the names of its contributors may be
 *       used to endorse or promote products derived from this software without
 *       specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS AND CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

namespace Hoa\Event;

/**
 * Interface \Hoa\Event\Listenable.
 *
 * Each object which is listenable must implement this interface.
 *
 * @copyright  Copyright © 2007-2017 Hoa community
 * @license    New BSD License
 */
interface Listenable extends Source
{
    /**
     * Attach a callable to a listenable component.
     *
     * @param   string  $listenerId    Listener ID.
     * @param   mixed   $callable      Callable.
     * @return  \Hoa\Event\Listenable
     */
    public function on($listenerId, $callable);
}