Skip to content

Commit 7919235

Browse files
authored
Merge pull request #83 from neo4j-php/feat/profiling
feat: Profiler integration
2 parents 5dd76bb + cf78648 commit 7919235

27 files changed

+642
-84
lines changed

.github/workflows/static-analysis.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ on:
44
branches:
55
- master
66
pull_request:
7-
branches:
8-
- master
97

108
jobs:
119
php-cs-fixer:

.github/workflows/tests.yml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,6 @@ on:
44
branches:
55
- master
66
pull_request:
7-
branches:
8-
- master
97

108
jobs:
119
build:

Dockerfile

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,9 @@ RUN apt-get update \
1010
&& docker-php-ext-enable xdebug \
1111
&& curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
1212

13-
WORKDIR /opt/project
14-
13+
RUN echo "xdebug.client_host=host.docker.internal" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
14+
RUN echo "xdebug.mode=debug,develop" >> /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini
1515

16+
WORKDIR /opt/project
1617

18+
CMD ["php", "-S", "0.0.0.0:80", "-t", "/opt/project/tests/App"]

bin/console.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@
55

66
require __DIR__ . '/../vendor/autoload.php';
77

8-
$console = new Application(new TestKernel('test', true));
8+
$console = new Application(new TestKernel($_ENV['APP_ENV'] ?? 'dev', true));
99

1010
$console->run();

composer.json

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,22 @@
1919
"symfony/config": "^5.4 || ^6.0 || ^7.0"
2020
},
2121
"require-dev": {
22+
"friendsofphp/php-cs-fixer": "^3.30",
23+
"kubawerlos/php-cs-fixer-custom-fixers": "^3.0",
2224
"matthiasnoback/symfony-dependency-injection-test": "^4.3 || ^5.0",
2325
"phpunit/phpunit": "^9.5",
26+
"psalm/plugin-phpunit": "^0.18",
2427
"psalm/plugin-symfony": "^5.0",
2528
"symfony/console": "^5.4 || ^6.0 || ^7.0",
2629
"symfony/framework-bundle": "^5.4 || ^6.0 || ^7.0",
2730
"symfony/http-kernel": "^5.4 || ^6.0 || ^7.0",
31+
"symfony/routing": "^5.4 || ^6.0 || ^7.0",
32+
"symfony/stopwatch": "^6.4",
2833
"symfony/test-pack": "^1.1",
34+
"symfony/twig-bundle": "^5.4 || ^6.0 || ^7.0",
35+
"symfony/web-profiler-bundle": "^5.4 || ^6.0 || ^7.0",
2936
"symfony/yaml": "^5.4 || ^6.0 || ^7.0",
30-
"vimeo/psalm": "^5.15.0",
31-
"kubawerlos/php-cs-fixer-custom-fixers": "^3.0",
32-
"friendsofphp/php-cs-fixer": "^3.30",
33-
"psalm/plugin-phpunit": "^0.18"
37+
"vimeo/psalm": "^5.15.0"
3438
},
3539
"autoload": {
3640
"psr-4": {
@@ -49,7 +53,7 @@
4953
}
5054
},
5155
"scripts": {
52-
"psalm": "php bin/console.php cache:warmup && vendor/bin/psalm --show-info=true",
56+
"psalm": "APP_ENV=dev php bin/console.php cache:warmup && vendor/bin/psalm --show-info=true",
5357
"fix-cs": "vendor/bin/php-cs-fixer fix",
5458
"check-cs": "vendor/bin/php-cs-fixer fix --dry-run",
5559
"ci-symfony-install-version": "./.github/scripts/setup-symfony-env.bash"

config/services.php

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
use Laudis\Neo4j\Contracts\SessionInterface;
88
use Laudis\Neo4j\Contracts\TransactionInterface;
99
use Neo4j\Neo4jBundle\ClientFactory;
10-
use Neo4j\Neo4jBundle\EventHandler;
10+
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
1111
use Neo4j\Neo4jBundle\SymfonyClient;
1212
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
1313

@@ -16,10 +16,6 @@
1616
return static function (ContainerConfigurator $configurator) {
1717
$services = $configurator->services();
1818

19-
$services->set('neo4j.event_handler', EventHandler::class)
20-
->autowire()
21-
->autoconfigure();
22-
2319
$services->set('neo4j.client_factory', ClientFactory::class)
2420
->args([
2521
service('neo4j.event_handler'),
@@ -47,4 +43,7 @@
4743
$services->alias(DriverInterface::class, 'neo4j.driver');
4844
$services->alias(SessionInterface::class, 'neo4j.session');
4945
$services->alias(TransactionInterface::class, 'neo4j.transaction');
46+
47+
$services->set('neo4j.subscriber', Neo4jProfileListener::class)
48+
->tag('kernel.event_subscriber');
5049
};

docker-compose.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,10 @@ services:
1616
- NEO4J_PORT=7687
1717
- NEO4J_USER=neo4j
1818
- NEO4J_PASSWORD=testtest
19+
- XDEBUG_CONFIG="client_host=host.docker.internal log=/tmp/xdebug.log"
1920
working_dir: /opt/project
21+
extra_hosts:
22+
- "host.docker.internal:host-gateway"
2023
networks:
2124
- neo4j-symfony
2225

psalm.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
</projectFiles>
1919
<plugins>
2020
<pluginClass class="Psalm\SymfonyPsalmPlugin\Plugin">
21-
<containerXml>var/cache/test/Neo4j_Neo4jBundle_Tests_App_TestKernelTestDebugContainer.xml</containerXml>
21+
<containerXml>var/cache/dev/Neo4j_Neo4jBundle_Tests_App_TestKernelDevDebugContainer.xml</containerXml>
2222
</pluginClass>
2323
<pluginClass class="Psalm\PhpUnitPlugin\Plugin"/>
2424
</plugins>

src/ClientFactory.php

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,15 +56,15 @@ public function create(): SymfonyClient
5656
/** @var ClientBuilder<SummarizedResult<CypherMap>> $builder */
5757
$builder = ClientBuilder::create();
5858

59-
if ($this->driverConfig) {
59+
if (null !== $this->driverConfig) {
6060
$builder = $builder->withDefaultDriverConfiguration($this->makeDriverConfig());
6161
}
6262

63-
if ($this->sessionConfiguration) {
63+
if (null !== $this->sessionConfiguration) {
6464
$builder = $builder->withDefaultSessionConfiguration($this->makeSessionConfig());
6565
}
6666

67-
if ($this->transactionConfiguration) {
67+
if (null !== $this->transactionConfiguration) {
6868
$builder = $builder->withDefaultTransactionConfiguration($this->makeTransactionConfig());
6969
}
7070

@@ -77,7 +77,7 @@ public function create(): SymfonyClient
7777
);
7878
}
7979

80-
if ($this->defaultDriver) {
80+
if (null !== $this->defaultDriver) {
8181
$builder = $builder->withDefaultDriver($this->defaultDriver);
8282
}
8383

src/Collector/Neo4jDataCollector.php

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Neo4j\Neo4jBundle\Collector;
6+
7+
use Neo4j\Neo4jBundle\EventListener\Neo4jProfileListener;
8+
use Symfony\Bundle\FrameworkBundle\DataCollector\AbstractDataCollector;
9+
use Symfony\Component\HttpFoundation\Request;
10+
use Symfony\Component\HttpFoundation\Response;
11+
12+
/**
13+
* @var array{
14+
* successful_statements_count: int,
15+
* failed_statements_count: int,
16+
* statements: array<array-key, array<string, mixed>> | list<array{
17+
* statement: mixed,
18+
* exception: mixed,
19+
* alias: string|null
20+
* }>,
21+
* } $data
22+
*/
23+
final class Neo4jDataCollector extends AbstractDataCollector
24+
{
25+
public function __construct(
26+
private readonly Neo4jProfileListener $subscriber,
27+
) {
28+
}
29+
30+
public function collect(Request $request, Response $response, ?\Throwable $exception = null): void
31+
{
32+
$t = $this;
33+
$profiledSummaries = $this->subscriber->getProfiledSummaries();
34+
$successfulStatements = [];
35+
foreach ($profiledSummaries as $summary) {
36+
$statement = ['status' => 'success'];
37+
foreach ($summary as $key => $value) {
38+
if (!is_array($value) && !is_object($value)) {
39+
$statement[$key] = $value;
40+
continue;
41+
}
42+
43+
$statement[$key] = $t->recursiveToArray($value);
44+
}
45+
$successfulStatements[] = $statement;
46+
}
47+
48+
$failedStatements = array_map(
49+
static fn (array $x) => [
50+
'status' => 'failure',
51+
'time' => $x['time'],
52+
'timestamp' => $x['timestamp'],
53+
'result' => [
54+
'statement' => $x['statement']->toArray(),
55+
],
56+
'exception' => [
57+
'code' => $x['exception']->getErrors()[0]->getCode(),
58+
'message' => $x['exception']->getErrors()[0]->getMessage(),
59+
'classification' => $x['exception']->getErrors()[0]->getClassification(),
60+
'category' => $x['exception']->getErrors()[0]->getCategory(),
61+
'title' => $x['exception']->getErrors()[0]->getTitle(),
62+
],
63+
'alias' => $x['alias'],
64+
],
65+
$this->subscriber->getProfiledFailures()
66+
);
67+
68+
$this->data['successful_statements_count'] = count($successfulStatements);
69+
$this->data['failed_statements_count'] = count($failedStatements);
70+
$mergedArray = array_merge($successfulStatements, $failedStatements);
71+
uasort(
72+
$mergedArray,
73+
static fn (array $a, array $b) => $a['start_time'] <=> $b['timestamp']
74+
);
75+
$this->data['statements'] = $mergedArray;
76+
}
77+
78+
public function reset(): void
79+
{
80+
parent::reset();
81+
$this->subscriber->reset();
82+
}
83+
84+
public function getName(): string
85+
{
86+
return 'neo4j';
87+
}
88+
89+
/** @api */
90+
public function getStatements(): array
91+
{
92+
return $this->data['statements'];
93+
}
94+
95+
public function getSuccessfulStatements(): array
96+
{
97+
return array_filter(
98+
$this->data['statements'],
99+
static fn (array $x) => 'success' === $x['status']
100+
);
101+
}
102+
103+
public function getFailedStatements(): array
104+
{
105+
return array_filter(
106+
$this->data['statements'],
107+
static fn (array $x) => 'failure' === $x['status']
108+
);
109+
}
110+
111+
/** @api */
112+
public function getFailedStatementsCount(): array
113+
{
114+
return $this->data['failed_statements_count'];
115+
}
116+
117+
/** @api */
118+
public function getSuccessfulStatementsCount(): array
119+
{
120+
return $this->data['successful_statements_count'];
121+
}
122+
123+
public function getQueryCount(): int
124+
{
125+
return count($this->data['statements']);
126+
}
127+
128+
public static function getTemplate(): ?string
129+
{
130+
return '@Neo4j/web_profiler.html.twig';
131+
}
132+
133+
private function recursiveToArray(mixed $obj): mixed
134+
{
135+
if (is_array($obj)) {
136+
return array_map(
137+
fn (mixed $x): mixed => $this->recursiveToArray($x),
138+
$obj
139+
);
140+
}
141+
142+
if (is_object($obj) && method_exists($obj, 'toArray')) {
143+
return $obj->toArray();
144+
}
145+
146+
return $obj;
147+
}
148+
}

0 commit comments

Comments
 (0)