<commit_before><?php
/**
 * SimpleComplex PHP Utils
 * @link      https://github.com/simplecomplex/php-utils
 * @copyright Copyright (c) 2017 <NAME>
 * @license   https://github.com/simplecomplex/php-utils/blob/master/LICENSE (MIT License)
 */
declare(strict_types=1);

namespace SimpleComplex\Utils;

use Psr\Log\LogLevel;
use Psr\Log\InvalidArgumentException;
use SimpleComplex\Utils\Exception\ConfigurationException;
use SimpleComplex\Utils\Exception\ParseIniException;
use SimpleComplex\Utils\Exception\ParseJsonException;

/**
 * Various helpers that do not deserve a class of their own.
 *
 * @package SimpleComplex\Utils
 */
class Utils
{
    /**
     * Reference to first object instantiated via the getInstance() method,
     * no matter which parent/child class the method was/is called on.
     *
     * @var Utils
     */
    protected static $instance;

    /**
     * First object instantiated via this method, disregarding class called on.
     *
     * @param mixed ...$constructorParams
     *
     * @return Utils
     *      static, really, but IDE might not resolve that.
     */
    public static function getInstance(...$constructorParams)
    {
        // Unsure about null ternary ?? for class and instance vars.
        if (!static::$instance) {
            static::$instance = new static(...$constructorParams);
        }
        return static::$instance;
    }

    /**
     * Get subject class name or (non-object) type.
     *
     * Counter to native gettype() this method returns:
     * - class name instead of 'object'
     * - 'float' instead of 'double'
     * - 'null' instead of 'NULL'
     *
     * Like native gettype() this method returns:
     * - 'boolean' not 'bool'
     * - 'integer' not 'int'
     * - 'unknown type' for unknown type
     *
     * @param mixed $subject
     *
     * @return string
     */
    public static function getType($subject)
    {
        if (!is_object($subject)) {
            $type = gettype($subject);
            switch ($type) {
                case 'double':
                    return 'float';
                case 'NULL':
                    return 'null';
                default:
                    return $type;
            }
        }
        return get_class($subject);
    }

    /**
     * Cast object or array to a class.
     *
     * Does not handle protected properties of neither arg (object) subject
     * nor final class instance.
     *
     * Does not support toClassName class whose constructor
     * has required parameter(s).
     *
     * @param object|array &$subject
     *      By reference.
     * @param string $toClassName
     *
     * @throws \TypeError
     *      Arg subject is not object|array.
     *      Arg subject is object whose class isn't a parent of arg toClassName.
     *      Propagated; toClassName class constructor has required parameter(s).
     */
    public static function cast(&$subject, string $toClassName) /*:void*/
    {
        if (is_object($subject)) {
            $from_class_name = get_class($subject);
            if ($from_class_name != \stdClass::class && !in_array($from_class_name, class_parents($toClassName))) {
                throw new \TypeError(
                    'Can\'t cast arg subject, class[' . $from_class_name
                    . '] is not parent class of arg toClassName[' . $toClassName . '].'
                );
            }
            $source_props = get_object_vars($subject);
        } elseif (!is_array($subject)) {
            throw new \TypeError('Arg subject type[' . static::getType($subject) . '] is not object or array.');
        } else {
            // Copy.
            $source_props = $subject;
        }
        // would break if subject is array and argument wasn't by reference.
        $subject = new $toClassName();
        foreach ($source_props as $key => &$value) {
            $subject->{$key} = $value;
        }
        // Iteration ref.
        unset($value);
    }

    /**
     * Class name without namespace.
     *
     * @param string $className
     *
     * @return string
     */
    public function classUnqualified(string $className) : string
    {
        $pos = strrpos($className, '\\');
        return $pos || $pos === 0 ? substr($className, $pos + 1) : $className;
    }

    /**
     * Merge arrays recursively, letting numerically indexed key values append
     * and dupe associative key values overwrite.
     *
     * Native array_replace[_recursive]():
     * - values of dupe keys always overwrite;
     *   no matter if numeric or accociative keys
     *
     * Native array_merge[_recursive]():
     * - values of dupe numeric keys get appended
     * - values of dupe accociative keys get dumped into sub array;
     *   in effect converting duplicate key values to array (sic!)
     *
     * @see array_replace_recursive()
     * @see array_merge_recursive()
     *
     * @param array $array0
     * @param array[] $arrayN
     *      Any number of arguments.
     *
     * @return array
     */
    function arrayMergeRecursive(array $array0, array ...$arrayN)
    {
        foreach ($arrayN as $arr) {
            if (!$array0) {
                $array0 = $arr;
            } elseif ($arr) {
                foreach ($arr as $key => $value) {
                    // Dupe key.
                    if (array_key_exists($key, $array0)) {
                        // Both values array: recurse.
                        if (is_array($value) && is_array($array0[$key])) {
                            $array0[$key] = static::arrayMergeRecursive($array0[$key], $value);
                        }
                        // Numeric key: append.
                        elseif (!$key || ctype_digit('' . $key)) {
                            $array0[] = $value;
                        }
                        // Associative key: overwrite.
                        else {
                            $array0[$key] = $value;
                        }
                    }
                    // Non-dupe key: append.
                    else {
                        $array0[$key] = $value;
                    }
                }
            }
        }
        return $array0;
    }
<commit_msg>Utils::arrayMergeRecursive() to combine the best of native   array_replace_recursive() and array_merge_recursive(), and avoid the latter's   crazy behaviour of dupe associative key scalar values become array.<commit_after><?php
/**
 * SimpleComplex PHP Utils
 * @link      https://github.com/simplecomplex/php-utils
 * @copyright Copyright (c) 2017 <NAME>
 * @license   https://github.com/simplecomplex/php-utils/blob/master/LICENSE (MIT License)
 */
declare(strict_types=1);

namespace SimpleComplex\Utils;

use Psr\Log\LogLevel;
use Psr\Log\InvalidArgumentException;
use SimpleComplex\Utils\Exception\ConfigurationException;
use SimpleComplex\Utils\Exception\ParseIniException;
use SimpleComplex\Utils\Exception\ParseJsonException;

/**
 * Various helpers that do not deserve a class of their own.
 *
 * @package SimpleComplex\Utils
 */
class Utils
{
    /**
     * Reference to first object instantiated via the getInstance() method,
     * no matter which parent/child class the method was/is called on.
     *
     * @var Utils
     */
    protected static $instance;

    /**
     * First object instantiated via this method, disregarding class called on.
     *
     * @param mixed ...$constructorParams
     *
     * @return Utils
     *      static, really, but IDE might not resolve that.
     */
    public static function getInstance(...$constructorParams)
    {
        // Unsure about null ternary ?? for class and instance vars.
        if (!static::$instance) {
            static::$instance = new static(...$constructorParams);
        }
        return static::$instance;
    }

    /**
     * Get subject class name or (non-object) type.
     *
     * Counter to native gettype() this method returns:
     * - class name instead of 'object'
     * - 'float' instead of 'double'
     * - 'null' instead of 'NULL'
     *
     * Like native gettype() this method returns:
     * - 'boolean' not 'bool'
     * - 'integer' not 'int'
     * - 'unknown type' for unknown type
     *
     * @param mixed $subject
     *
     * @return string
     */
    public static function getType($subject)
    {
        if (!is_object($subject)) {
            $type = gettype($subject);
            switch ($type) {
                case 'double':
                    return 'float';
                case 'NULL':
                    return 'null';
                default:
                    return $type;
            }
        }
        return get_class($subject);
    }

    /**
     * Cast object or array to a class.
     *
     * Does not handle protected properties of neither arg (object) subject
     * nor final class instance.
     *
     * Does not support toClassName class whose constructor
     * has required parameter(s).
     *
     * @param object|array &$subject
     *      By reference.
     * @param string $toClassName
     *
     * @throws \TypeError
     *      Arg subject is not object|array.
     *      Arg subject is object whose class isn't a parent of arg toClassName.
     *      Propagated; toClassName class constructor has required parameter(s).
     */
    public static function cast(&$subject, string $toClassName) /*:void*/
    {
        if (is_object($subject)) {
            $from_class_name = get_class($subject);
            if ($from_class_name != \stdClass::class && !in_array($from_class_name, class_parents($toClassName))) {
                throw new \TypeError(
                    'Can\'t cast arg subject, class[' . $from_class_name
                    . '] is not parent class of arg toClassName[' . $toClassName . '].'
                );
            }
            $source_props = get_object_vars($subject);
        } elseif (!is_array($subject)) {
            throw new \TypeError('Arg subject type[' . static::getType($subject) . '] is not object or array.');
        } else {
            // Copy.
            $source_props = $subject;
        }
        // would break if subject is array and argument wasn't by reference.
        $subject = new $toClassName();
        foreach ($source_props as $key => &$value) {
            $subject->{$key} = $value;
        }
        // Iteration ref.
        unset($value);
    }

    /**
     * Class name without namespace.
     *
     * @param string $className
     *
     * @return string
     */
    public function classUnqualified(string $className) : string
    {
        $pos = strrpos($className, '\\');
        return $pos || $pos === 0 ? substr($className, $pos + 1) : $className;
    }

    /**
     * Merge arrays recursively, letting numerically indexed key values append
     * and dupe associative key values overwrite.
     *
     * Native array_replace[_recursive]():
     * - values of dupe keys always overwrite;
     *   no matter if numeric or accociative keys
     *
     * Native array_merge[_recursive]():
     * - values of dupe numeric keys get appended
     * - values of dupe accociative keys get dumped into sub array;
     *   in effect converting duplicate key values to array (sic!)
     *
     * @see array_replace_recursive()
     * @see array_merge_recursive()
     *
     * @param array $array0
     * @param array[] $arrayN
     *      Any number of arguments.
     *
     * @return array
     */
    function arrayMergeRecursive(array $array0, array ...$arrayN)
    {
        foreach ($arrayN as $arr) {
            if (!$array0) {
                $array0 = $arr;
            } elseif ($arr) {
                foreach ($arr as $key => $value) {
                    // Dupe key.
                    if (array_key_exists($key, $array0)) {
                        // Both values array: recurse.
                        if (is_array($value) && is_array($array0[$key])) {
                            $array0[$key] = $this->arrayMergeRecursive($array0[$key], $value);
                        }
                        // Numeric key: append.
                        elseif (!$key || ctype_digit('' . $key)) {
                            $array0[] = $value;
                        }
                        // Associative key: overwrite.
                        else {
                            $array0[$key] = $value;
                        }
                    }
                    // Non-dupe key: append.
                    else {
                        $array0[$key] = $value;
                    }
                }
            }
        }
        return $array0;
    }

{'fraction_non_alphanumeric': 0.11072390305718359, 'fraction_numerical': 0.0030096626009821006, 'mean_word_length': 1.686595744680851, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 12, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '709973', 'n_tokens_mistral': 3434, 'n_tokens_neox': 3312, 'n_words': 981}