diff --git a/.github/workflows/run-integration.yml b/.github/workflows/run-integration.yml
index 40aca15..bd90874 100644
--- a/.github/workflows/run-integration.yml
+++ b/.github/workflows/run-integration.yml
@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
- php: [8.4, 8.3, 8.2, 8.1, 8.0, 7.4, 7.3, 7.2]
+ php: [8.4, 8.3, 8.2, 8.1, 8.0]
name: PHP${{ matrix.php }}
diff --git a/.github/workflows/run-screenshots.yml b/.github/workflows/run-screenshots.yml
new file mode 100644
index 0000000..8139c2a
--- /dev/null
+++ b/.github/workflows/run-screenshots.yml
@@ -0,0 +1,51 @@
+name: Screenshots
+
+on:
+ push:
+ branches:
+ - master
+ pull_request:
+ branches:
+ - "*"
+ schedule:
+ - cron: '0 0 * * *'
+
+jobs:
+ php-tests:
+ runs-on: ubuntu-latest
+ timeout-minutes: 15
+ env:
+ COMPOSER_NO_INTERACTION: 1
+
+ strategy:
+ matrix:
+ php: [8.4]
+
+ name: PHP${{ matrix.php }}
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: ${{ matrix.php }}
+ coverage: none
+ extensions: pdo_sqlite
+
+ - name: Install dependencies
+ run: |
+ composer update --prefer-dist --no-progress
+ composer update --prefer-dist --no-progress --working-dir=demo/bridge/monolog
+ composer update --prefer-dist --no-progress --working-dir=demo/bridge/doctrine
+ composer run --working-dir=demo/bridge/doctrine install-schema
+
+ - name: Execute Unit Tests
+ run: vendor/bin/phpunit --testsuite=Browser
+
+ - name: Upload screenshots
+ uses: actions/upload-artifact@v4
+ with:
+ name: debugbar-screenshots
+ path: tests/screenshots
diff --git a/.github/workflows/run-tests.yml b/.github/workflows/run-tests.yml
index 3c05c2e..676d28f 100644
--- a/.github/workflows/run-tests.yml
+++ b/.github/workflows/run-tests.yml
@@ -19,7 +19,7 @@ jobs:
strategy:
matrix:
- php: [8.4, 8.3, 8.2, 8.1, 8.0, 7.4, 7.3, 7.2]
+ php: [8.4, 8.3, 8.2, 8.1, 8.0]
name: PHP${{ matrix.php }}
@@ -38,4 +38,4 @@ jobs:
run: composer update --prefer-dist --no-progress
- name: Execute Unit Tests
- run: vendor/bin/phpunit --testsuite=Unit
+ run: vendor/bin/phpunit --testsuite=Unit
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index db0f376..01ff6a4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -2,7 +2,10 @@ composer.lock
/vendor
/demo/bridge/*/vendor
/demo/bridge/doctrine/db.sqlite
+/demo/profiles
/src/DebugBar/Resources/vendor
.phpunit.result.cache
/drivers
+/chromedriver
.phpunit.cache/
+/tests/screenshots
\ No newline at end of file
diff --git a/README.md b/README.md
index 04a6a15..2583027 100644
--- a/README.md
+++ b/README.md
@@ -5,6 +5,8 @@
Displays a debug bar in the browser with information from php.
No more `var_dump()` in your code!
+> **Note: Debug Bar is for development use only. Never install this on websites that are publicly accessible.**
+

**Features:**
@@ -57,7 +59,9 @@ Integrations with other frameworks:
The best way to install DebugBar is using [Composer](http://getcomposer.org)
with the following command:
-```composer require maximebf/debugbar```
+```bash
+composer require maximebf/debugbar --dev
+```
## Quick start
diff --git a/chromedriver b/chromedriver
deleted file mode 100755
index 5a809fa..0000000
Binary files a/chromedriver and /dev/null differ
diff --git a/composer.json b/composer.json
index da3f121..4e50eed 100644
--- a/composer.json
+++ b/composer.json
@@ -1,8 +1,8 @@
{
- "name": "maximebf/debugbar",
+ "name": "php-debugbar/php-debugbar",
"description": "Debug bar in the browser for php application",
- "keywords": ["debug", "debugbar"],
- "homepage": "https://github.com/maximebf/php-debugbar",
+ "keywords": ["debug", "debugbar", "debug bar", "dev"],
+ "homepage": "https://github.com/php-debugbar/php-debugbar",
"type": "library",
"license": "MIT",
"authors": [
@@ -17,7 +17,7 @@
}
],
"require": {
- "php": "^7.2|^8",
+ "php": "^8",
"psr/log": "^1|^2|^3",
"symfony/var-dumper": "^4|^5|^6|^7"
},
@@ -56,7 +56,7 @@
},
"extra": {
"branch-alias": {
- "dev-master": "1.22-dev"
+ "dev-master": "2.0-dev"
}
}
}
diff --git a/demo/bootstrap.php b/demo/bootstrap.php
index 32b5057..b7031ba 100644
--- a/demo/bootstrap.php
+++ b/demo/bootstrap.php
@@ -11,7 +11,9 @@ $debugbar = new StandardDebugBar();
$debugbarRenderer = $debugbar->getJavascriptRenderer()
->setBaseUrl('../src/DebugBar/Resources')
->setAjaxHandlerEnableTab(true)
- ->setEnableJqueryNoConflict(false);
+ ->setHideEmptyTabs(true)
+ ->setEnableJqueryNoConflict(false)
+ ->setTheme($_GET['theme'] ?? 'auto');
//
// create a writable profiles folder in the demo directory to uncomment the following lines
@@ -20,7 +22,7 @@ $debugbarRenderer = $debugbar->getJavascriptRenderer()
// $debugbar->setStorage(new DebugBar\Storage\RedisStorage(new Predis\Client()));
// $debugbarRenderer->setOpenHandlerUrl('open.php');
-function render_demo_page(Closure $callback = null)
+function render_demo_page(?Closure $callback = null)
{
global $debugbarRenderer;
?>
diff --git a/demo/bridge/doctrine/index.php b/demo/bridge/doctrine/index.php
index 9a00acf..f771502 100644
--- a/demo/bridge/doctrine/index.php
+++ b/demo/bridge/doctrine/index.php
@@ -11,9 +11,10 @@ $debugbar->addCollector(new DebugBar\Bridge\DoctrineCollector($debugStack));
$product = new Demo\Product();
$product->setName("foobar");
-
+$product->setUpdated();
$entityManager->persist($product);
$entityManager->flush();
+$entityManager->createQuery("select p from Demo\\Product p where p.updated>:u")->setParameter("u", new \DateTime('1 hour ago'))->execute();
$entityManager->createQuery("select p from Demo\\Product p where p.name=:c")->setParameter("c", "")->execute();
render_demo_page();
diff --git a/demo/bridge/doctrine/src/Demo/Product.php b/demo/bridge/doctrine/src/Demo/Product.php
index c7d8f08..d2a8a4f 100644
--- a/demo/bridge/doctrine/src/Demo/Product.php
+++ b/demo/bridge/doctrine/src/Demo/Product.php
@@ -11,6 +11,8 @@ class Product
protected $id;
/** @Column(type="string") **/
protected $name;
+ /** @Column(type="datetime", nullable=true) **/
+ protected $updated;
public function getId()
{
@@ -26,4 +28,10 @@ class Product
{
$this->name = $name;
}
+
+ public function setUpdated(): void
+ {
+ // will NOT be saved in the database
+ $this->updated = new \DateTime('now');
+ }
}
diff --git a/src/DebugBar/Bridge/CacheCacheCollector.php b/src/DebugBar/Bridge/CacheCacheCollector.php
index 7e7f46f..55d87cc 100644
--- a/src/DebugBar/Bridge/CacheCacheCollector.php
+++ b/src/DebugBar/Bridge/CacheCacheCollector.php
@@ -38,7 +38,7 @@ class CacheCacheCollector extends MonologCollector
* @param bool $level
* @param bool $bubble
*/
- public function __construct(Cache $cache = null, Logger $logger = null, $level = Logger::DEBUG, $bubble = true)
+ public function __construct(?Cache $cache = null, ?Logger $logger = null, $level = Logger::DEBUG, $bubble = true)
{
parent::__construct(null, $level, $bubble);
diff --git a/src/DebugBar/Bridge/DoctrineCollector.php b/src/DebugBar/Bridge/DoctrineCollector.php
index d33b7c9..f5cf0f0 100644
--- a/src/DebugBar/Bridge/DoctrineCollector.php
+++ b/src/DebugBar/Bridge/DoctrineCollector.php
@@ -60,7 +60,7 @@ class DoctrineCollector extends DataCollector implements Renderable, AssetProvid
foreach ($this->debugStack->queries as $q) {
$queries[] = array(
'sql' => $q['sql'],
- 'params' => (object) $this->getParameters($q),
+ 'params' => (object) $this->getParameters($q['params'] ?? []),
'duration' => $q['executionMS'],
'duration_str' => $this->formatDuration($q['executionMS'])
);
@@ -80,13 +80,22 @@ class DoctrineCollector extends DataCollector implements Renderable, AssetProvid
*
* @return array
*/
- public function getParameters($query) : array
+ public function getParameters($params) : array
{
- $params = [];
- foreach ($query['params'] ?? [] as $name => $param) {
- $params[$name] = htmlentities($param?:"", ENT_QUOTES, 'UTF-8', false);
- }
- return $params;
+ return array_map(function ($param) {
+ if (is_string($param)) {
+ return htmlentities($param, ENT_QUOTES, 'UTF-8', false);
+ } elseif (is_array($param)) {
+ return '[' . implode(', ', $this->getParameters($param)) . ']';
+ } elseif (is_numeric($param)) {
+ return strval($param);
+ } elseif ($param instanceof \DateTimeInterface) {
+ return $param->format('Y-m-d H:i:s');
+ } elseif (is_object($param)) {
+ return json_encode($param);
+ }
+ return $param ?: '';
+ }, $params);
}
/**
diff --git a/src/DebugBar/Bridge/MonologCollector.php b/src/DebugBar/Bridge/MonologCollector.php
index 49eb37c..25063ea 100644
--- a/src/DebugBar/Bridge/MonologCollector.php
+++ b/src/DebugBar/Bridge/MonologCollector.php
@@ -37,7 +37,7 @@ class MonologCollector extends AbstractProcessingHandler implements DataCollecto
* @param boolean $bubble
* @param string $name
*/
- public function __construct(Logger $logger = null, $level = Logger::DEBUG, $bubble = true, $name = 'monolog')
+ public function __construct(?Logger $logger = null, $level = Logger::DEBUG, $bubble = true, $name = 'monolog')
{
parent::__construct($level, $bubble);
$this->name = $name;
diff --git a/src/DebugBar/Bridge/PropelCollector.php b/src/DebugBar/Bridge/PropelCollector.php
index 93ad4ff..4a287ee 100644
--- a/src/DebugBar/Bridge/PropelCollector.php
+++ b/src/DebugBar/Bridge/PropelCollector.php
@@ -49,7 +49,7 @@ class PropelCollector extends DataCollector implements BasicLogger, Renderable,
*
* @param PropelConfiguration $config Apply profiling on a specific config
*/
- public static function enablePropelProfiling(PropelConfiguration $config = null)
+ public static function enablePropelProfiling(?PropelConfiguration $config = null)
{
if ($config === null) {
$config = Propel::getConfiguration(PropelConfiguration::TYPE_OBJECT);
@@ -74,7 +74,7 @@ class PropelCollector extends DataCollector implements BasicLogger, Renderable,
* @param LoggerInterface $logger A logger to forward non-query log lines to
* @param PropelPDO $conn Bound this collector to a connection only
*/
- public function __construct(LoggerInterface $logger = null, PropelPDO $conn = null)
+ public function __construct(?LoggerInterface $logger = null, ?PropelPDO $conn = null)
{
if ($conn) {
$conn->setLogger($this);
diff --git a/src/DebugBar/Bridge/Twig/TimeableTwigExtensionProfiler.php b/src/DebugBar/Bridge/Twig/TimeableTwigExtensionProfiler.php
index 9152738..9dea9b6 100644
--- a/src/DebugBar/Bridge/Twig/TimeableTwigExtensionProfiler.php
+++ b/src/DebugBar/Bridge/Twig/TimeableTwigExtensionProfiler.php
@@ -36,7 +36,7 @@ class TimeableTwigExtensionProfiler extends ProfilerExtension
$this->timeDataCollector = $timeDataCollector;
}
- public function __construct(Profile $profile, TimeDataCollector $timeDataCollector = null)
+ public function __construct(Profile $profile, ?TimeDataCollector $timeDataCollector = null)
{
parent::__construct($profile);
diff --git a/src/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php b/src/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
index 9e98c19..9dcf0d7 100644
--- a/src/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
+++ b/src/DebugBar/Bridge/Twig/TraceableTwigEnvironment.php
@@ -24,7 +24,7 @@ use Twig_TokenStream;
/**
* Wrapped a Twig Environment to provide profiling features
- *
+ *
* @deprecated
*/
class TraceableTwigEnvironment extends Twig_Environment
@@ -39,7 +39,7 @@ class TraceableTwigEnvironment extends Twig_Environment
* @param Twig_Environment $twig
* @param TimeDataCollector $timeDataCollector
*/
- public function __construct(Twig_Environment $twig, TimeDataCollector $timeDataCollector = null)
+ public function __construct(Twig_Environment $twig, ?TimeDataCollector $timeDataCollector = null)
{
$this->twig = $twig;
$this->timeDataCollector = $timeDataCollector;
diff --git a/src/DebugBar/DataCollector/ExceptionsCollector.php b/src/DebugBar/DataCollector/ExceptionsCollector.php
index 64adcf4..1ca1cfa 100644
--- a/src/DebugBar/DataCollector/ExceptionsCollector.php
+++ b/src/DebugBar/DataCollector/ExceptionsCollector.php
@@ -97,11 +97,22 @@ class ExceptionsCollector extends DataCollector implements Renderable
if (isset($track['file'])) {
$track['file'] = $this->normalizeFilePath($track['file']);
}
-
return $track;
}, $trace);
}
+ // Remove large objects from the trace
+ $trace = array_map(function ($track) {
+ if (isset($track['args'])) {
+ foreach ($track['args'] as $key => $arg) {
+ if (is_object($arg)) {
+ $track['args'][$key] = '[object ' . $this->getDataFormatter()->formatClassName($arg) . ']';
+ }
+ }
+ }
+ return $track;
+ }, $trace);
+
return $trace;
}
diff --git a/src/DebugBar/DataCollector/PDO/PDOCollector.php b/src/DebugBar/DataCollector/PDO/PDOCollector.php
index 0ea563b..6e7f15b 100644
--- a/src/DebugBar/DataCollector/PDO/PDOCollector.php
+++ b/src/DebugBar/DataCollector/PDO/PDOCollector.php
@@ -26,7 +26,7 @@ class PDOCollector extends DataCollector implements Renderable, AssetProvider
* @param \PDO $pdo
* @param TimeDataCollector $timeCollector
*/
- public function __construct(\PDO $pdo = null, TimeDataCollector $timeCollector = null)
+ public function __construct(?\PDO $pdo = null, ?TimeDataCollector $timeCollector = null)
{
$this->timeCollector = $timeCollector;
if ($pdo !== null) {
@@ -138,7 +138,7 @@ class PDOCollector extends DataCollector implements Renderable, AssetProvider
* @param string|null $connectionName the pdo connection (eg default | read | write)
* @return array
*/
- protected function collectPDO(TraceablePDO $pdo, TimeDataCollector $timeCollector = null, $connectionName = null)
+ protected function collectPDO(TraceablePDO $pdo, ?TimeDataCollector $timeCollector = null, $connectionName = null)
{
if (empty($connectionName) || $connectionName == 'default') {
$connectionName = 'pdo';
diff --git a/src/DebugBar/DataCollector/PDO/TracedStatement.php b/src/DebugBar/DataCollector/PDO/TracedStatement.php
index 9111489..0a78fa6 100644
--- a/src/DebugBar/DataCollector/PDO/TracedStatement.php
+++ b/src/DebugBar/DataCollector/PDO/TracedStatement.php
@@ -57,7 +57,7 @@ class TracedStatement
* @param float $endTime
* @param int $endMemory
*/
- public function end(\Exception $exception = null, int $rowCount = 0, float $endTime = null, int $endMemory = null) : void
+ public function end(?\Exception $exception = null, int $rowCount = 0, ?float $endTime = null, ?int $endMemory = null) : void
{
$this->endTime = $endTime ?: microtime(true);
$this->duration = $this->endTime - $this->startTime;
diff --git a/src/DebugBar/DataFormatter/HasXdebugLinks.php b/src/DebugBar/DataFormatter/HasXdebugLinks.php
index ea4f734..778c15c 100644
--- a/src/DebugBar/DataFormatter/HasXdebugLinks.php
+++ b/src/DebugBar/DataFormatter/HasXdebugLinks.php
@@ -120,11 +120,12 @@ trait HasXdebugLinks
'vscode-remote' => 'vscode://vscode-remote/%f:%l',
'vscode-insiders-remote' => 'vscode-insiders://vscode-remote/%f:%l',
'vscodium' => 'vscodium://file/%f:%l',
- 'nova' => 'nova://core/open/file?filename=%f&line=%l',
+ 'nova' => 'nova://open?path=%f&line=%l',
'xdebug' => 'xdebug://%f@%l',
'atom' => 'atom://core/open/file?filename=%f&line=%l',
'espresso' => 'x-espresso://open?filepath=%f&lines=%l',
'netbeans' => 'netbeans://open/?f=%f:%l',
+ 'cursor' => 'cursor://file/%f:%l',
);
if (is_string($editor) && isset($editorLinkTemplates[$editor])) {
diff --git a/src/DebugBar/DebugBar.php b/src/DebugBar/DebugBar.php
index c64e729..2c31c0b 100644
--- a/src/DebugBar/DebugBar.php
+++ b/src/DebugBar/DebugBar.php
@@ -148,7 +148,7 @@ class DebugBar implements ArrayAccess
* @param StorageInterface $storage
* @return $this
*/
- public function setStorage(StorageInterface $storage = null)
+ public function setStorage(?StorageInterface $storage = null)
{
$this->storage = $storage;
return $this;
diff --git a/src/DebugBar/JavascriptRenderer.php b/src/DebugBar/JavascriptRenderer.php
index 41c17c5..7377147 100644
--- a/src/DebugBar/JavascriptRenderer.php
+++ b/src/DebugBar/JavascriptRenderer.php
@@ -62,6 +62,10 @@ class JavascriptRenderer
protected $useRequireJs = false;
+ protected $theme = null;
+
+ protected $hideEmptyTabs = null;
+
protected $initialization;
protected $controls = array();
@@ -157,6 +161,12 @@ class JavascriptRenderer
if (array_key_exists('use_requirejs', $options)) {
$this->setUseRequireJs($options['use_requirejs']);
}
+ if (array_key_exists('theme', $options)) {
+ $this->setTheme($options['theme']);
+ }
+ if (array_key_exists('hide_empty_tabs', $options)) {
+ $this->setHideEmptyTabs($options['hide_empty_tabs']);
+ }
if (array_key_exists('controls', $options)) {
foreach ($options['controls'] as $name => $control) {
$this->addControl($name, $control);
@@ -397,6 +407,40 @@ class JavascriptRenderer
return $this->useRequireJs;
}
+ /**
+ * Sets the default theme
+ *
+ * @param boolean $hide
+ * @return $this
+ */
+ public function setTheme($theme='auto')
+ {
+ $this->theme = $theme;
+ return $this;
+ }
+
+ /**
+ * Sets whether to hide empty tabs or not
+ *
+ * @param boolean $hide
+ * @return $this
+ */
+ public function setHideEmptyTabs($hide = true)
+ {
+ $this->hideEmptyTabs = $hide;
+ return $this;
+ }
+
+ /**
+ * Checks if empty tabs are hidden or not
+ *
+ * @return boolean
+ */
+ public function areEmptyTabsHidden()
+ {
+ return $this->hideEmptyTabs;
+ }
+
/**
* Adds a control to initialize
*
@@ -1036,7 +1080,7 @@ class JavascriptRenderer
public function replaceTagInBuffer($here = true, $initialize = true, $renderStackedData = true, $head = false)
{
$render = ($head ? $this->renderHead() : "")
- . $this->render($initialize, $renderStackedData);
+ . $this->render($initialize, $renderStackedData);
$current = ($here && ob_get_level() > 0) ? ob_get_clean() : self::REPLACEABLE_TAG;
@@ -1075,7 +1119,7 @@ class JavascriptRenderer
$nonce = $this->getNonceAttribute();
- if ($nonce != '') {
+ if ($nonce != '') {
$js = preg_replace("/