diff --git a/.github/workflows/analyse.yml b/.github/workflows/analyse.yml
new file mode 100644
index 0000000..c67aaa4
--- /dev/null
+++ b/.github/workflows/analyse.yml
@@ -0,0 +1,25 @@
+name: PHPStan
+
+on: ['push', 'pull_request']
+
+jobs:
+ test:
+ runs-on: ubuntu-latest
+ name: analyse
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v2
+
+ - name: Setup PHP
+ uses: shivammathur/setup-php@v2
+ with:
+ php-version: 8.2
+ extensions: dom, curl, libxml, mbstring, zip, pdo, sqlite, pdo_sqlite, bcmath, soap, intl, gd, exif, iconv, imagick, fileinfo
+ coverage: none
+
+ - name: Install dependencies
+ run: composer install --no-interaction
+
+ - name: Analyse
+ run: vendor/bin/phpstan analyse
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..24df6ff
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,4 @@
+.DS_Store
+.idea
+composer.lock
+/vendor
diff --git a/Model/Profiler/XhprofProfiler.php b/Model/Profiler/XhprofProfiler.php
new file mode 100644
index 0000000..b465c54
--- /dev/null
+++ b/Model/Profiler/XhprofProfiler.php
@@ -0,0 +1,57 @@
+tags = $tags;
+ }
+
+ public function handle(): void
+ {
+ $this->driver->start(
+ [
+ self::IGNORED_FUNCTIONS_KEY => ['SpiralPackages\Profiler\Profiler::end'],
+ ]
+ );
+ }
+
+ /**
+ * @throws FileSystemException
+ * @throws RuntimeException
+ */
+ public function terminate(array $tags = []): void
+ {
+ $result = $this->driver->end();
+ $endpoint = $this->deploymentConfig->get('xhprofprofiler/endpoint');
+ $appName = $this->deploymentConfig->get('xhprofprofiler/app_name');
+ if (!empty($appName) && !empty($endpoint) && is_string($endpoint) && is_string($appName)) {
+ $storage = new WebStorage(new NativeHttpClient(), $endpoint);
+ $storage->store(
+ $appName,
+ \array_merge($this->tags ?? [], $tags),
+ new \DateTimeImmutable(),
+ $result
+ );
+ }
+ }
+}
diff --git a/Observer/StartXhprof.php b/Observer/StartXhprof.php
new file mode 100644
index 0000000..812ab1e
--- /dev/null
+++ b/Observer/StartXhprof.php
@@ -0,0 +1,37 @@
+isEnabled($observer->getRequest())) {
+ $this->profiler->handle();
+ }
+ }
+
+ public function isEnabled(Http $request): bool
+ {
+ if ($request->getHeader(XhprofProfiler::HEADER)) {
+ return filter_var($request->getHeader(XhprofProfiler::HEADER), FILTER_VALIDATE_BOOLEAN);
+ }
+
+ try {
+ return (bool) getenv('XHPROF_ENABLED');
+ } catch (Throwable) {
+ return false;
+ }
+ }
+}
diff --git a/Observer/StopXhprof.php b/Observer/StopXhprof.php
new file mode 100644
index 0000000..da0847f
--- /dev/null
+++ b/Observer/StopXhprof.php
@@ -0,0 +1,39 @@
+isEnabled($observer->getRequest())) {
+ $this->profiler->terminate(
+ ['route' => $observer->getRequest()->getFullActionName()]
+ );
+ }
+ }
+
+ public function isEnabled(Http $request): bool
+ {
+ if ($request->getHeader(XhprofProfiler::HEADER)) {
+ return filter_var($request->getHeader(XhprofProfiler::HEADER), FILTER_VALIDATE_BOOLEAN);
+ }
+
+ try {
+ return (bool) getenv('XHPROF_ENABLED');
+ } catch (Throwable) {
+ return false;
+ }
+ }
+}
diff --git a/composer.json b/composer.json
new file mode 100644
index 0000000..1e551c0
--- /dev/null
+++ b/composer.json
@@ -0,0 +1,45 @@
+{
+ "name": "justbetter/magento2-xhprof-profiler",
+ "description": "Xhprof profiler integration for Magento",
+ "keywords": [
+ "justbetter",
+ "magento",
+ "buggregator",
+ "xhprof"
+ ],
+ "type": "magento2-module",
+ "license": "MIT",
+ "require": {
+ "php": ">=8.2",
+ "magento/framework": "*",
+ "magento/module-config": "^101.2",
+ "spiral-packages/profiler": "^1.2"
+ },
+ "repositories": {
+ "magento": {
+ "type": "composer",
+ "url": "https://repo-magento-mirror.fooman.co.nz/"
+ }
+ },
+ "authors": [
+ {
+ "name": "Robin Mulder",
+ "email": "robin@justbetter.nl",
+ "homepage": "https://justbetter.nl",
+ "role": "Developer"
+ }
+ ],
+ "autoload": {
+ "psr-4": { "JustBetter\\XhprofProfiler\\": "" },
+ "files": [ "registration.php" ]
+ },
+ "require-dev": {
+ "bitexpert/phpstan-magento": "^0.30.1",
+ "phpstan/phpstan": "^1.10"
+ },
+ "config": {
+ "allow-plugins": {
+ "magento/composer-dependency-version-audit-plugin": true
+ }
+ }
+}
diff --git a/etc/events.xml b/etc/events.xml
new file mode 100644
index 0000000..398d359
--- /dev/null
+++ b/etc/events.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/etc/module.xml b/etc/module.xml
new file mode 100644
index 0000000..02176c0
--- /dev/null
+++ b/etc/module.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
diff --git a/phpstan.neon b/phpstan.neon
new file mode 100644
index 0000000..7593d96
--- /dev/null
+++ b/phpstan.neon
@@ -0,0 +1,10 @@
+includes:
+ - vendor/bitexpert/phpstan-magento/extension.neon
+parameters:
+ paths:
+ - .
+ excludePaths:
+ - vendor
+ - Test/*
+ level: 9
+ checkMissingIterableValueType: false
diff --git a/registration.php b/registration.php
new file mode 100644
index 0000000..0594981
--- /dev/null
+++ b/registration.php
@@ -0,0 +1,9 @@
+