themichaelhall/debug

View on GitHub
src/VarDump.php

Summary

Maintainability
A
2 hrs
Test Coverage
<?php
/**
 * This file is a part of the debug package.
 *
 * Read more at https://github.com/themichaelhall/debug
 */
declare(strict_types=1);

namespace MichaelHall\Debug;

/**
 * Class VarDump.
 *
 * @since 1.0.0
 */
class VarDump
{
    /**
     * Dumps a variable as a readable string.
     *
     * @since 1.0.0
     *
     * @param mixed $var The variable.
     *
     * @return string The variable as a readable string.
     */
    public static function toString($var): string
    {
        return self::toStringRecursive($var, 0, []);
    }

    /**
     * Writes a variable as a readable string.
     *
     * @since 1.0.0
     *
     * @param mixed $var The variable.
     */
    public static function write($var): void
    {
        echo self::toString($var);
    }

    /**
     * Dumps a variable as a readable string.
     *
     * @param mixed $var             The variable.
     * @param int   $indent          The indent.
     * @param array $previousObjects The objects previously handled.
     *
     * @return string The variable as a readable string.
     */
    private static function toStringRecursive($var, int $indent, array $previousObjects): string
    {
        if (is_resource($var)) {
            return '"' . get_resource_type($var) . ' #' . (int) $var . '" (Resource)';
        }

        if (is_object($var)) {
            return self::objectToString($var, $indent, $previousObjects);
        }

        if (is_array($var)) {
            return self::arrayToString($var, $indent, $previousObjects);
        }

        if (is_string($var)) {
            return '"' . $var . '" string[' . mb_strlen($var) . ']';
        }

        if (is_float($var)) {
            return $var . ' float';
        }

        if (is_int($var)) {
            return $var . ' int';
        }

        if (is_bool($var)) {
            return ($var ? 'true' : 'false') . ' bool';
        }

        return 'null';
    }

    /**
     * Converts an object to a string.
     *
     * @param mixed $var             The object.
     * @param int   $indent          The indent.
     * @param array $previousObjects The objects previously handled.
     *
     * @return string The object as a string.
     */
    private static function objectToString($var, int $indent, array $previousObjects): string
    {
        $indentString = str_repeat('  ', $indent);
        $stringLabel = self::getStringLabel($var);

        $result = ($stringLabel !== null ? '"' . $stringLabel . '" ' : '') . get_class($var);

        foreach ($previousObjects as $previousObject) {
            if ($previousObject === $var) {
                $result .= ' *RECURSION*';

                return $result;
            }
        }

        $result .= "\n" . $indentString . "{\n";

        $reflectionClass = new \ReflectionClass($var);
        foreach (self::getReflectionClassProperties($reflectionClass) as $property) {
            $property->setAccessible(true);
            $result .= $indentString . '  ' . $property->getName() . ' => ' . self::toStringRecursive($property->getValue($var), $indent + 1, array_merge($previousObjects, [$var])) . "\n";
        }

        $result .= $indentString . '}';

        return $result;
    }

    /**
     * Converts an array to a string.
     *
     * @param array $var             The array.
     * @param int   $indent          The indent.
     * @param array $previousObjects The objects previously handled.
     *
     * @return string The array as a string.
     */
    private static function arrayToString(array $var, int $indent, array $previousObjects): string
    {
        $indentString = str_repeat('  ', $indent);
        $result = 'array[' . count($var) . "]\n" . $indentString . "[\n";

        foreach ($var as $key => $value) {
            $result .= $indentString . '  ' . self::toString($key) . ' => ' . self::toStringRecursive($value, $indent + 1, $previousObjects) . "\n";
        }

        $result .= $indentString . ']';

        return $result;
    }

    /**
     * Returns the properties for a reflection class and its base classes.
     *
     * @param \ReflectionClass $reflectionClass The reflection class.
     *
     * @return \ReflectionProperty[] The reflection properties.
     */
    private static function getReflectionClassProperties(\ReflectionClass $reflectionClass): array
    {
        $properties = [];

        do {
            foreach ($reflectionClass->getProperties() as $property) {
                if (!in_array($property, $properties)) {
                    $properties[] = $property;
                }
            }

            $reflectionClass = $reflectionClass->getParentClass();
        } while ($reflectionClass !== false);

        return $properties;
    }

    /**
     * Returns a string label for an object or null if no string label could be created.
     *
     * @param mixed $var The object.
     *
     * @return null|string The string label for an object or null if no string label could be created.
     */
    private static function getStringLabel($var): ?string
    {
        if (method_exists($var, '__toString')) {
            return $var->__toString();
        }

        if ($var instanceof \DateTimeInterface) {
            return $var->format('Y-m-d H:i:s.u T');
        }

        if ($var instanceof \DateInterval) {
            return $var->format('%R%yy %mm %dd %hh %im %ss');
        }

        return null;
    }
}