Skip to content

Comments

Refactor how table data is stored#68

Merged
bkdotcom merged 39 commits into3.6from
Table
Feb 19, 2026
Merged

Refactor how table data is stored#68
bkdotcom merged 39 commits into3.6from
Table

Conversation

@bkdotcom
Copy link
Owner

@bkdotcom bkdotcom commented Feb 8, 2026

New Table utilities / classes for representing table data

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a new Table module providing structured table rendering with improved HTML output organization.
    • Enhanced debug abstraction handling with refined object serialization and structured value representation.
  • Refactor

    • Added strict array type hints across constructors and methods to improve type safety and code reliability.
    • Redesigned table and trace data rendering for improved organization and consistency in debug output.
  • Tests

    • Added comprehensive test coverage for new table components and abstraction changes.

@coderabbitai
Copy link

coderabbitai bot commented Feb 8, 2026

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch Table

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

* 3.6:
  Laravel/ServiceProvider::$logDb undefined prop
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 11

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
src/Teams/Elements/Table.php (1)

27-36: ⚠️ Potential issue | 🟠 Major

Constructor type hint breaks Traversable inputs (use iterable).

asRows() and the docblock accept Traversable, but the new array type hint rejects it, causing a TypeError for iterator-based callers. This is a behavioral regression.

✅ Suggested fix
-    public function __construct(array $rows = array())
+    public function __construct(iterable $rows = array())
tests/Debug/Plugin/Method/TraceTest.php (1)

128-171: ⚠️ Potential issue | 🟠 Major

Fix HTML trace expectations to use table rows from getValues()

$logEntry['args'][0] is now a table abstraction; numeric indexing ($trace[$i - 1]) is legacy and will yield null/incorrect data. Also, the index column is already present in the row data, so prefixing it duplicates the index. Build expectations from getValues()['rows'] and use children when present.

Proposed fix
-                'html' => function ($output, LogEntry $logEntry) {
-                    $trace = $logEntry['args'][0];
+                'html' => function ($output, LogEntry $logEntry) {
+                    $traceValues = $logEntry['args'][0]->getValues();
                     self::assertStringContainsString('<caption>trace</caption>' . "\n"
                         . '<thead>' . "\n"
                         . '<tr>' . "\n"
                         . '<th class="t_string" scope="col"></th>' . "\n"
                         . '<th class="t_string" scope="col">file</th>' . "\n"
                         . '<th class="t_string" scope="col">line</th>' . "\n"
                         . '<th class="t_string" scope="col">function</th>' . "\n"
                         . '</tr>' . "\n"
                         . '</thead>', self::normalizeString($output));
@@
-                    for ($i = 1; $i < $count; $i++) {
+                    for ($i = 1; $i < $count; $i++) {
                         // build expected values
-
-                        $valuesExpect = \array_merge(
-                            [(string) ($i - 1)],
-                            \array_values($trace[$i - 1])
-                        );
+                        $row = $traceValues['rows'][$i - 1];
+                        $rowValues = isset($row['children']) ? $row['children'] : $row;
+                        $valuesExpect = \array_values($rowValues);
+                        $valuesExpect[0] = (string) $valuesExpect[0];
 
                         $valuesExpect[1] = $this->debug->getDump('html')->valDumper->dump($valuesExpect[1], array('tagName' => null));
                         $valuesExpect[2] = $valuesExpect[2] === null ? 'null' : (string) $valuesExpect[2];
                         $valuesExpect[3] = $this->debug->getDump('html')->valDumper->dump($valuesExpect[3], array('tagName' => null));
src/Debug/Plugin/Method/Trace.php (1)

120-139: ⚠️ Potential issue | 🔴 Critical

Fix argument mismatch when calling getTraceFinish.
Line 138 passes $meta, but getTraceFinish only accepts one parameter (Line 184), which raises an ArgumentCountError in PHP 8+. Remove the extra argument.

🐛 Suggested fix
-        return $this->getTraceFinish($trace, $meta);
+        return $this->getTraceFinish($trace);
src/Debug/js_src/FileLinks.js (1)

115-133: ⚠️ Potential issue | 🟡 Minor

Fix remove path to avoid leaving extra header/colspan and creating links.
When remove is true and no links exist, this now adds a header and link column. When removing existing links, the header and context-row colspan aren’t reverted, leaving the table misaligned.

🔧 Proposed fix
 function createFileLinksTrace ($entry, remove) {
   var isUpdate = $entry.find('.file-link').length > 0
-  if (!isUpdate) {
-    $entry.find('table thead tr > *:last-child').after('<th></th>')
-  } else if (remove) {
-    $entry.find('table tr:not(.context) > *:last-child').remove()
-    return
-  }
+  if (remove) {
+    if (isUpdate) {
+      $entry.find('table thead tr > *:last-child').remove()
+      $entry.find('table tbody tr:not(.context) > *:last-child').remove()
+      $entry.find('table tbody tr.context').each(function () {
+        var $td0 = $(this).find('> td').eq(0)
+        var colspan = parseInt($td0.attr('colspan'), 10)
+        if (!isNaN(colspan) && colspan > 1) {
+          $td0.attr('colspan', colspan - 1)
+        }
+      })
+    }
+    return
+  }
+  if (!isUpdate) {
+    $entry.find('table thead tr > *:last-child').after('<th></th>')
+  }
   $entry.find('table tbody tr:not(.context)').each(function () {
     createFileLinksTraceProcessTr($(this), isUpdate)
   })
   if (isUpdate) {
     return;
   }
src/Debug/Abstraction/Abstracter.php (1)

62-62: ⚠️ Potential issue | 🟡 Minor

PHPDoc syntax error: malformed type annotation.

The type annotation array>string,mixed> should be array<string,mixed>.

-    /** `@var` array>string,mixed> */
+    /** `@var` array<string,mixed> */
🤖 Fix all issues with AI agents
In `@src/Container/ObjectBuilder.php`:
- Around line 82-96: The build method currently ignores the $useContainer flag
by always checking $this->container->has($classname) first; change the logic so
you only consult the container when $useContainer is true (e.g., guard the
container->has and container->get calls with $useContainer) and likewise only
call $this->container->offsetSet($classname, $return) when $useContainer is
true; update the build(...) implementation (referencing build, $useContainer,
$this->container->has, $this->container->get, and $this->container->offsetSet)
so that setting and returning container instances are skipped when $useContainer
is false.

In `@src/Debug/Abstraction/Abstracter.php`:
- Line 20: The unconditional top-level import "use SensitiveParameterValue" and
the runtime "instanceof SensitiveParameterValue" check in Abstracter.php will
crash on PHP <8.2; remove the unconditional use and change the runtime check to
be guarded (e.g., if (defined('PHP_VERSION_ID') && PHP_VERSION_ID >= 80200) { /*
instanceof check */ } or use if (class_exists('SensitiveParameterValue')) { /*
instanceof check using fully-qualified class name */ }), or alternatively update
composer.json to require PHP >= 8.2; ensure all references in the Abstracter
class to SensitiveParameterValue use the guarded path or class_exists guard so
older PHP versions never try to import or evaluate that symbol.

In `@src/Debug/Abstraction/Object/Subscriber.php`:
- Around line 301-314: The onStartTable method can leave
tableCellValueAbstracter subscribed and isAbstractingTable true if
jsonSerialize() or crate() throws; wrap the core work (subscribe, values
assignment, crate call, setting values['type'] and abs['unstructuredValue']) in
a try/finally so you always call
$debug->eventManager->unsubscribe(Debug::EVENT_OBJ_ABSTRACT_END, [$this,
'tableCellValueAbstracter']) and set $this->isAbstractingTable = false in the
finally block; ensure unsubscribe and flag-reset occur even on exceptions so
subsequent abstractions are not affected.

In `@src/Debug/Abstraction/Type.php`:
- Around line 190-200: The code uses "instanceof UnitEnum" which fatal-errors on
PHP <8.1; update the check to be guarded so older PHP versions skip it (for
example replace the raw instanceof UnitEnum check with a conditional that first
verifies the symbol exists, e.g., class_exists('UnitEnum') or PHP_VERSION_ID >=
80100) so the branch that sets TYPE_OBJ_ENUM only runs when UnitEnum is
available; modify the elseif that references UnitEnum in the method returning
[self::TYPE_OBJECT, $typeMore] (and keep using TYPE_OBJ_ENUM / TYPE_OBJECT
unchanged) or alternatively bump the composer platform requirement to PHP 8.1+
if you prefer to drop backwards compatibility.

In `@src/Debug/Dump/Html/Table.php`:
- Around line 151-156: In buildContextRow(TableRow $row, $expanded) remove the
invalid second argument to addClass: call $row->addClass('expanded') only when
$expanded is truthy (i.e., wrap the addClass call in the existing if ($expanded)
block) and leave the subsequent $row->setAttrib('data-toggle', 'next') unchanged
so no extra argument is passed to Element::addClass.

In `@src/Debug/Dump/TextAnsi/Value.php`:
- Around line 336-352: In dumpTable(Abstraction $abs) avoid calling
\bdk\Table\Utility::asArray() when the BdkTable has no header: first construct
$table = new BdkTable($abs->getValues()), check $table->getHeader() for null and
if null return $this->dumpArray($abs->getValues()) (or
$this->dumpArray($table->getValues())) as a fallback; only call
\bdk\Table\Utility::asArray($table) and proceed with caption handling/dumping
when getHeader() is non-null so Utility::asArray() never receives a table
without a header.

In `@src/Debug/Framework/WordPress/Shortcodes.php`:
- Around line 99-105: The code is concatenating Abstraction objects returned by
$cells[2]->getValue() (e.g., $links and similarly $method) which calls
__toString() and drops metadata; instead, extract the raw string/value from the
Abstraction, perform the concatenation to build the HTML fragment, then re-wrap
the resulting string with crateWithVals() (preserving flags like
addQuotes/sanitize) before calling $cells[2]->setValue(...); apply the same
pattern for the other occurrence around $method so metadata is retained when
calling setValue() and buildInfoRow().

In `@src/Debug/LogEntry.php`:
- Around line 231-236: The code uses getMeta('detectFiles', false) which forces
a false default and overrides global config; change it so getMeta('detectFiles')
(or getMeta('detectFiles', null)) is used and only call
subject->abstracter->abstractString->setCfg('detectFiles', $detectFiles) when
the meta is not null, and only populate $cfgRestore['detectFiles'] when setCfg
returned a different previous value; update the DetectFiles handling around the
variables $detectFiles, $detectFilesWas and $cfgRestore to preserve global
behavior when meta is absent.

In `@src/Table/Element.php`:
- Around line 466-471: The unserialize method currently calls
\unserialize($data) which allows object instantiation from untrusted input;
change it to use the allowed_classes option to block object injection (e.g. call
\unserialize($data, ['allowed_classes' => false])) and keep the existing check
that only arrays are passed to __unserialize; for PHP versions older than 7
(PHP_VERSION_ID < 70000) fall back to the current \unserialize($data) behavior
behind a version check so compatibility with PHP 5.x is preserved, referencing
the unserialize method and the __unserialize call to locate the change.

In `@src/Table/TableRow.php`:
- Around line 76-84: The setChildren method currently treats any Element as
acceptable; change the instanceof check to require TableCell so only TableCell
instances are preserved: in TableRow::setChildren, map $children and replace the
conditional ($child instanceof Element) with ($child instanceof TableCell),
otherwise construct new TableCell($child), then call
parent::setChildren($children) as before to ensure rows only contain TableCell
objects.

In `@src/Table/Utility.php`:
- Around line 34-40: self::$tempData assignment calls
$table->getHeader()->getChildren() without checking for null, causing a fatal
error when getHeader() returns null; update the 'headerVals' computation in the
self::$tempData block to first check $table->getHeader() (or retrieve
$table->getHeader() into a variable) and only call ->getChildren() when it's not
null, otherwise use an empty array for headerVals, leaving the array_map logic
(and the 'columnsMeta' and 'rowKey' entries) unchanged so behavior is preserved
when a header exists.
🟡 Minor comments (11)
src/Debug/Utility/Profile.php-38-41 (1)

38-41: ⚠️ Potential issue | 🟡 Minor

BC break confirmed: Constructor now strictly requires array values.

The type narrowing at line 40 from untyped to array does break callers passing null or other non-array values, though the defensive cast at line 42 suggests this was intentional. All internal call sites in the repository use the default parameter and are unaffected. External library users who previously passed non-array values will receive a TypeError.

If this is a deliberate API tightening, ensure the version bump and migration guide reflect the breaking change.

src/Debug/Plugin/Method/Trace.php-253-265 (1)

253-265: ⚠️ Potential issue | 🟡 Minor

Guard against non‑method frames. Line 258 assumes Class->method format but traces contain Class::staticMethod, {closure}, and fully qualified function names. When preg_match fails to match these patterns, accessing $matches['classname'] triggers an undefined index notice. Add validation before accessing the array.

💡 Suggested fix
 private function removeMinInternal(array $trace)
 {
     $count = \count($trace);
     $internalClasses = [__CLASS__, 'bdk\PubSub\Manager'];
     for ($i = 3; $i < $count; $i++) {
-        \preg_match('/^(?P<classname>.+)->(?P<function>.+)$/', $trace[$i]['function'], $matches);
-        $isInternal = \in_array($matches['classname'], $internalClasses, true) || $matches['function'] === 'publishBubbleEvent';
+        if (!isset($trace[$i]['function'])
+            || \preg_match('/^(?P<classname>.+)->(?P<function>.+)$/', $trace[$i]['function'], $matches) !== 1
+        ) {
+            break;
+        }
+        $isInternal = \in_array($matches['classname'], $internalClasses, true)
+            || $matches['function'] === 'publishBubbleEvent';
         if ($isInternal === false) {
             break;
         }
     }
     return \array_slice($trace, $i);
 }
src/Debug/Route/Text.php-28-28 (1)

28-28: ⚠️ Potential issue | 🟡 Minor

Avoid overwriting a preconfigured dumper.

If callers set a custom dumper before route construction, this now unconditionally replaces it. Consider guarding the assignment.

Suggested guard
-        $this->dumper = $debug->getDump('text');
+        if (!$this->dumper) {
+            $this->dumper = $debug->getDump('text');
+        }
tests/Table/TableCellTest.php-593-600 (1)

593-600: ⚠️ Potential issue | 🟡 Minor

Drop unused $html1 variable.

PHPMD flagged Line 594 as unused; removing the assignment keeps the test clean.

🛠️ Suggested fix
-        $html1 = $cell->getHtml();
+        $cell->getHtml();
tests/Table/TableCellTest.php-31-41 (1)

31-41: ⚠️ Potential issue | 🟡 Minor

Capture the original val dumper before overriding.

Currently you call TableCell::setValDumper(...) before capturing the original value, so tearDown restores the default instead of the prior global state. This can leak between tests.

🛠️ Suggested fix
-        TableCell::setValDumper(['bdk\Table\TableCell', 'valDumper']);
-        // Store original dumper
-        $reflection = new \ReflectionClass(self::CLASS_TABLE_CELL);
+        // Store original dumper
+        $reflection = new \ReflectionClass(self::CLASS_TABLE_CELL);
         $property = $reflection->getProperty('valDumper');
         if (PHP_VERSION_ID < 80100) {
             $property->setAccessible(true);
         }
         self::$originalValDumper = $property->getValue();
+        TableCell::setValDumper(['bdk\Table\TableCell', 'valDumper']);
src/Debug/js/Debug.js-783-792 (1)

783-792: ⚠️ Potential issue | 🟡 Minor

Guard against missing colspan in context rows.

If a context row lacks a colspan attribute, parseInt returns NaN and the attribute becomes NaN. A safe default avoids layout glitches.

🛠️ Suggested fix
-      var $td0 = $(this).find('> td').eq(0);
-      $td0.attr('colspan', parseInt($td0.attr('colspan'), 10) + 1);
+      var $td0 = $(this).find('> td').eq(0);
+      var colspan = parseInt($td0.attr('colspan'), 10) || 1;
+      $td0.attr('colspan', colspan + 1);
src/Table/Utility.php-92-114 (1)

92-114: ⚠️ Potential issue | 🟡 Minor

Missing null safety for columnsMeta array access.

Line 94 accesses self::$tempData['columnsMeta'][$iCell]['key'] without checking if columnsMeta or the specific index exists. This could cause undefined index errors if table metadata is incomplete.

🛡️ Proposed fix to add defensive check
     private static function getCellKeyValue(TableCell $tableCell, $iCell)
     {
+        $columnsMeta = self::$tempData['columnsMeta'] ?? array();
+        $columnMeta = $columnsMeta[$iCell] ?? array();
-        $key = self::$tempData['columnsMeta'][$iCell]['key'];
+        $key = $columnMeta['key'] ?? null;
         $value = $tableCell->getValue();
src/Table/Table.php-92-117 (1)

92-117: ⚠️ Potential issue | 🟡 Minor

Guard against empty thead/tfoot to avoid undefined offsets.

Lines 98 and 114: If a thead/tfoot Element exists but has zero rows (e.g., created via new Element('thead', []) and passed to setChildren()), accessing $rows[0] will trigger an undefined array offset. Return null when the rows array is empty.

🧩 Suggested guard for empty header/footer rows
         $rows = $this->tfoot->getChildren();
+        if (!$rows) {
+            return null;
+        }
         return \count($rows) > 1
             ? $rows
             : $rows[0];
         $rows = $this->thead->getChildren();
+        if (!$rows) {
+            return null;
+        }
         return \count($rows) > 1
             ? $rows
             : $rows[0];
src/Table/Element.php-22-25 (1)

22-25: ⚠️ Potential issue | 🟡 Minor

Fix malformed PHPDoc generic for $cfg.

The annotation array>string,mixed> is invalid and can break static analysis tools; use array<string,mixed>.

✏️ Suggested fix
-    /** `@var` array>string,mixed> */
+    /** `@var` array<string,mixed> */
src/Table/Element.php-158-165 (1)

158-165: ⚠️ Potential issue | 🟡 Minor

Normalize getIndex() when not found.

array_search can return false, which can be mistaken for index 0 by callers; return null explicitly.

✅ Suggested fix
-        return \array_search($this, $siblings, true);
+        $index = \array_search($this, $siblings, true);
+        return $index === false ? null : $index;
src/Debug/Dump/Html/Table.php-158-166 (1)

158-166: ⚠️ Potential issue | 🟡 Minor

Guard against missing context-value cell.

Directly accessing $row->getCells()[2] will emit notices if the row has fewer cells. Add a defensive fallback.

🛡️ Suggested fix
-        $contextHtml = $this->helper->buildContext($row->getMeta('context'), $row->getCells()[2]->getValue())
+        $cells = $row->getCells();
+        $contextValue = isset($cells[2]) ? $cells[2]->getValue() : null;
+        $contextHtml = $this->helper->buildContext($row->getMeta('context'), $contextValue)
             . $this->buildContextArguments($row->getMeta('args'));
🧹 Nitpick comments (18)
src/Promise/AbstractResolvedPromise.php (1)

22-22: Consider adding type hints to the constructor signature.

Since this PR includes typehint updates as part of coding-standard cleanups, consider adding actual type declarations to match the documented types.

🔧 Suggested enhancement with type hints
-    public function __construct($state, $result)
+    public function __construct(string $state, $result)

Note: $result can remain untyped (equivalent to mixed in PHP 7.x) or be explicitly typed as mixed in PHP 8.0+.

tests/Debug/Collector/DoctrineMiddlewareTest.php (1)

173-175: Remove redundant $colClass reassignment.

This assignment duplicates lines 93-95 exactly. Since $haveParamType hasn't changed and $colClass was already set, this can be removed.

♻️ Proposed fix
-        $colClass = $haveParamType
-            ? ' <span class="classname"><span class="namespace">Doctrine\DBAL\</span>ParameterType</span>'
-            : '';
         $select2expect = <<<EOD
tests/Debug/Utility/SerializeLogTest.php (1)

110-110: Consider documenting why this test is disabled.

Renaming testBackwardsImportCompatibility to t_estBackwardsImportCompatibility effectively disables this test by breaking PHPUnit's naming convention. While this is a valid technique, it lacks visibility compared to using $this->markTestSkipped() or the @skip annotation.

Consider adding a comment explaining the reason for disabling this test, or use a more explicit skipping mechanism:

💡 Suggested alternatives

Option 1 - Add explanatory comment:

-    public function t_estBackwardsImportCompatibility($dataFilepath)
+    // TODO: Re-enable once [reason] is resolved
+    public function t_estBackwardsImportCompatibility($dataFilepath)

Option 2 - Use explicit skip:

-    public function t_estBackwardsImportCompatibility($dataFilepath)
+    public function testBackwardsImportCompatibility($dataFilepath)
     {
+        $this->markTestSkipped('Temporarily disabled: [reason]');
         $serialized = \file_get_contents($dataFilepath);
tests/Debug/data/PropertyHooks.php (1)

256-256: Consider removing the commented-out line entirely.

If traverseValues is no longer part of the data structure (as indicated by similar changes across other test fixtures), the commented line should be removed rather than left as dead code.

♻️ Suggested fix
     'sort' => 'inheritance visibility name',
     'stringified' => null,
-    // 'traverseValues' => array(),
     'type' => Type::TYPE_OBJECT,
tests/Debug/Route/ChromeLoggerTest.php (1)

35-35: Inconsistent route access pattern within the same test file.

Line 35 now uses getRoute('chromeLogger'), but lines 66 and 129 still use the magic property $this->debug->routeChromeLogger. Consider updating all occurrences for consistency.

♻️ Suggested fix for consistency

Update lines 66 and 129 to use the same getter pattern:

-        $this->debug->routeChromeLogger->setCfg('group', false);
+        $this->debug->getRoute('chromeLogger')->setCfg('group', false);
src/Debug/Dump/TextAnsi/TextAnsiObject.php (1)

115-117: Inconsistent use of ANSI escape codes.

The dumpProperties method still uses inline escape sequences "\e[4m" and "\e[24m" instead of the newly defined self::ANSI_UNDERLINE and self::ANSI_NO_UNDERLINE constants used in dumpMethods.

♻️ Suggested refactor for consistency
     $header = \count($abs['properties']) > 0
-            ? "\e[4m" . $this->debug->i18n->trans('object.properties')  . ':' . "\e[24m" . "\n"
+            ? self::ANSI_UNDERLINE . $this->debug->i18n->trans('object.properties')  . ':' . self::ANSI_NO_UNDERLINE . "\n"
             : $this->debug->i18n->trans('object.properties.none') . "\n";
src/Debug/Plugin/Method/Table.php (1)

136-161: Unused $isRow parameter - verify callback interface compliance.

Static analysis flags $isRow as unused. If this is part of the TableFactory::getValInfo callback signature, consider adding a brief comment explaining why it's present but unused, or prefix with underscore ($_isRow) to indicate intentional non-use.

💡 Suggested documentation
     /**
      * "getValInfo" callback used by TableFactory
      *
      * `@param` mixed $value Value to get type of
-     * `@param` bool  $isRow Does value represent a row
+     * `@param` bool  $isRow Does value represent a row (unused, required by callback signature)
      *
      * `@return` array<string,mixed>
      */
-    public function getValInfo($value, $isRow = false)
+    public function getValInfo($value, $isRow = false) // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter
src/Debug/Route/Firephp.php (1)

220-243: Add a defensive guard for unexpected tabular args.
methodTabular assumes args[0] is an object with getValues(). If a malformed/empty table entry slips through (custom callers, edge-case trace), this will fatal and abort header output. Consider short‑circuiting when the arg isn’t table-like.

🛡️ Suggested guard
-        $data = $logEntry['args'][0]->getValues();
+        $tableAbs = $logEntry['args'][0] ?? null;
+        if (!$tableAbs || !\is_object($tableAbs) || !\method_exists($tableAbs, 'getValues')) {
+            return null;
+        }
+        $data = $tableAbs->getValues();
tests/Table/TableCellTest.php (1)

329-345: Avoid leaving commented-out tests.

Consider either enabling the DateTimeImmutable test or removing the commented block to keep the suite clean.

src/Table/TableRow.php (2)

14-20: Redundant tagName declaration in both $defaults and $tagName.

Consider removing the redundant $tagName property declaration if the parent class can initialize it from $defaults. However, this may be intentional for direct property access performance.


27-34: Constructor heuristic for distinguishing children from properties may be fragile.

The condition ArrayUtil::isList($children) || \reset($children) instanceof Element assumes that if the array is associative and the first element is not an Element, it must be properties. An empty array would pass isList() but the semantics might be unclear.

Consider adding a docblock example clarifying expected input formats for better maintainability.

src/Debug/Framework/Laravel/ServiceProvider.php (1)

141-158: Consider handling potential ReflectionException.

new ReflectionClass($class) on line 145 can throw ReflectionException if the class doesn't exist or isn't loadable. While model classes from Eloquent events should always exist, defensive error handling would improve robustness.

🛡️ Proposed fix to add try-catch
     private function buildModelCountTable()
     {
         $modelCounts = array();
         foreach ($this->modelCounts as $class => $count) {
+            try {
+                $ref = new ReflectionClass($class);
+                $fileName = $ref->getFileName();
+            } catch (\ReflectionException $e) {
+                $fileName = null;
+            }
-            $ref = new ReflectionClass($class);
             $modelCounts[] = array(
                 'model' => $this->debug->abstracter->crateWithVals($class, array(
                     'attribs' => array(
-                        'data-file' => $ref->getFileName(),
+                        'data-file' => $fileName,
                     ),
src/Table/Utility.php (3)

15-17: Static state properties can cause issues with reentrant calls.

Using static $options and $tempData properties means calling asArray() on a different table while one is being processed would corrupt the state. Consider using instance methods or passing state through parameters.


66-71: Remove commented-out code block.

The commented-out KEY_INDEX handling code should be removed or, if intentionally kept for reference, should include a comment explaining why it's preserved.


77-80: Variable $cellInfo accessed outside its declaration scope.

The variable $cellInfo is declared inside the foreach loop but accessed after the loop exits. While this works in PHP, it's a code smell. Consider restructuring to make the data flow clearer.

♻️ Proposed refactor for clarity
     private static function rowAsArray(TableRow $tableRow, $iRow)
     {
         $row = array();
+        $lastCellInfo = null;
         self::$tempData['rowKey'] = $iRow;
         foreach ($tableRow->getCells() as $iCell => $tableCell) {
             $cellInfo = self::getCellKeyValue($tableCell, $iCell);
+            $lastCellInfo = $cellInfo;
             $key = $cellInfo['key'];
             $value = $cellInfo['value'];
             if ($key === null) {
                 continue;
             }
             $row[$key] = $value;
         }
-        if (\count($row) === 1 && $cellInfo['isScalar'] && self::$options['forceArray'] === false) {
+        if (\count($row) === 1 && $lastCellInfo && $lastCellInfo['isScalar'] && self::$options['forceArray'] === false) {
             // this scalar column is the only column other than the index
-            $row = $row[$key];
+            $row = $row[$lastCellInfo['key']];
         }
         return $row;
     }
tests/Table/TableRowTest.php (1)

527-539: Static analysis warning: $row variable appears unused but serves a purpose.

The $row variable establishes the parent-child relationship needed for getIndex() to work. Consider adding a brief comment or using the variable explicitly to clarify intent and silence the warning.

🔧 Optional: Suppress or clarify the unused variable
 public function testGetIndex()
 {
     $cell1 = new TableCell('A');
     $cell2 = new TableCell('B');
     $cell3 = new TableCell('C');

     $row = new TableRow([$cell1, $cell2, $cell3]);
+    self::assertSame('tr', $row->getTagName()); // Verify row setup and use $row

-    // TableRow's children are the cells
     self::assertSame(0, $cell1->getIndex());
     self::assertSame(1, $cell2->getIndex());
     self::assertSame(2, $cell3->getIndex());
 }
tests/Debug/Plugin/Method/TableTest.php (2)

44-67: Commented-out test code should be addressed.

The testColKeys method is fully commented out. If the test is no longer relevant due to the refactor, it should be removed entirely. If it needs to be updated, a TODO comment explaining the plan would be helpful.

🧹 Remove or restore the commented test

If the test is obsolete, remove it:

-    /*
-    public function testColKeys()
-    {
-        $table = new Table(null, array(), $this->debug);
-        // ... test body ...
-    }
-    */

If the test needs updating, add a TODO or skip annotation:

/**
 * `@todo` Update for new Table abstraction
 */
public function testColKeys()
{
    self::markTestSkipped('Needs update for new Table abstraction');
}

1182-1183: Debug line left in test provider.

Line 1182 has a commented debug line that filters test cases. While harmless when commented, it could be removed for cleanliness.

-        // return \array_intersect_key($tests, \array_flip(['traversableOfTraversable']));
         return $tests;

Comment on lines +99 to +105
$cells = $row->getCells();
$shortcode = $cells[0]->getValue();
$links = $cells[2]->getValue();
$cells[2]->setValue(
$links . ' <a href="#" data-toggle="#shortcode_' . $shortcode . '_doc" title="handler phpDoc"><i class="fa fa-code"></i></a>'
);
$rowsNew[] = $this->buildInfoRow($row);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# First, let's read the file to see the actual code at the specified lines
cat -n src/Debug/Framework/WordPress/Shortcodes.php | sed -n '95,130p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1569


🏁 Script executed:

#!/bin/bash
# Search for getValue() implementation to understand return type
rg -n 'getValue\(\)' src/Debug/Framework/WordPress/Shortcodes.php -B 2 -A 2

Repository: bkdotcom/PHPDebugConsole

Length of output: 721


🏁 Script executed:

#!/bin/bash
# Search for crateWithVals definition and usage
rg -n 'crateWithVals' --type=php | head -20

Repository: bkdotcom/PHPDebugConsole

Length of output: 2484


🏁 Script executed:

#!/bin/bash
# Let's understand the structure - find where this Cell class is defined
rg -n 'class.*Cell' --type=php | head -10

Repository: bkdotcom/PHPDebugConsole

Length of output: 579


🏁 Script executed:

#!/bin/bash
# Find getValue() implementation in TableCell
rg -n 'public function getValue' --type=php -A 3 | grep -A 3 'TableCell\|Element'

Repository: bkdotcom/PHPDebugConsole

Length of output: 349


🏁 Script executed:

#!/bin/bash
# Search for crateWithVals implementation
rg -n 'function crateWithVals' --type=php -A 5

Repository: bkdotcom/PHPDebugConsole

Length of output: 604


🏁 Script executed:

#!/bin/bash
# Look for TableCell and Element class definitions
cat -n src/Table/Element.php | head -50

Repository: bkdotcom/PHPDebugConsole

Length of output: 1480


🏁 Script executed:

#!/bin/bash
# Check TableCell class
cat -n src/Table/TableCell.php | head -50

Repository: bkdotcom/PHPDebugConsole

Length of output: 1546


🏁 Script executed:

#!/bin/bash
# Check TableCell::setValue() implementation
cat -n src/Table/TableCell.php | sed -n '50,120p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 2136


🏁 Script executed:

#!/bin/bash
# Get more of crateWithVals implementation
cat -n src/Debug/Abstraction/Abstracter.php | sed -n '174,210p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1763


🏁 Script executed:

#!/bin/bash
# Search for Abstraction class definition to understand structure
rg -n 'class Abstraction' --type=php | head -5

Repository: bkdotcom/PHPDebugConsole

Length of output: 316


🏁 Script executed:

#!/bin/bash
# Check what the shortcode cells contain - look at where table is created
rg -n 'getCells\|TableCell' src/Debug/Framework/WordPress/Shortcodes.php -B 2 -A 2

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Look at where the table is created and populated in Shortcodes.php
cat -n src/Debug/Framework/WordPress/Shortcodes.php | head -100

Repository: bkdotcom/PHPDebugConsole

Length of output: 3252


🏁 Script executed:

#!/bin/bash
# Search for where this shortcodes method is called or how table is built
rg -n 'shortcodes|Shortcodes' src/Debug/Framework/WordPress/Shortcodes.php | head -20

Repository: bkdotcom/PHPDebugConsole

Length of output: 387


🏁 Script executed:

#!/bin/bash
# Look for the full class and method context
cat -n src/Debug/Framework/WordPress/Shortcodes.php | sed -n '80,110p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1292


🏁 Script executed:

#!/bin/bash
# Check Abstraction class to understand the structure
cat -n src/Debug/Abstraction/Abstraction.php | sed -n '1,100p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 3118


🏁 Script executed:

#!/bin/bash
# Find getShortCodeData to see how cells are populated
cat -n src/Debug/Framework/WordPress/Shortcodes.php | sed -n '130,200p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 2940


🏁 Script executed:

#!/bin/bash
# Check if methodTable plugin creates cells with Abstraction values
rg -n 'methodTable|doTable' src/Debug/Framework/ --type=php -l

Repository: bkdotcom/PHPDebugConsole

Length of output: 112


🏁 Script executed:

#!/bin/bash
# Search for how table cells are created and what values are set
rg -n 'TableCell\|setValue' src/Debug/ --type=php -B 2 -A 2 | grep -A 5 'crateWithVals'

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check the methodTable plugin to understand how it processes table data
find src/Debug -name "*methodTable*" -o -name "*MethodTable*" | head -5

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Find the methodTable plugin
find src -name "*ethodTable*" -o -name "*method*table*" -type f | grep -i table

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Search for getPlugin('methodTable')
rg -n "methodTable" src/Debug --type=php -B 2 -A 2

Repository: bkdotcom/PHPDebugConsole

Length of output: 2421


🏁 Script executed:

#!/bin/bash
# Look for Route/Html/Table which might be the methodTable plugin
ls -la src/Debug/Dump/ | grep -i table

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Check Dump directory for Table-related files
find src/Debug -path "*Table*" -name "*.php" -type f | head -10

Repository: bkdotcom/PHPDebugConsole

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Check the methodTable plugin (Method/Table.php)
cat -n src/Debug/Plugin/Method/Table.php | head -100

Repository: bkdotcom/PHPDebugConsole

Length of output: 3223


🏁 Script executed:

#!/bin/bash
# Search for doTable method in Table.php
rg -n 'doTable' src/Debug/Plugin/Method/Table.php -A 30

Repository: bkdotcom/PHPDebugConsole

Length of output: 3986


🏁 Script executed:

#!/bin/bash
# Look for where cells are populated with values
rg -n 'setValue|TableCell' src/Debug/Plugin/Method/Table.php -B 2 -A 2

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Find valsFromLogEntry method
rg -n 'valsFromLogEntry' src/Debug/Plugin/Method/Table.php -A 30

Repository: bkdotcom/PHPDebugConsole

Length of output: 2254


🏁 Script executed:

#!/bin/bash
# Check TableFactory to see how cells are created
rg -n 'new TableCell|setValue' src/Table/Factory.php -B 2 -A 2 | head -50

Repository: bkdotcom/PHPDebugConsole

Length of output: 857


🏁 Script executed:

#!/bin/bash
# See what getShortCodeData returns
cat -n src/Debug/Framework/WordPress/Shortcodes.php | sed -n '195,230p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1635


🏁 Script executed:

#!/bin/bash
# Check if values go through abstraction
rg -n 'abstracter|crateWithVals' src/Debug/Plugin/Method/Table.php

Repository: bkdotcom/PHPDebugConsole

Length of output: 139


🏁 Script executed:

#!/bin/bash
# See what type of values are in the shortcodes table
cat -n src/Debug/Framework/WordPress/Shortcodes.php | sed -n '195,225p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1428


🏁 Script executed:

#!/bin/bash
# Test to understand Abstraction concatenation behavior
python3 << 'EOF'
# Check if Abstraction extends ArrayObject or array-like
import subprocess
result = subprocess.run(['rg', '-n', 'class Abstraction', 'src/Debug/Abstraction/Abstraction.php', '-A 5'], 
                       capture_output=True, text=True)
print(result.stdout)
print("---")
result = subprocess.run(['rg', '-n', 'extends', 'src/Debug/Abstraction/Abstraction.php', '-A 2'], 
                       capture_output=True, text=True)
print(result.stdout)
EOF

Repository: bkdotcom/PHPDebugConsole

Length of output: 73


🏁 Script executed:

#!/bin/bash
# Verify Abstraction class structure
cat -n src/Debug/Abstraction/Abstraction.php | sed -n '27,35p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 401


🏁 Script executed:

#!/bin/bash
# Check how Abstraction is used as array-like (if at all)
rg -n 'Abstraction implements' src/Debug/Abstraction/Abstraction.php -A 2

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Verify the actual test to see if there's a case of Abstraction concatenation
rg -n 'to string' tests/ --type=php -i -B 2 -A 2 | head -30

Repository: bkdotcom/PHPDebugConsole

Length of output: 470


🏁 Script executed:

#!/bin/bash
# Check how Abstraction's __toString is implemented in detail
cat -n src/Debug/Abstraction/Abstraction.php | sed -n '48,65p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 702


🏁 Script executed:

#!/bin/bash
# Verify what happens when Abstraction is concatenated
rg -n '__toString' src/Debug/Abstraction/ --type=php -B 2 -A 10

Repository: bkdotcom/PHPDebugConsole

Length of output: 12954


🏁 Script executed:

#!/bin/bash
# Check tests to see if there's any test for Abstraction concatenation
rg -n 'Abstraction.*\.' tests/ --type=php | grep -i concat | head -5

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

#!/bin/bash
# Let me trace through the actual execution - what exactly happens in modifyShortcodeTable
# The cells[2] contains value from getShortCodeData which wraps links with crateWithVals
# So getValue() returns Abstraction object
# Then concatenation calls __toString()
# Then setValue() stores the concatenated string (losing metadata)
# Check if there's any issue with this by looking at how the cell is rendered

cat -n src/Table/TableCell.php | sed -n '60,90p'

Repository: bkdotcom/PHPDebugConsole

Length of output: 1097


Preserve Abstraction metadata when concatenating values and storing back to cells.

At line 101/121: $links and $method are Abstraction objects returned from getValue(). Direct string concatenation (line 103, 122) calls __toString(), extracting only the raw value and discarding metadata like addQuotes and sanitize flags. When the concatenated string is stored back via setValue(), the metadata is permanently lost, causing the HTML <a> tag and its attributes to be unexpectedly escaped or sanitized.

Extract the string value from Abstraction objects, perform concatenation, then re-wrap the result with crateWithVals() to restore metadata:

Suggested fix
-            $links = $cells[2]->getValue();
-            $cells[2]->setValue(
-                $links . ' <a href="#" data-toggle="#shortcode_' . $shortcode . '_doc" title="handler phpDoc"><i class="fa fa-code"></i></a>'
-            );
+            $linksVal = $cells[2]->getValue();
+            if ($linksVal instanceof Abstraction && isset($linksVal['value'])) {
+                $linksVal = $linksVal['value'];
+            }
+            $linksHtml = $linksVal . ' <a href="#" data-toggle="#shortcode_' . $shortcode . '_doc" title="handler phpDoc"><i class="fa fa-code"></i></a>';
+            $cells[2]->setValue($this->debug->abstracter->crateWithVals(
+                $linksHtml,
+                array(
+                    'addQuotes' => false,
+                    'sanitize' => false,
+                )
+            ));
@@
-        $method = $cells[1]->getValue();
-        $phpDoc = $this->debug->phpDoc->getParsed($method . '()');
+        $methodVal = $cells[1]->getValue();
+        if ($methodVal instanceof Abstraction && isset($methodVal['value'])) {
+            $methodVal = $methodVal['value'];
+        }
+        $phpDoc = $this->debug->phpDoc->getParsed($methodVal . '()');

Also applies to: 119-123

🤖 Prompt for AI Agents
In `@src/Debug/Framework/WordPress/Shortcodes.php` around lines 99 - 105, The code
is concatenating Abstraction objects returned by $cells[2]->getValue() (e.g.,
$links and similarly $method) which calls __toString() and drops metadata;
instead, extract the raw string/value from the Abstraction, perform the
concatenation to build the HTML fragment, then re-wrap the resulting string with
crateWithVals() (preserving flags like addQuotes/sanitize) before calling
$cells[2]->setValue(...); apply the same pattern for the other occurrence around
$method so metadata is retained when calling setValue() and buildInfoRow().

Comment on lines +466 to +471
public function unserialize($data)
{
/** @var mixed */
$unserialized = \unserialize($data);
if (\is_array($unserialized)) {
$this->__unserialize($unserialized);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check if the file exists and read the relevant lines
fd -t f "Element.php" | grep -E "src/Table"

Repository: bkdotcom/PHPDebugConsole

Length of output: 89


🏁 Script executed:

# Get the exact content around lines 466-471
if [ -f "src/Table/Element.php" ]; then
  sed -n '460,475p' src/Table/Element.php
fi

Repository: bkdotcom/PHPDebugConsole

Length of output: 415


🏁 Script executed:

# Search for all unserialize() calls in the codebase to understand the scope
rg "unserialize\(" --type php

Repository: bkdotcom/PHPDebugConsole

Length of output: 3479


🌐 Web query:

PHP unserialize allowed_classes parameter security best practices

💡 Result:

Best practices for unserialize() + allowed_classes (security)

  1. Prefer not to use unserialize() at all for untrusted data
    Use a data-only format like JSON instead. PHP explicitly warns that untrusted input must not be passed to unserialize() even if allowed_classes is set, because object instantiation/autoloading can still lead to exploitation. [1][2]

  2. If you must use unserialize(), default to “no objects”

    $value = unserialize($data, ['allowed_classes' => false]);

    This prevents object creation (disallowed classes become __PHP_Incomplete_Class). [1]

  3. If objects are required, use a strict allow-list (not true, not “option omitted”)
    Omitting allowed_classes is equivalent to allowing all classes. Use an explicit allow-list of the minimum safe classes and audit them carefully. [1][4]

  4. Assume deserialization = potential “object injection” surface
    Attacks typically rely on existing classes with dangerous magic methods like __wakeup() / __destruct() and “POP chains”. Treat any deserialized values as attacker-controlled and design classes accordingly. [2]

  5. Add integrity protection for externally stored serialized blobs
    If the serialized payload is stored client-side or otherwise exposed, sign/verify it (e.g., HMAC) so tampering is detected before deserialization (PHP manual specifically suggests this). [1]

  6. Constrain resource/DoS risk
    Use max_depth to limit nesting and reduce risk of crashes/stack overflows:

    unserialize($data, ['allowed_classes' => false, 'max_depth' => 64]);

    [1]

  7. Operational guardrails

    • Avoid/limit autoload side effects around deserialization (autoloading can be part of the exploit path). [1][2]
    • Add SAST/lint rules to flag unserialize() calls missing restrictive allowed_classes. [4]

Key takeaway: allowed_classes is risk-reduction, not a guarantee—don’t unserialize untrusted input; otherwise block objects (false) or tightly allow-list, and protect integrity + depth. [1][2]


🏁 Script executed:

# Check minimum PHP version requirement
if [ -f "composer.json" ]; then
  cat composer.json | jq '.require.php' 2>/dev/null || grep -A2 '"php"' composer.json
fi

Repository: bkdotcom/PHPDebugConsole

Length of output: 90


🏁 Script executed:

# Check the Php.php file to see how it handles allowed_classes for compatibility
cat -n src/Debug/Utility/Php.php | grep -A 10 -B 5 "allowed_classes"

Repository: bkdotcom/PHPDebugConsole

Length of output: 859


Harden unserialize() against object injection.

Unrestricted unserialize() can instantiate arbitrary classes from untrusted input. Restrict allowed_classes to prevent object injection. Since the code only expects arrays (as evidenced by the if (\is_array($unserialized)) check), block all object instantiation.

🛡️ Suggested fix
-        $unserialized = \unserialize($data);
+        $unserialized = \unserialize($data, ['allowed_classes' => false]);

Note: The allowed_classes parameter is available in PHP 7.0+. For PHP 5.4 compatibility, wrap the call in a version check (e.g., PHP_VERSION_ID >= 70000) as shown in src/Debug/Utility/Php.php.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
public function unserialize($data)
{
/** @var mixed */
$unserialized = \unserialize($data);
if (\is_array($unserialized)) {
$this->__unserialize($unserialized);
public function unserialize($data)
{
/** `@var` mixed */
$unserialized = \unserialize($data, ['allowed_classes' => false]);
if (\is_array($unserialized)) {
$this->__unserialize($unserialized);
🤖 Prompt for AI Agents
In `@src/Table/Element.php` around lines 466 - 471, The unserialize method
currently calls \unserialize($data) which allows object instantiation from
untrusted input; change it to use the allowed_classes option to block object
injection (e.g. call \unserialize($data, ['allowed_classes' => false])) and keep
the existing check that only arrays are passed to __unserialize; for PHP
versions older than 7 (PHP_VERSION_ID < 70000) fall back to the current
\unserialize($data) behavior behind a version check so compatibility with PHP
5.x is preserved, referencing the unserialize method and the __unserialize call
to locate the change.

Comment on lines +76 to +84
public function setChildren(array $children)
{
$children = \array_map(static function ($child) {
return $child instanceof Element
? $child
: new TableCell($child);
}, $children);
return parent::setChildren($children);
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the file structure and find TableRow.php
find . -name "TableRow.php" -type f

Repository: bkdotcom/PHPDebugConsole

Length of output: 126


🏁 Script executed:

# Let's look at the TableRow class to understand its parent and the setChildren method
cat -n src/Table/TableRow.php | head -100

Repository: bkdotcom/PHPDebugConsole

Length of output: 2384


🏁 Script executed:

# Find the Element class and its appendChild method
ast-grep --pattern $'class Element {
  $$$
  public function appendChild($$$) {
    $$$
  }
  $$$
}'

Repository: bkdotcom/PHPDebugConsole

Length of output: 50


🏁 Script executed:

# Also search for any type hints or documentation about what can be added as children
rg "TableRow|TableCell" -A 3 -B 3 src/Table/ | head -80

Repository: bkdotcom/PHPDebugConsole

Length of output: 3362


🏁 Script executed:

# Find the Element class
find . -name "Element.php" -type f

Repository: bkdotcom/PHPDebugConsole

Length of output: 91


🏁 Script executed:

# Look at the Element class and find appendChild and setChildren methods
cat -n src/Table/Element.php | head -150

Repository: bkdotcom/PHPDebugConsole

Length of output: 4865


🏁 Script executed:

# Search for appendChild implementation in Element
rg "function appendChild" src/Table/ -A 10

Repository: bkdotcom/PHPDebugConsole

Length of output: 611


🏁 Script executed:

# Search for setChildren implementation in Element
rg "function setChildren" src/Table/ -A 10

Repository: bkdotcom/PHPDebugConsole

Length of output: 1662


Use TableCell instead of Element in the instanceof check.

The setChildren method checks $child instanceof Element rather than $child instanceof TableCell. This is inconsistent with the class contract: getCells() returns array<TableCell>, setCells() expects array<TableCell>|array<array>, and appendCell() enforces TableCell explicitly. Allowing any Element subclass undermines type safety and could result in non-TableCell elements being stored in the row.

🤖 Prompt for AI Agents
In `@src/Table/TableRow.php` around lines 76 - 84, The setChildren method
currently treats any Element as acceptable; change the instanceof check to
require TableCell so only TableCell instances are preserved: in
TableRow::setChildren, map $children and replace the conditional ($child
instanceof Element) with ($child instanceof TableCell), otherwise construct new
TableCell($child), then call parent::setChildren($children) as before to ensure
rows only contain TableCell objects.

Comment on lines +34 to +40
self::$tempData = array(
'columnsMeta' => $table->getMeta('columns'),
'headerVals' => \array_map(static function ($cell) {
return $cell->getValue();
}, $table->getHeader()->getChildren()),
'rowKey' => 0,
);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Missing null check on getHeader() before calling getChildren().

If the table has no header ($table->getHeader() returns null), line 37-38 will cause a fatal error when calling ->getChildren() on null.

🐛 Proposed fix to add null check
         self::$tempData = array(
             'columnsMeta' => $table->getMeta('columns'),
-            'headerVals' => \array_map(static function ($cell) {
-                return $cell->getValue();
-            }, $table->getHeader()->getChildren()),
+            'headerVals' => $table->getHeader()
+                ? \array_map(static function ($cell) {
+                    return $cell->getValue();
+                }, $table->getHeader()->getChildren())
+                : array(),
             'rowKey' => 0,
         );
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self::$tempData = array(
'columnsMeta' => $table->getMeta('columns'),
'headerVals' => \array_map(static function ($cell) {
return $cell->getValue();
}, $table->getHeader()->getChildren()),
'rowKey' => 0,
);
self::$tempData = array(
'columnsMeta' => $table->getMeta('columns'),
'headerVals' => $table->getHeader()
? \array_map(static function ($cell) {
return $cell->getValue();
}, $table->getHeader()->getChildren())
: array(),
'rowKey' => 0,
);
🤖 Prompt for AI Agents
In `@src/Table/Utility.php` around lines 34 - 40, self::$tempData assignment calls
$table->getHeader()->getChildren() without checking for null, causing a fatal
error when getHeader() returns null; update the 'headerVals' computation in the
self::$tempData block to first check $table->getHeader() (or retrieve
$table->getHeader() into a variable) and only call ->getChildren() when it's not
null, otherwise use an empty array for headerVals, leaving the array_map logic
(and the 'columnsMeta' and 'rowKey' entries) unchanged so behavior is preserved
when a header exists.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Introduces a new internal bdk\Table module to represent tabular data as structured objects and refactors Debug’s table/trace/profile rendering and serialization to use this structure across HTML/text/script/FirePHP/WAMP outputs.

Changes:

  • Added bdk\Table\Table, TableRow, TableCell, Factory, and Utility to standardize table creation/serialization.
  • Refactored Debug’s table/trace/profile pipelines and multiple routes/dumpers to emit/consume table abstractions consistently.
  • Tightened many PHP method signatures to use array type hints and updated tests/fixtures accordingly.

Reviewed changes

Copilot reviewed 149 out of 150 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
tests/docroot/frontController.php Switches globals request creation to ServerRequestUtility::fromGlobals().
tests/Table/Fixture/Stringable.php Adds a __toString() fixture for table-related tests.
tests/PubSub/Fixture/ValueStore.php Adds array typehint to onSet in test fixture.
tests/Debug/data/PropertyHooks.php Updates expected abstraction fixture (traverseValues no longer present).
tests/Debug/data/PropertyAsymVisibility.php Updates expected abstraction fixture (traverseValues no longer present).
tests/Debug/data/ConfusableIdentifiers.php Updates expected abstraction fixture (removes traverseValues).
tests/Debug/Utility/SerializeLogTest.php Updates serialization/back-compat test expectations (but introduces a test discovery issue).
tests/Debug/Utility/PhpTypeTest.php Renames enum test case key (enumenum.pure).
tests/Debug/Utility/PhpTest.php Renames enum test case key (enumenum.pure).
tests/Debug/Type/StringTest.php Updates expected table HTML/structure and entry shape for script output.
tests/Debug/Type/EnumTest.php Updates expected table HTML structure for enums.
tests/Debug/ServiceProviderTest.php Updates where Html\Table is accessed (now under valDumper).
tests/Debug/Route/WampTest.php Updates expected trace payload shape to new table abstraction format.
tests/Debug/Route/Html/ErrorSummaryTest.php Updates expected trace table markup formatting.
tests/Debug/Route/ChromeLoggerTest.php Uses getRoute('chromeLogger') accessor instead of direct property.
tests/Debug/Plugin/PrettifyTest.php Adjusts expected XML string formatting.
tests/Debug/Plugin/Method/ProfileTest.php Updates profile table assertions to new table abstraction shape.
tests/Debug/Plugin/Method/GeneralTest.php Updates getDump signature usage to include route discriminator.
tests/Debug/Plugin/Method/BasicTest.php Updates trace/table expectations to new abstraction usage.
tests/Debug/Plugin/LogResponseTest.php Updates headers table assertions to new table structure.
tests/Debug/Plugin/LogRequestTest.php Updates request/cookie table expectations to new table structure and meta.
tests/Debug/LoggerTest.php Updates logged table expectations to full table abstraction array form.
tests/Debug/Framework/WordPress/ShortcodesTest.php Updates shortcode table expectations to new table structure + child/info row layout.
tests/Debug/Framework/WordPress/ObjectCacheTest.php Updates cache table expectations to new table structure (header/footer/rows).
tests/Debug/Framework/WordPress/HooksTest.php Updates hooks table expectations to new table structure (header/footer/rows).
tests/Debug/DebugTestFramework.php Tweaks setup behavior, test harness defaults, and abstraction key assertions.
tests/Debug/Collector/StatementInfoTest.php Updates expected JSON log format for parameter tables.
tests/Debug/Collector/PdoTest.php Updates expected parameter table structure.
tests/Debug/Collector/MysqliTest.php Updates expected parameter table structure in serialized log output.
tests/Debug/Collector/MonologHandlerTest.php Updates table expectations to new table abstraction structure.
tests/Debug/Collector/DoctrineMiddlewareTest.php Updates expected HTML table output and enum/PDO-type handling strings.
tests/Debug/Collector/DoctrineLoggerTest.php Updates expected HTML table output formatting.
src/Teams/Elements/Table.php Tightens constructor typing for Teams table element rows.
src/Table/Utility.php Adds conversion of bdk\Table\Table to array for dumpers/routes.
src/Table/TableRow.php Adds TableRow element with cell normalization.
src/Table/TableCell.php Adds TableCell element with rendering hooks and default value dumping.
src/Table/Table.php Adds Table element with caption/header/footer/body structure and serialization.
src/Slack/BlockFactory.php Adds array typehints for $values params in multiple builders.
src/Slack/BlockElementsFactory.php Adds array typehints for $values params in multiple element builders.
src/PubSub/ValueStore.php Adds array typehint to onSet.
src/Promise/Create.php Import ordering tweak.
src/Promise/AbstractResolvedPromise.php Updates constructor docblock param descriptions.
src/I18n/I18n.php Adds array typehint for $cfg.
src/ErrorHandler/Plugin/StatsStoreFile.php Adds array typehints for ctor and postSetCfg.
src/ErrorHandler/Plugin/Stats.php Adds array typehints for ctor and postSetCfg.
src/ErrorHandler/Plugin/Emailer.php Adds array typehint for $cfg.
src/ErrorHandler/ErrorHandler.php Adds array typehints across ctor, getInstance, and handler methods.
src/ErrorHandler/AbstractErrorHandler.php Adds array typehint to postSetCfg.
src/ErrorHandler/AbstractError.php Adds array typehint to onSet.
src/ErrorHandler/AbstractComponent.php Adds array typehint to postSetCfg.
src/Debug/js_src/enhanceEntries.js Minor JS formatting change (removes semicolon).
src/Debug/js_src/Tooltip.js Simplifies jQuery tag creation (<dt>/<dd>).
src/Debug/js_src/FileLinks.js Adjusts trace link creation to skip context rows and fix colspan.
src/Debug/js/Debug.js Built artifact updates matching JS source changes.
src/Debug/Utility/Utility.php Updates doc comment wording for translation wrapper args.
src/Debug/Utility/StopWatch.php Adds array typehint for ctor $vals.
src/Debug/Utility/Profile.php Adds array typehint for ctor $namespacesIgnore.
src/Debug/Utility/PhpType.php Improves closure detection and doc wording; imports Closure.
src/Debug/Utility/PhpDoc.php Adds array typehint for ctor $cfg.
src/Debug/Utility/HtmlBuild.php Adds array typehint for internal attrib builder helper.
src/Debug/Utility/FileTree.php Adds array typehints for filesToTree args.
src/Debug/Utility/FileStreamWrapper.php Import ordering tweak.
src/Debug/Route/WampCrate.php Adds table-specific crating and ensures trace meta is crated.
src/Debug/Route/Text.php Ensures text dumper is always initialized.
src/Debug/Route/Stream.php Adds array typehint to postSetCfg.
src/Debug/Route/Script.php Uses route-specific base dumper config and simplifies string replacement logic.
src/Debug/Route/Html/FatalError.php Ensures trace log entry is crated before dumping.
src/Debug/Route/Html.php Adds array typehint to postSetCfg.
src/Debug/Route/Firephp.php Uses route-specific base dumper config and converts table abstraction to FirePHP table format.
src/Debug/Route/ChromeLogger.php Uses route-specific base dumper config; adds array typehint to reduce fill.
src/Debug/Route/AbstractRoute.php Adds array typehint to channel-key matcher arg.
src/Debug/Psr15/Middleware.php Adds array typehint for $cfg.
src/Debug/Plugin/Prettify.php Trims trailing whitespace from pretty-printed XML output.
src/Debug/Plugin/Method/Trace.php Refactors trace generation to use table plugin and table structure/meta updates.
src/Debug/Plugin/Method/Table.php Refactors table method to use bdk\Table\Factory + structured meta handling.
src/Debug/Plugin/Method/Profile.php Ensures profile output uses table plugin and updates key column values to identifier abstractions.
src/Debug/Plugin/Method/GroupCleanup.php Removes redundant stack count bookkeeping.
src/Debug/Plugin/Method/Group.php Adds array typehint to autoArgs.
src/Debug/Plugin/Method/General.php Extends dumper retrieval to support route-specific instances; container integration tweaks.
src/Debug/Plugin/LogRequest.php Passes detectFiles meta when logging $_FILES.
src/Debug/LogEntry.php Tightens constructor arg typing; fixes detectFiles cfg propagation; adds array typehint to onSet.
src/Debug/Framework/Yii2/LogTarget.php Adds array typehint for $config.
src/Debug/Framework/Yii1_1/LogRoute.php Adds array typehint for $opts.
src/Debug/Framework/WordPress/WpHttp.php Adds array typehint to postSetCfg.
src/Debug/Framework/WordPress/WpDb.php Adds array typehint to postSetCfg.
src/Debug/Framework/WordPress/WordPress.php Adds array typehint to postSetCfg.
src/Debug/Framework/WordPress/Shortcodes.php Reworks shortcode output to build/modify a structured table and inserts phpDoc rows via TableRow/TableCell.
src/Debug/Framework/WordPress/ObjectCache.php Migrates table meta config to new column meta format.
src/Debug/Framework/WordPress/Hooks.php Migrates hooks table meta to new column labels/meta/total cols format.
src/Debug/Framework/Laravel/ServiceProvider.php Refactors model usage table building to structured rows and new table meta format.
src/Debug/Framework/Laravel/LogViews.php Simplifies view logging call (relies on explicit data-file attrib).
src/Debug/Framework/Laravel/EventsSubscriber.php Adds array typehint for $payload.
src/Debug/Framework/Laravel/CacheEventsSubscriber.php Adds array typehint for $options.
src/Debug/Dump/TextAnsi/Value.php Adds table dumping; refactors identifier markup/highlighting and numeric/date formatting.
src/Debug/Dump/TextAnsi/TextAnsiObject.php Uses identifier type constants and adds underlined methods header styling.
src/Debug/Dump/TextAnsi.php Refactors val dumper initialization and renames to initValDumper.
src/Debug/Dump/Text/Value.php Adds table dumping for plain text output.
src/Debug/Dump/Text/TextObject.php Uses identifier type constants for markup.
src/Debug/Dump/Text.php Refactors val dumper init; routes tabular output through default handling.
src/Debug/Dump/Substitution.php Adds array typehints for args/options.
src/Debug/Dump/Html/Value.php Adds table dumping support; improves timestamp container wrapping; supports trueAs/falseAs.
src/Debug/Dump/Html/Object/Enum.php Prevents type-class output for brief enum rendering via dumpType=false.
src/Debug/Dump/Html/Object/AbstractSection.php Uses identifier type constants in markup.
src/Debug/Dump/Html/HtmlStringBinary.php Refactors post-dump builder and avoids leaking attribs in wrapped output.
src/Debug/Dump/Html/HtmlString.php Refactors binary substitution handling and prettified wrapping logic.
src/Debug/Dump/Html/HtmlObject.php Uses identifier type constants for attribute markup.
src/Debug/Dump/Html/Helper.php Removes trace-context row injection logic from helper (handled elsewhere).
src/Debug/Dump/Html/Group.php Uses identifier type constants for function-name markup.
src/Debug/Dump/Html.php Removes dedicated Html\Table usage in favor of Value-driven dumping and default formatting.
src/Debug/Dump/Base/Value.php Adds table dumping for base dumper paths (ChromeLogger/FirePHP/Script/etc).
src/Debug/Dump/Base.php Adds route-specific undefined handling config and refactors tabular processing.
src/Debug/Dump/AbstractValue.php Adds config storage, tracks previous options, and formalizes abstract dump hooks incl. table dumping.
src/Debug/Debug.php Adds array typehint for ctor and getInstance.
src/Debug/Config.php Tightens typing for internal config path args.
src/Debug/Collector/MonologHandler/CompatTrait_3.0.php Adds PHPCS disable for duplicate class name within compat trait guard.
src/Debug/Collector/GuzzleMiddleware.php Adds array typehint for $cfg.
src/Debug/Collector/CurlHttpMessageMiddleware.php Adds array typehint for $cfg.
src/Debug/Collector/AbstractAsyncMiddleware.php Adds array typehint for $cfg.
src/Debug/Abstraction/Type.php Adds table type constant and better object subtype detection (closure/enum).
src/Debug/Abstraction/Object/Subscriber.php Refactors object abstraction lifecycle hooks and adds special handling for bdk\Table objects.
src/Debug/Abstraction/Object/PropertiesInstance.php Removes isTraverseOnly guard (traverseValues path removed).
src/Debug/Abstraction/Object/MethodParams.php Adds array typehint for buildValues.
src/Debug/Abstraction/Object/Helper.php Removes isTraverseOnly helper (no longer used).
src/Debug/Abstraction/Object/Abstraction.php Adds unstructuredValue handling and tightens ctor typing.
src/Debug/Abstraction/Object/AbstractInheritable.php Adds array typehint for buildValues.
src/Debug/Abstraction/Abstraction.php Adds array typehint for ctor values and onSet.
src/Debug/Abstraction/Abstracter.php Adds detectFiles cfg; tightens typing for crate APIs; adjusts abstraction detection logic.
src/Debug/Abstraction/AbstractString.php Renames $maxlen to $maxLen for consistency.
src/Debug/Abstraction/AbstractObject.php Returns unstructured abstractions for elements/tables; adjusts root instance subscription behavior.
src/Debug/AbstractDebug.php Adds array typehint for ctor; changes fallback custom method handling to call root log().
src/CurlHttpMessage/Handler/CurlMulti.php Adds array typehint for ctor $options.
src/CurlHttpMessage/Factory.php Adds array typehints for ctor/options/headers in builder methods.
src/CurlHttpMessage/AbstractClient.php Adds array typehint for ctor $options.
src/Container/ObjectBuilder.php Refactors build semantics and docs; adds useContainer flag.
src/Container/Container.php Adds getObject($useContainer) and tightens typing for ctor/setValues.
src/Backtrace/Backtrace.php Increases debug_backtrace depth limit.
phpunit.xml.dist Adds a dedicated Table testsuite.
src/Debug/Utility/TableRow.php Removes legacy table row utility (replaced by new table module).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 173 out of 174 changed files in this pull request and generated no new comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@bkdotcom bkdotcom merged commit f68077f into 3.6 Feb 19, 2026
55 of 63 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant