packages/deriver/bench/LaravelBench.php
1<?php
2
3declare(strict_types=1);
4
5namespace Bench;
6
7use Deriver\Deriver;
8use Deriver\Deriver\BranchEvaluators;
9use Deriver\Deriver\Candidate\CandidateCollection;
10use Deriver\Deriver\DeriverConfig;
11use Deriver\Deriver\MicroEvaluator;
12use Deriver\Finder;
13use PhpBench\Attributes as Bench;
14use RuntimeException;
15
16#[Bench\Iterations(5)]
17#[Bench\Revs(1)]
18#[Bench\Warmup(0)]
19#[Bench\OutputTimeUnit('milliseconds')]
20final class LaravelBench
21{
22 private const QUERIES = [
23 'App\Services\PublishingRules::initialState#return',
24 'App\Services\PublishingRules::nextState#return',
25 'App\Services\PublishingRules::escalationHours#return',
26 'App\Services\Publishing::create#return',
27 ];
28
29 /** @var list<string> */
30 private array $files;
31 private DeriverConfig $configuration;
32 private Deriver $prepared;
33 /** @var list<CandidateCollection> */
34 private array $results = [];
35
36 public function locate(): void
37 {
38 $this->locateWithEvaluators([]);
39 }
40
41 public function locateWithBranchPruning(): void
42 {
43 $this->locateWithEvaluators(BranchEvaluators::truth());
44 }
45
46 /** @param list<MicroEvaluator> $evaluators */
47 public function locateWithEvaluators(array $evaluators): void
48 {
49 $root = __DIR__ . '/target/laravel';
50 $this->files = (new Finder())->in($root)
51 ->include('**/*.php', 'artisan')
52 ->exclude('vendor', 'tests', 'storage', 'bootstrap/cache', 'resources/views', 'database/factories', 'database/seeders')
53 ->files();
54 // Include the framework traits used by these application sources.
55 $this->files = [
56 ...$this->files,
57 $root . '/vendor/laravel/framework/src/Illuminate/Bus/Queueable.php',
58 $root . '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/Factories/HasFactory.php',
59 $root . '/vendor/laravel/framework/src/Illuminate/Database/Eloquent/SoftDeletes.php',
60 $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Bus/Dispatchable.php',
61 $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Events/Dispatchable.php',
62 $root . '/vendor/laravel/framework/src/Illuminate/Foundation/Queue/Queueable.php',
63 $root . '/vendor/laravel/framework/src/Illuminate/Notifications/HasDatabaseNotifications.php',
64 $root . '/vendor/laravel/framework/src/Illuminate/Notifications/Notifiable.php',
65 $root . '/vendor/laravel/framework/src/Illuminate/Notifications/RoutesNotifications.php',
66 $root . '/vendor/laravel/framework/src/Illuminate/Queue/InteractsWithQueue.php',
67 $root . '/vendor/laravel/framework/src/Illuminate/Queue/SerializesAndRestoresModelIdentifiers.php',
68 $root . '/vendor/laravel/framework/src/Illuminate/Queue/SerializesModels.php',
69 $root . '/vendor/laravel/framework/src/Illuminate/Support/InteractsWithTime.php',
70 $root . '/vendor/laravel/sanctum/src/HasApiTokens.php',
71 ];
72 sort($this->files, SORT_STRING);
73 $this->configuration = new DeriverConfig(phpVersion: '8.4', evaluators: $evaluators);
74 }
75
76 public function prepare(): void
77 {
78 $this->locate();
79 $this->prepared = Deriver::prepare($this->files, $this->configuration);
80 }
81
82 public function prepareWithBranchPruning(): void
83 {
84 $this->locateWithBranchPruning();
85 $this->prepared = Deriver::prepare($this->files, $this->configuration);
86 }
87
88 #[Bench\BeforeMethods('locate')]
89 #[Bench\Groups(['preparation'])]
90 public function benchPrepare(): void
91 {
92 $this->prepared = Deriver::prepare($this->files, $this->configuration);
93 }
94
95 #[Bench\BeforeMethods('prepare')]
96 #[Bench\Groups(['first-query'])]
97 #[Bench\AfterMethods('verifyResults')]
98 public function benchFirstQuery(): void
99 {
100 $this->results = [$this->prepared->derive(self::QUERIES[0])];
101 }
102
103 #[Bench\BeforeMethods('prepare')]
104 #[Bench\Groups(['query-batch'])]
105 #[Bench\AfterMethods('verifyResults')]
106 public function benchQueryBatch(): void
107 {
108 $this->results = [];
109 foreach (self::QUERIES as $query) {
110 $this->results[] = $this->prepared->derive($query);
111 }
112 }
113
114 #[Bench\BeforeMethods('locateWithBranchPruning')]
115 #[Bench\Groups(['preparation', 'branch-pruning'])]
116 public function benchPrepareWithBranchPruning(): void
117 {
118 $this->benchPrepare();
119 }
120
121 #[Bench\BeforeMethods('prepareWithBranchPruning')]
122 #[Bench\Groups(['first-query', 'branch-pruning'])]
123 #[Bench\AfterMethods('verifyResults')]
124 public function benchFirstQueryWithBranchPruning(): void
125 {
126 $this->benchFirstQuery();
127 }
128
129 #[Bench\BeforeMethods('prepareWithBranchPruning')]
130 #[Bench\Groups(['query-batch', 'branch-pruning'])]
131 #[Bench\AfterMethods('verifyResults')]
132 public function benchQueryBatchWithBranchPruning(): void
133 {
134 $this->benchQueryBatch();
135 }
136
137 public function verifyResults(): void
138 {
139 foreach ($this->results as $collection) {
140 if (count($collection) === 0) {
141 throw new RuntimeException('A benchmark query returned no candidates.');
142 }
143 }
144 }
145}
146