From e066b43cef18ce38e126d2495d5ba7054edcee13 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 6 Jan 2026 23:34:06 +0300 Subject: [PATCH 1/8] Add benchmarks --- .github/workflows/benchmark.yml | 31 +++++++++++ composer.json | 4 +- docs/internals.md | 18 ++++++ phpbench.json | 7 +++ tests/Benchmark/BlocksBench.php | 70 +++++++++++++++++++++++ tests/Benchmark/MultiRendererBench.php | 77 ++++++++++++++++++++++++++ tests/Benchmark/NestedViewsBench.php | 49 ++++++++++++++++ tests/Benchmark/ViewBasicBench.php | 26 +++++++++ tests/Benchmark/ViewLocaleBench.php | 32 +++++++++++ tests/Benchmark/ViewThemeBench.php | 44 +++++++++++++++ tests/Benchmark/WebViewAssetsBench.php | 54 ++++++++++++++++++ tests/Benchmark/WebViewBench.php | 26 +++++++++ 12 files changed, 437 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/benchmark.yml create mode 100644 phpbench.json create mode 100644 tests/Benchmark/BlocksBench.php create mode 100644 tests/Benchmark/MultiRendererBench.php create mode 100644 tests/Benchmark/NestedViewsBench.php create mode 100644 tests/Benchmark/ViewBasicBench.php create mode 100644 tests/Benchmark/ViewLocaleBench.php create mode 100644 tests/Benchmark/ViewThemeBench.php create mode 100644 tests/Benchmark/WebViewAssetsBench.php create mode 100644 tests/Benchmark/WebViewBench.php diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml new file mode 100644 index 000000000..459713d70 --- /dev/null +++ b/.github/workflows/benchmark.yml @@ -0,0 +1,31 @@ +on: + pull_request: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + + push: + paths-ignore: + - 'docs/**' + - 'README.md' + - 'CHANGELOG.md' + - '.gitignore' + - '.gitattributes' + - 'infection.json.dist' + - 'psalm.xml' + +name: bechmark + +jobs: + phpbench: + uses: yiisoft/actions/.github/workflows/phpbench.yml@master + with: + os: >- + ['ubuntu-latest', 'windows-latest'] + php: >- + ['8.1'] diff --git a/composer.json b/composer.json index 361dc37e2..39a2415b1 100644 --- a/composer.json +++ b/composer.json @@ -37,6 +37,7 @@ "require-dev": { "bamarni/composer-bin-plugin": "^1.8.3", "maglnet/composer-require-checker": "^4.7.1", + "phpbench/phpbench": "^1.4.3", "phpunit/phpunit": "^10.5.48", "rector/rector": "^2.1.2", "spatie/phpunit-watcher": "^1.24", @@ -82,6 +83,7 @@ }, "scripts": { "test": "phpunit --testdox --no-interaction", - "test-watch": "phpunit-watcher watch" + "test-watch": "phpunit-watcher watch", + "bench": "phpbench run" } } diff --git a/docs/internals.md b/docs/internals.md index 087a514ac..4e7928cdd 100644 --- a/docs/internals.md +++ b/docs/internals.md @@ -42,3 +42,21 @@ all dependencies are correctly defined in `composer.json`. To run the checker, e ```shell ./vendor/bin/composer-require-checker ``` + +## Benchmarks + +The package provides performance benchmarks for the main use-cases, implemented with [phpbench](https://phpbench.readthedocs.io/). + +Benchmarks are located in `tests/Benchmark` and configured via `phpbench.json`. + +To run the benchmarks: + +```shell +./vendor/bin/phpbench run +``` + +Or via Composer script: + +```shell +composer bench +``` diff --git a/phpbench.json b/phpbench.json new file mode 100644 index 000000000..447d32025 --- /dev/null +++ b/phpbench.json @@ -0,0 +1,7 @@ +{ + "runner.bootstrap": "vendor/autoload.php", + "runner.path": "tests/Benchmark", + "runner.revs": 1000, + "runner.iterations": 10, + "runner.retry_threshold": 2 +} diff --git a/tests/Benchmark/BlocksBench.php b/tests/Benchmark/BlocksBench.php new file mode 100644 index 000000000..1b6a31747 --- /dev/null +++ b/tests/Benchmark/BlocksBench.php @@ -0,0 +1,70 @@ +setBlock('block-id-1', '...content of block1...'); +$this->setBlock('block-id-2', '...content of block2...'); +PHP + ); + } + + $layoutView = $basePath . '/layout.php'; + if (!is_file($layoutView)) { + file_put_contents($layoutView, <<<'PHP' + +hasBlock('block-id-1')): ?> + getBlock('block-id-1') ?> + + ... default content for block1 ... + + +hasBlock('block-id-2')): ?> + getBlock('block-id-2') ?> + + ... default content for block2 ... + +PHP + ); + } + + $this->view = new View($basePath, new SimpleEventDispatcher()); + } + + public function benchRenderBlocksWithTemplates(): void + { + // Render content view to define blocks. + $this->view->render('content'); + + // Render layout view which reads the defined blocks. + $this->view->render('layout'); + } +} diff --git a/tests/Benchmark/MultiRendererBench.php b/tests/Benchmark/MultiRendererBench.php new file mode 100644 index 000000000..2390a2302 --- /dev/null +++ b/tests/Benchmark/MultiRendererBench.php @@ -0,0 +1,77 @@ +view = (new View($basePath, new SimpleEventDispatcher())) + ->withRenderers([ + 'blade' => new class () implements TemplateRendererInterface { + public function render(ViewInterface $view, string $template, array $parameters): string + { + return file_get_contents($template); + } + }, + 'tpl' => new class () implements TemplateRendererInterface { + public function render(ViewInterface $view, string $template, array $parameters): string + { + return file_get_contents($template); + } + }, + ]); + } + + public function benchRenderPhpView(): void + { + $this->view->render('view'); + } + + public function benchRenderBladeView(): void + { + $this->view->render('view.blade.php'); + } + + public function benchRenderTplView(): void + { + $this->view->render('view.tpl'); + } +} diff --git a/tests/Benchmark/NestedViewsBench.php b/tests/Benchmark/NestedViewsBench.php new file mode 100644 index 000000000..b0a9f40b6 --- /dev/null +++ b/tests/Benchmark/NestedViewsBench.php @@ -0,0 +1,49 @@ +render('./sub/sub'); +PHP + ); + } + + $subView = $subDir . '/sub.php'; + if (!is_file($subView)) { + file_put_contents($subView, 'nested-view-content'); + } + + $this->view = new View($basePath, new SimpleEventDispatcher()); + } + + public function benchRenderNestedView(): void + { + $this->view->render('base'); + } +} diff --git a/tests/Benchmark/ViewBasicBench.php b/tests/Benchmark/ViewBasicBench.php new file mode 100644 index 000000000..93da3315b --- /dev/null +++ b/tests/Benchmark/ViewBasicBench.php @@ -0,0 +1,26 @@ +view = new View(__DIR__ . '/../public/view', new SimpleEventDispatcher()); + } + + public function benchRenderSimpleView(): void + { + $this->view->render('only-content', [ + 'content' => 'benchmark', + ]); + } +} diff --git a/tests/Benchmark/ViewLocaleBench.php b/tests/Benchmark/ViewLocaleBench.php new file mode 100644 index 000000000..78bdbef39 --- /dev/null +++ b/tests/Benchmark/ViewLocaleBench.php @@ -0,0 +1,32 @@ +view = new View($basePath, new SimpleEventDispatcher()); + $this->view->setLocale('es'); + } + + public function benchRenderLocalizedView(): void + { + $this->view->render('file'); + } +} diff --git a/tests/Benchmark/ViewThemeBench.php b/tests/Benchmark/ViewThemeBench.php new file mode 100644 index 000000000..f6ea1457a --- /dev/null +++ b/tests/Benchmark/ViewThemeBench.php @@ -0,0 +1,44 @@ + $themePath, + ]); + + $this->view = new View($basePath, new SimpleEventDispatcher()); + $this->view->setTheme($theme); + } + + public function benchRenderThemedView(): void + { + $this->view->render('only-content', [ + 'content' => 'benchmark', + ]); + } +} diff --git a/tests/Benchmark/WebViewAssetsBench.php b/tests/Benchmark/WebViewAssetsBench.php new file mode 100644 index 000000000..edb4b1801 --- /dev/null +++ b/tests/Benchmark/WebViewAssetsBench.php @@ -0,0 +1,54 @@ +view = new WebView(__DIR__ . '/../public/view', new SimpleEventDispatcher()); + $this->preparedView = $this->view; + } + + #[Bench\BeforeMethods(['prepareView'])] + public function prepareView(): void + { + $view = $this->view->withClearedState(); + + $view->addCssFiles([ + 'file-1.css', + ['file-2.css', 'crossorigin' => 'anonymous', WebView::POSITION_BEGIN], + ]); + + $view->addJsFiles([ + 'file-1.js', + ['file-2.js', 'async' => true, WebView::POSITION_BEGIN], + ]); + + $view->addCssStrings([ + '.a1 { color: red; }', + ['.a2 { color: red; }', WebView::POSITION_HEAD], + ]); + + $view->addJsStrings([ + 'uniqueName' => 'app1.start();', + 'app2.start();', + ]); + + $this->preparedView = $view; + } + + public function benchRegisterAssetsAndRender(): void + { + $this->preparedView->render('positions.php'); + } +} diff --git a/tests/Benchmark/WebViewBench.php b/tests/Benchmark/WebViewBench.php new file mode 100644 index 000000000..9bdbec4fb --- /dev/null +++ b/tests/Benchmark/WebViewBench.php @@ -0,0 +1,26 @@ +view = new WebView(__DIR__ . '/../public/view', new SimpleEventDispatcher()); + $this->view->setTitle('Posts'); + } + + public function benchRenderLayout(): void + { + $this->view->render('layout', [ + 'content' => 'content', + ]); + } +} From 77167d81061f93d9db4a71af442ca590e8d33557 Mon Sep 17 00:00:00 2001 From: StyleCI Bot Date: Tue, 6 Jan 2026 20:34:34 +0000 Subject: [PATCH 2/8] Apply fixes from StyleCI --- tests/Benchmark/BlocksBench.php | 8 ++++++-- tests/Benchmark/MultiRendererBench.php | 4 +++- tests/Benchmark/NestedViewsBench.php | 4 +++- tests/Benchmark/ViewBasicBench.php | 1 - 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/tests/Benchmark/BlocksBench.php b/tests/Benchmark/BlocksBench.php index 1b6a31747..9aaff292e 100644 --- a/tests/Benchmark/BlocksBench.php +++ b/tests/Benchmark/BlocksBench.php @@ -19,7 +19,9 @@ public function __construct() $contentView = $basePath . '/content.php'; if (!is_file($contentView)) { - file_put_contents($contentView, <<<'PHP' + file_put_contents( + $contentView, + <<<'PHP' Date: Tue, 6 Jan 2026 20:34:57 +0000 Subject: [PATCH 3/8] Apply Rector changes (CI) --- tests/Benchmark/BlocksBench.php | 2 +- tests/Benchmark/MultiRendererBench.php | 20 ++++++++++---------- tests/Benchmark/NestedViewsBench.php | 2 +- tests/Benchmark/ViewBasicBench.php | 2 +- tests/Benchmark/ViewLocaleBench.php | 2 +- tests/Benchmark/ViewThemeBench.php | 2 +- tests/Benchmark/WebViewAssetsBench.php | 2 +- tests/Benchmark/WebViewBench.php | 2 +- 8 files changed, 17 insertions(+), 17 deletions(-) diff --git a/tests/Benchmark/BlocksBench.php b/tests/Benchmark/BlocksBench.php index 9aaff292e..e82a80d66 100644 --- a/tests/Benchmark/BlocksBench.php +++ b/tests/Benchmark/BlocksBench.php @@ -10,7 +10,7 @@ final class BlocksBench { - private View $view; + private readonly View $view; public function __construct() { diff --git a/tests/Benchmark/MultiRendererBench.php b/tests/Benchmark/MultiRendererBench.php index d60a73743..1c0d048f4 100644 --- a/tests/Benchmark/MultiRendererBench.php +++ b/tests/Benchmark/MultiRendererBench.php @@ -12,7 +12,7 @@ final class MultiRendererBench { - private View $view; + private readonly View $view; public function __construct() { @@ -23,15 +23,15 @@ public function __construct() if (!is_file($phpView)) { file_put_contents( $phpView, - <<<'PHP' - Date: Tue, 6 Jan 2026 20:35:04 +0000 Subject: [PATCH 4/8] Apply fixes from StyleCI --- tests/Benchmark/MultiRendererBench.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/Benchmark/MultiRendererBench.php b/tests/Benchmark/MultiRendererBench.php index 1c0d048f4..72cf88867 100644 --- a/tests/Benchmark/MultiRendererBench.php +++ b/tests/Benchmark/MultiRendererBench.php @@ -25,11 +25,11 @@ public function __construct() $phpView, <<<'PHP_WRAP' Date: Tue, 6 Jan 2026 23:37:01 +0300 Subject: [PATCH 5/8] Remove workflow --- .github/workflows/benchmark.yml | 31 ------------------------------- 1 file changed, 31 deletions(-) delete mode 100644 .github/workflows/benchmark.yml diff --git a/.github/workflows/benchmark.yml b/.github/workflows/benchmark.yml deleted file mode 100644 index 459713d70..000000000 --- a/.github/workflows/benchmark.yml +++ /dev/null @@ -1,31 +0,0 @@ -on: - pull_request: - paths-ignore: - - 'docs/**' - - 'README.md' - - 'CHANGELOG.md' - - '.gitignore' - - '.gitattributes' - - 'infection.json.dist' - - 'psalm.xml' - - push: - paths-ignore: - - 'docs/**' - - 'README.md' - - 'CHANGELOG.md' - - '.gitignore' - - '.gitattributes' - - 'infection.json.dist' - - 'psalm.xml' - -name: bechmark - -jobs: - phpbench: - uses: yiisoft/actions/.github/workflows/phpbench.yml@master - with: - os: >- - ['ubuntu-latest', 'windows-latest'] - php: >- - ['8.1'] From a42f62f7f42159f94d07b579de24bef7ffb1a695 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 6 Jan 2026 23:45:22 +0300 Subject: [PATCH 6/8] Fix --- tests/Benchmark/WebViewAssetsBench.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Benchmark/WebViewAssetsBench.php b/tests/Benchmark/WebViewAssetsBench.php index 0f4273f63..7765dae7f 100644 --- a/tests/Benchmark/WebViewAssetsBench.php +++ b/tests/Benchmark/WebViewAssetsBench.php @@ -19,7 +19,7 @@ public function __construct() $this->preparedView = $this->view; } - #[Bench\BeforeMethods(['prepareView'])] + #[Bench\BeforeMethods(['benchRegisterAssetsAndRender'])] public function prepareView(): void { $view = $this->view->withClearedState(); From 029f521138108bfdf889ce910e42533e2dbbbd58 Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Tue, 6 Jan 2026 23:46:59 +0300 Subject: [PATCH 7/8] Add event dispatched to require-dev --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 39a2415b1..868db0330 100644 --- a/composer.json +++ b/composer.json @@ -44,7 +44,8 @@ "yiisoft/aliases": "^3.0", "yiisoft/di": "^1.4", "yiisoft/psr-dummy-provider": "^1.0.2", - "yiisoft/test-support": "^3.0.2" + "yiisoft/test-support": "^3.0.2", + "psr/event-dispatcher": "^1.0" }, "extra": { "config-plugin-options": { From 7fae7fd54d4729aa48d5876aa37491796aaa33bd Mon Sep 17 00:00:00 2001 From: Alexander Makarov Date: Wed, 7 Jan 2026 00:01:00 +0300 Subject: [PATCH 8/8] Revert --- composer.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 868db0330..39a2415b1 100644 --- a/composer.json +++ b/composer.json @@ -44,8 +44,7 @@ "yiisoft/aliases": "^3.0", "yiisoft/di": "^1.4", "yiisoft/psr-dummy-provider": "^1.0.2", - "yiisoft/test-support": "^3.0.2", - "psr/event-dispatcher": "^1.0" + "yiisoft/test-support": "^3.0.2" }, "extra": { "config-plugin-options": {