diff --git a/CHANGELOG.md b/CHANGELOG.md index 198bc84..8613f00 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +0.5.0 / 2025-12-08 + - Refactored `ppd` function for improved CLI test handling with conditional exit strategy + - Added support for automatic object-to-array conversion in `PrettyPrint` (`asArray()`/`toArray()`) + - Added namespace declaration to global helper functions + 0.4.3 / 2025-12-05 - Default precision increased from 2 to 4 - Refactor `PrettyPrint` for modularity: extract formatting logic into dedicated private methods diff --git a/README.md b/README.md index 4f01428..af37473 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,20 @@ composer require apphp/pretty-print Note: When used in web (non-CLI) environments, output is automatically wrapped in `
` to preserve spacing. In CLI, no wrapping is applied. When you request a string to be returned (see `return` option), no auto-wrapping is applied.
 
+### Import functions
+
+All examples below assume you have imported the helper functions from the `Apphp\PrettyPrint` namespace, for example:
+
+```php
+use function Apphp\PrettyPrint\pprint;
+use function Apphp\PrettyPrint\pp;
+use function Apphp\PrettyPrint\ppd;
+```
+or simply
+```php
+use function Apphp\PrettyPrint\{pprint, pp, ppd};
+```
+
 ### Global helper functions
 
 Print scalars/strings
@@ -153,6 +167,31 @@ $pp($tensor3d, headB: 2, tailB: 1, headRows: 1, tailRows: 1, headCols: 1, tailCo
 $pp('Metrics:', [[0.91, 0.02], [0.03, 0.88]]);
 ```
 
+### Objects with `asArray()` / `toArray()`
+
+If you pass an object that exposes an `asArray()` or `toArray()` method, `pprint` / `PrettyPrint` will automatically convert it to an array before formatting:
+
+```php
+class UserCollection
+{
+    public function asArray(): array
+    {
+        return [
+            ['id' => 1, 'name' => 'Alice'],
+            ['id' => 2, 'name' => 'Bob'],
+        ];
+    }
+}
+
+$users = new UserCollection();
+
+pprint($users);
+// [[1, 'Alice'],
+//  [2, 'Bob']]
+```
+
+If `asArray()` is not present but `toArray()` is, `toArray()` will be used instead.
+
 ## Running tests
 
 ```bash
diff --git a/src/PrettyPrint.php b/src/PrettyPrint.php
index ff41711..db3c28b 100644
--- a/src/PrettyPrint.php
+++ b/src/PrettyPrint.php
@@ -249,6 +249,18 @@ private function normalizeArgs(array $args): array
             }
         }
         $args = array_values($args);
+
+        // Convert objects to arrays if possible
+        foreach ($args as $i => $value) {
+            if (is_object($value)) {
+                if (is_callable([$value, 'asArray'])) {
+                    $args[$i] = $value->asArray();
+                } elseif (is_callable([$value, 'toArray'])) {
+                    $args[$i] = $value->toArray();
+                }
+            }
+        }
+
         if (count($args) > self::MAX_ARGS) {
             $args = array_slice($args, 0, self::MAX_ARGS);
         }
@@ -263,8 +275,7 @@ private function normalizeArgs(array $args): array
      * @param array $fmt
      * @param string $start
      * @param string $end
-     * @param int $prevPrecision
-     * @return bool True if handled, false otherwise
+     * @return string|null True if handled, false otherwise
      */
     private function tryLabel3D(array $args, array $fmt, string $start, string $end): ?string
     {
@@ -292,8 +303,7 @@ private function tryLabel3D(array $args, array $fmt, string $start, string $end)
      * @param array $args
      * @param string $start
      * @param string $end
-     * @param int $prevPrecision
-     * @return bool True if handled, false otherwise
+     * @return string|null True if handled, false otherwise
      */
     private function tryLabel2D(array $args, string $start, string $end): ?string
     {
diff --git a/src/functions.php b/src/functions.php
index bcd953d..10cd2f2 100644
--- a/src/functions.php
+++ b/src/functions.php
@@ -2,43 +2,47 @@
 
 declare(strict_types=1);
 
-namespace {
-
-    use Apphp\PrettyPrint\PrettyPrint;
-
-    /**
-     * Convenience wrapper around PrettyPrint's callable interface.
-     * @param ...$args
-     * @return string
-     */
-    function pprint(...$args): string
-    {
-        return (new PrettyPrint())(...$args);
-    }
-
-    /**
-     * Alias for pprint
-     * @param ...$args
-     * @return string
-     */
-    function pp(...$args): string
-    {
-        return pprint(...$args);
-    }
+namespace Apphp\PrettyPrint;
+
+use Apphp\PrettyPrint\Env;
+use Apphp\PrettyPrint\PrettyPrint;
+
+/**
+ * Convenience wrapper around PrettyPrint's callable interface.
+ * @param ...$args
+ * @return string
+ */
+function pprint(...$args): string
+{
+    return (new PrettyPrint())(...$args);
+}
 
-    /**
-     * Alias for pprint with exit after printing
-     * @param ...$args
-     * @return string
-     */
-    function ppd(...$args): string
-    {
-        return pprint(...$args);
+/**
+ * Alias for pprint
+ * @param ...$args
+ * @return string
+ */
+function pp(...$args): string
+{
+    return pprint(...$args);
+}
 
-        if (PHP_SAPI === 'cli' && getenv('APP_ENV') === 'test') {
-            return '';
-        }
+/**
+ * Alias for pprint with exit after printing
+ * @param ...$args
+ */
+function ppd(...$args)
+{
+    $exiter = null;
 
-        exit;
+    if (Env::isCli() && getenv('APP_ENV') === 'test') {
+        $exiter = fn() => null;
     }
+
+    $exiter ??= fn() => exit;
+
+    // Execute behavior
+    pprint(...$args);
+    $exiter();
 }
+
diff --git a/tests/FunctionsTest.php b/tests/FunctionsTest.php
index 146b5e6..ee89131 100644
--- a/tests/FunctionsTest.php
+++ b/tests/FunctionsTest.php
@@ -11,11 +11,12 @@
 use PHPUnit\Framework\Attributes\CoversFunction;
 use PHPUnit\Framework\Attributes\RunInSeparateProcess;
 use PHPUnit\Framework\Attributes\PreserveGlobalState;
+use function Apphp\PrettyPrint\{pprint, pp, ppd};
 
-#[Group('prettyprint')]
-#[CoversFunction('pprint')]
-#[CoversFunction('pp')]
-#[CoversFunction('ppd')]
+#[Group('PrettyPrint')]
+#[CoversFunction('Apphp\\PrettyPrint\\pprint')]
+#[CoversFunction('Apphp\\PrettyPrint\\pp')]
+#[CoversFunction('Apphp\\PrettyPrint\\ppd')]
 final class FunctionsTest extends TestCase
 {
     #[Test]
@@ -47,4 +48,40 @@ public function ppdPrintsThenExits(): void
         $this->expectOutputString("hello\n");
         ppd('hello');
     }
+
+    #[Test]
+    #[TestDox('pprint uses asArray() when object provides it')]
+    public function pprintUsesAsArrayOnObject(): void
+    {
+        $obj = new class {
+            public function asArray(): array
+            {
+                return [1, 2, 3];
+            }
+        };
+
+        ob_start();
+        pprint($obj);
+        $out = ob_get_clean();
+
+        self::assertSame("[1, 2, 3]\n", $out);
+    }
+
+    #[Test]
+    #[TestDox('pprint falls back to toArray() when object has no asArray()')]
+    public function pprintUsesToArrayOnObject(): void
+    {
+        $obj = new class {
+            public function toArray(): array
+            {
+                return ['a' => 1, 'b' => 2];
+            }
+        };
+
+        ob_start();
+        pprint($obj);
+        $out = ob_get_clean();
+
+        self::assertSame("[1, 2]\n", $out);
+    }
 }
diff --git a/tests/PrettyPrintTest.php b/tests/PrettyPrintTest.php
index 689facc..c5f3acb 100644
--- a/tests/PrettyPrintTest.php
+++ b/tests/PrettyPrintTest.php
@@ -514,4 +514,44 @@ public function defaultFormatterUsesFormatForArrayFor1D(): void
         $out = ob_get_clean();
         self::assertSame("[1, 2, 3]\n", $out);
     }
+
+    #[Test]
+    #[TestDox('converts objects with asArray() to arrays before formatting')]
+    public function convertsObjectWithAsArrayToArray(): void
+    {
+        $pp = new PrettyPrint();
+
+        $obj = new class {
+            public function asArray(): array
+            {
+                return [10, 20, 30];
+            }
+        };
+
+        ob_start();
+        $pp($obj);
+        $out = ob_get_clean();
+
+        self::assertSame("[10, 20, 30]\n", $out);
+    }
+
+    #[Test]
+    #[TestDox('converts objects with toArray() to arrays when asArray() is not available')]
+    public function convertsObjectWithToArrayToArray(): void
+    {
+        $pp = new PrettyPrint();
+
+        $obj = new class {
+            public function toArray(): array
+            {
+                return ['x' => 5, 'y' => 6];
+            }
+        };
+
+        ob_start();
+        $pp($obj);
+        $out = ob_get_clean();
+
+        self::assertSame("[5, 6]\n", $out);
+    }
 }