Skip to content

Commit 517dec9

Browse files
authored
Merge pull request #44 from php-middleware/fix/ajax-debugbar-initialization
Attach AJAX requests to the existing debug bar
2 parents 7a55035 + 3199d5a commit 517dec9

7 files changed

Lines changed: 121 additions & 16 deletions

File tree

.github/workflows/tests.yml

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,46 +1,65 @@
11
name: CI
22
on:
3-
- push
3+
push:
4+
branches:
5+
- master
6+
pull_request:
47
jobs:
58
phpstan:
69
runs-on: ubuntu-latest
710
steps:
811
- name: Checkout
9-
uses: actions/checkout@v2
12+
uses: actions/checkout@v4
1013

1114
- name: Setup PHP, with composer and extensions
1215
uses: shivammathur/setup-php@v2
1316
with:
14-
php-version: 7.3
17+
php-version: '8.0'
1518

1619
- name: Install dependencies with Composer
17-
uses: ramsey/composer-install@v1
20+
uses: ramsey/composer-install@v3
1821

1922
- name: Run phpstan
2023
run: vendor/bin/phpstan analyse --level=6 src/
2124
tests:
2225
strategy:
26+
fail-fast: false
2327
matrix:
2428
dependencies:
2529
- highest
2630
- lowest
31+
# PHP 7.3 and 7.4 are not built here: no laminas-diactoros release
32+
# that Composer 2.10+ accepts as a psr/http-factory-implementation
33+
# provider supports them, so the dev dependencies cannot even be
34+
# installed on those versions.
2735
php-versions:
28-
- 7.3
29-
- 7.4
30-
- 8.0
31-
- 8.1
36+
- '8.0'
37+
- '8.1'
38+
- '8.2'
39+
- '8.3'
40+
exclude:
41+
# Lowest bounds of the dev dependencies (Slim 3.0, Pimple 3.0,
42+
# vfsStream 1.6.8) were released before PHP 8.1 and fatal on load
43+
# there. That is a limitation of those old releases, not of this
44+
# library: every tested PHP version is covered by the highest jobs.
45+
- dependencies: lowest
46+
php-versions: '8.1'
47+
- dependencies: lowest
48+
php-versions: '8.2'
49+
- dependencies: lowest
50+
php-versions: '8.3'
3251
runs-on: ubuntu-latest
3352
steps:
3453
- name: Checkout
35-
uses: actions/checkout@v2
54+
uses: actions/checkout@v4
3655

3756
- name: Setup PHP, with composer and extensions
3857
uses: shivammathur/setup-php@v2
3958
with:
4059
php-version: ${{ matrix.php-versions }}
4160

4261
- name: Install dependencies with Composer
43-
uses: ramsey/composer-install@v1
62+
uses: ramsey/composer-install@v3
4463
with:
4564
dependency-versions: ${{ matrix.dependencies }}
4665

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,14 @@ Sometimes you want to have control when enable or disable PHP Debug Bar:
3636
We allow you to disable attaching phpdebugbar using `X-Enable-Debug-Bar: false` header, cookie or request attribute.
3737
To force enable just send request with `X-Enable-Debug-Bar` header, cookie or request attribute with `true` value.
3838

39+
### AJAX requests
40+
41+
Requests sent with `X-Requested-With: XMLHttpRequest` are attached to the debug bar
42+
already initialized by the main request: the middleware appends only the collected data
43+
of the AJAX request (rendered as a `(ajax)` dataset) instead of the initialization code
44+
and assets. Without it every AJAX response would create another debug bar on top of the
45+
existing one.
46+
3947
### PSR-17
4048

4149
This package isn't require any PSR-7 implementation - you need to provide it by own. Middleware require ResponseFactory and StreamFactory interfaces. [List of existing interfaces](https://packagist.org/providers/psr/http-factory-implementation).

composer.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
],
1414
"require": {
1515
"php": "^7.3 || ^8.0",
16-
"maximebf/debugbar": "^1.4",
16+
"maximebf/debugbar": "^1.18",
1717
"psr/http-server-handler": "^1.0",
1818
"psr/http-server-middleware": "^1.0",
1919
"psr/container-implementation": "^1.0 || ^2.0",
@@ -23,6 +23,7 @@
2323
"require-dev": {
2424
"phpunit/phpunit": "^9.1.4",
2525
"mikey179/vfsstream": "^1.6.8",
26+
"symfony/var-dumper": "^4.4.30",
2627
"slim/slim": "^3.0",
2728
"mezzio/mezzio": "^3.0",
2829
"mezzio/mezzio-fastroute": "^3.0.1",

src/PhpDebugBarMiddleware.php

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ public function process(ServerRequest $request, RequestHandler $handler): Respon
6262
}
6363

6464
if ($this->isHtmlResponse($response)) {
65-
return $this->attachDebugBarToHtmlResponse($response);
65+
return $this->attachDebugBarToHtmlResponse($response, !$this->isXmlHttpRequest($request));
6666
}
6767

6868
return $this->prepareHtmlResponseWithDebugBar($response);
@@ -122,10 +122,16 @@ private function prepareHtmlResponseWithDebugBar(Response $response): Response
122122
->withAddedHeader('Content-type', 'text/html');
123123
}
124124

125-
private function attachDebugBarToHtmlResponse(Response $response): Response
125+
/**
126+
* @param bool $initialize Render the debug bar initialization code and assets.
127+
* Must be false for responses attached to an already
128+
* initialized debug bar (XMLHttpRequest), otherwise a
129+
* second debug bar is created on top of the existing one.
130+
*/
131+
private function attachDebugBarToHtmlResponse(Response $response, bool $initialize = true): Response
126132
{
127-
$head = $this->debugBarRenderer->renderHead();
128-
$body = $this->debugBarRenderer->render();
133+
$head = $initialize ? $this->debugBarRenderer->renderHead() : '';
134+
$body = $this->debugBarRenderer->render($initialize);
129135
$responseBody = $response->getBody();
130136

131137
if (! $responseBody->eof() && $responseBody->isSeekable()) {
@@ -205,6 +211,11 @@ private function isHtml(MessageInterface $message, string $headerName): bool
205211
return strpos($message->getHeaderLine($headerName), 'text/html') !== false;
206212
}
207213

214+
private function isXmlHttpRequest(ServerRequest $request): bool
215+
{
216+
return strtolower($request->getHeaderLine('X-Requested-With')) === 'xmlhttprequest';
217+
}
218+
208219
private function isRedirect(Response $response): bool
209220
{
210221
$statusCode = $response->getStatusCode();

test/AbstractMiddlewareRunnerTest.php

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,27 @@ final public function testAppendJsIntoHtmlContent(): void
3232
$this->assertStringContainsString('"/phpdebugbar/debugbar.js"', $responseBody);
3333
}
3434

35+
final public function testNotAppendInitializationCodeIntoXmlHttpRequestContent(): void
36+
{
37+
$response = $this->dispatchApplication([
38+
'REQUEST_URI' => '/hello',
39+
'REQUEST_METHOD' => 'GET',
40+
'HTTP_ACCEPT' => 'text/html',
41+
'HTTP_X_REQUESTED_WITH' => 'XMLHttpRequest',
42+
], [
43+
'/hello' => function (ServerRequestInterface $request) {
44+
return new Response\HtmlResponse('Hello!');
45+
},
46+
]);
47+
48+
$responseBody = (string) $response->getBody();
49+
50+
$this->assertStringContainsString('Hello!', $responseBody);
51+
$this->assertStringContainsString('phpdebugbar.addDataSet(', $responseBody);
52+
$this->assertStringNotContainsString('var phpdebugbar = new PhpDebugBar.DebugBar();', $responseBody);
53+
$this->assertStringNotContainsString('"/phpdebugbar/debugbar.js"', $responseBody);
54+
}
55+
3556
final public function testGetStatics(): void
3657
{
3758
$response = $this->dispatchApplication([

test/MezzioTest.php

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
use Mezzio\Container\ServerRequestErrorResponseGeneratorFactory;
2323
use Mezzio\MiddlewareContainer;
2424
use Mezzio\MiddlewareFactory;
25+
use Mezzio\MiddlewareFactoryInterface;
2526
use Mezzio\Response\ServerRequestErrorResponseGenerator;
2627
use Mezzio\Router\FastRouteRouter;
2728
use Mezzio\Router\FastRouteRouterFactory;
@@ -34,6 +35,7 @@
3435
use Mezzio\Router\RouterInterface;
3536
use Laminas\HttpHandlerRunner\Emitter\EmitterInterface;
3637
use Laminas\HttpHandlerRunner\RequestHandlerRunner;
38+
use Laminas\HttpHandlerRunner\RequestHandlerRunnerInterface;
3739
use Laminas\ServiceManager\Factory\InvokableFactory;
3840
use Laminas\ServiceManager\ServiceManager;
3941
use Laminas\Stratigility\MiddlewarePipe;
@@ -104,6 +106,8 @@ private function createContainer(array $server): ContainerInterface
104106
$serviceManagerConfig['factories'][DispatchMiddleware::class] = DispatchMiddlewareFactory::class;
105107
$serviceManagerConfig['factories'][ResponseFactory::class] = InvokableFactory::class;
106108
$serviceManagerConfig['factories'][StreamFactory::class] = InvokableFactory::class;
109+
$serviceManagerConfig['aliases'][MiddlewareFactoryInterface::class] = MiddlewareFactory::class;
110+
$serviceManagerConfig['aliases'][RequestHandlerRunnerInterface::class] = RequestHandlerRunner::class;
107111
$serviceManagerConfig['aliases'][RouterInterface::class] = FastRouteRouter::class;
108112
$serviceManagerConfig['aliases'][\Mezzio\ApplicationPipeline::class] = MiddlewarePipe::class;
109113
$serviceManagerConfig['aliases'][ResponseFactoryInterface::class] = ResponseFactory::class;

test/PhpDebugBarMiddlewareTest.php

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,9 @@ protected function setUp(): void
2929
$this->debugbarRenderer = $this->getMockBuilder(JavascriptRenderer::class)->disableOriginalConstructor()->getMock();
3030
$this->debugbarRenderer->method('renderHead')->willReturn('RenderHead');
3131
$this->debugbarRenderer->method('getBaseUrl')->willReturn('/phpdebugbar');
32-
$this->debugbarRenderer->method('render')->willReturn('RenderBody');
32+
$this->debugbarRenderer->method('render')->willReturnCallback(function (bool $initialize = true): string {
33+
return $initialize ? 'RenderBody' : 'RenderBodyWithoutInit';
34+
});
3335
$responseFactory = new ResponseFactory();
3436
$streamFactory = new StreamFactory();
3537

@@ -244,6 +246,45 @@ public function testForceNotAttachDebugbarIfAttributePresents(): void
244246
$this->assertSame('ResponseBody', (string) $result->getBody());
245247
}
246248

249+
public function testNotRenderInitializationCodeForXmlHttpRequest(): void
250+
{
251+
$request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html', 'X-Requested-With' => 'XMLHttpRequest']);
252+
$response = new Response('php://memory', 200, ['Content-Type' => 'text/html']);
253+
$response->getBody()->write('ResponseBody');
254+
$requestHandler = new RequestHandlerStub($response);
255+
256+
$result = $this->middleware->process($request, $requestHandler);
257+
258+
$this->assertTrue($requestHandler->isCalled(), 'Request handler is not called');
259+
$this->assertSame($response, $result);
260+
$this->assertSame('ResponseBodyRenderBodyWithoutInit', (string) $result->getBody());
261+
}
262+
263+
public function testNotRenderInitializationCodeForLowercasedXmlHttpRequestHeaderValue(): void
264+
{
265+
$request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html', 'X-Requested-With' => 'xmlhttprequest']);
266+
$response = new Response('php://memory', 200, ['Content-Type' => 'text/html']);
267+
$response->getBody()->write('ResponseBody');
268+
$requestHandler = new RequestHandlerStub($response);
269+
270+
$result = $this->middleware->process($request, $requestHandler);
271+
272+
$this->assertSame('ResponseBodyRenderBodyWithoutInit', (string) $result->getBody());
273+
}
274+
275+
public function testNotAttachDebugbarToXmlHttpRequestIfForceDisabled(): void
276+
{
277+
$request = new ServerRequest([], [], null, null, 'php://input', ['Accept' => 'text/html', 'X-Requested-With' => 'XMLHttpRequest', 'X-Enable-Debug-Bar' => 'false']);
278+
$response = new Response('php://memory', 200, ['Content-Type' => 'text/html']);
279+
$response->getBody()->write('ResponseBody');
280+
$requestHandler = new RequestHandlerStub($response);
281+
282+
$result = $this->middleware->process($request, $requestHandler);
283+
284+
$this->assertSame($response, $result);
285+
$this->assertSame('ResponseBody', (string) $result->getBody());
286+
}
287+
247288
public function testAppendsToEndOfHtmlResponse(): void
248289
{
249290
$html = '<html><head><title>Foo</title></head><body>Content</body>';

0 commit comments

Comments
 (0)