Created
July 25, 2025 12:09
-
-
Save eighty9nine/a2f676da9dfd8d319cd7b2a356957ea5 to your computer and use it in GitHub Desktop.
Benchmarking Laravel helpers
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| namespace App\Console\Commands; | |
| use Illuminate\Console\Command; | |
| use Illuminate\Support\Benchmark; | |
| use Illuminate\Support\Str; | |
| use Illuminate\Support\Arr; | |
| use Illuminate\Support\Collection; | |
| class BenchmarkHelpersCommand extends Command | |
| { | |
| protected $signature = 'benchmark:helpers {--iterations=1000 : Number of iterations for each test}'; | |
| protected $description = 'Benchmark Laravel helpers vs PHP native implementations'; | |
| private $results = []; | |
| public function handle() | |
| { | |
| $iterations = (int) $this->option('iterations'); | |
| $this->info("Benchmarking Laravel helpers vs PHP native implementations"); | |
| $this->info("Running {$iterations} iterations for each test"); | |
| // Test data | |
| $testArray = range(1, 1000); | |
| $testString = 'Hello World, this is a test string for benchmarking purposes!'; | |
| $testCollection = collect($testArray); | |
| $nestedArray = [ | |
| 'user' => [ | |
| 'profile' => [ | |
| 'name' => 'John Doe', | |
| 'email' => 'john@example.com' | |
| ] | |
| ] | |
| ]; | |
| // Count total benchmarks for progress bar | |
| $totalBenchmarks = 11; // Update this if you add more benchmarks | |
| $bar = $this->output->createProgressBar($totalBenchmarks); | |
| $bar->start(); | |
| $this->benchmarkStringOperations($testString, $iterations, $bar); | |
| $this->benchmarkArrayOperations($testArray, $nestedArray, $iterations, $bar); | |
| $this->benchmarkCollectionOperations($testCollection, $iterations, $bar); | |
| $this->benchmarkMiscOperations($iterations, $bar); | |
| $bar->finish(); | |
| $this->line("\n"); | |
| $this->displayAllResults(); | |
| } | |
| private function benchmarkStringOperations(string $testString, int $iterations, $bar) | |
| { | |
| // Str::slug vs manual implementation | |
| $results = Benchmark::measure([ | |
| 'Str::slug()' => fn() => $this->runIterations($iterations, fn() => Str::slug($testString)), | |
| 'PHP native slug' => fn() => $this->runIterations($iterations, fn() => $this->manualSlug($testString)), | |
| ], 5); | |
| $this->storeResults('String Slug Generation -- Str::slug()', $results); | |
| $bar->advance(); | |
| // Str::camel vs manual implementation | |
| $results = Benchmark::measure([ | |
| 'Str::camel()' => fn() => $this->runIterations($iterations, fn() => Str::camel($testString)), | |
| 'PHP native camel' => fn() => $this->runIterations($iterations, fn() => $this->manualCamel($testString)), | |
| ], 5); | |
| $this->storeResults('Camel Case Conversion -- Str::camel()', $results); | |
| $bar->advance(); | |
| // Str::length vs strlen | |
| $results = Benchmark::measure([ | |
| 'Str::length()' => fn() => $this->runIterations($iterations, fn() => Str::length($testString)), | |
| 'strlen()' => fn() => $this->runIterations($iterations, fn() => strlen($testString)), | |
| ], 5); | |
| $this->storeResults('String Length -- Str::length()', $results); | |
| $bar->advance(); | |
| // Str::contains vs strpos | |
| $results = Benchmark::measure([ | |
| 'Str::contains()' => fn() => $this->runIterations($iterations, fn() => Str::contains($testString, 'test')), | |
| 'strpos() !== false' => fn() => $this->runIterations($iterations, fn() => strpos($testString, 'test') !== false), | |
| ], 5); | |
| $this->storeResults('String Contains Check -- Str::contains()', $results); | |
| $bar->advance(); | |
| } | |
| private function benchmarkArrayOperations(array $testArray, array $nestedArray, int $iterations, $bar) | |
| { | |
| // Arr::get vs manual array access | |
| $results = Benchmark::measure([ | |
| 'Arr::get()' => fn() => $this->runIterations($iterations, fn() => Arr::get($nestedArray, 'user.profile.name')), | |
| 'PHP manual access' => fn() => $this->runIterations($iterations, fn() => $this->manualArrayGet($nestedArray, 'user.profile.name')), | |
| ], 5); | |
| $this->storeResults('Nested Array Access -- Arr::get()', $results); | |
| $bar->advance(); | |
| // Arr::first vs manual implementation | |
| $results = Benchmark::measure([ | |
| 'Arr::first()' => fn() => $this->runIterations($iterations, fn() => Arr::first($testArray, fn($item) => $item > 50)), | |
| 'PHP manual first' => fn() => $this->runIterations($iterations, fn() => $this->manualArrayFirst($testArray, fn($item) => $item > 50)), | |
| ], 5); | |
| $this->storeResults('Array First Match -- Arr::first()', $results); | |
| $bar->advance(); | |
| // Arr::flatten vs manual implementation | |
| $nestedTestArray = [[1, 2], [3, 4], [5, [6, 7]]]; | |
| $results = Benchmark::measure([ | |
| 'Arr::flatten()' => fn() => $this->runIterations($iterations, fn() => Arr::flatten($nestedTestArray)), | |
| 'PHP manual flatten' => fn() => $this->runIterations($iterations, fn() => $this->manualFlatten($nestedTestArray)), | |
| ], 5); | |
| $this->storeResults('Array Flatten -- Arr::flatten()', $results); | |
| $bar->advance(); | |
| } | |
| private function benchmarkCollectionOperations(Collection $testCollection, int $iterations, $bar) | |
| { | |
| // Collection::map vs array_map | |
| $results = Benchmark::measure([ | |
| 'Collection::map()' => fn() => $this->runIterations($iterations, fn() => $testCollection->map(fn($item) => $item * 2)), | |
| 'array_map()' => fn() => $this->runIterations($iterations, fn() => array_map(fn($item) => $item * 2, $testCollection->toArray())), | |
| ], 5); | |
| $this->storeResults('Map Operation -- Collection::map()', $results); | |
| $bar->advance(); | |
| // Collection::filter vs array_filter | |
| $results = Benchmark::measure([ | |
| 'Collection::filter()' => fn() => $this->runIterations($iterations, fn() => $testCollection->filter(fn($item) => $item > 50)), | |
| 'array_filter()' => fn() => $this->runIterations($iterations, fn() => array_filter($testCollection->toArray(), fn($item) => $item > 50)), | |
| ], 5); | |
| $this->storeResults('Filter Operation -- Collection::filter()', $results); | |
| $bar->advance(); | |
| // Collection::sum vs array_sum | |
| $results = Benchmark::measure([ | |
| 'Collection::sum()' => fn() => $this->runIterations($iterations, fn() => $testCollection->sum()), | |
| 'array_sum()' => fn() => $this->runIterations($iterations, fn() => array_sum($testCollection->toArray())), | |
| ], 5); | |
| $this->storeResults('Sum Operation -- Collection::sum()', $results); | |
| $bar->advance(); | |
| } | |
| private function benchmarkMiscOperations(int $iterations, $bar) | |
| { | |
| // value() helper vs direct assignment | |
| $testValue = 'test'; | |
| $results = Benchmark::measure([ | |
| 'value() helper' => fn() => $this->runIterations($iterations, fn() => value($testValue)), | |
| 'Direct assignment' => fn() => $this->runIterations($iterations, fn() => $testValue), | |
| ], 5); | |
| $this->storeResults('Value Helper -- Collection::sum()', $results); | |
| $bar->advance(); | |
| // filled() vs !empty() | |
| $testVar = 'not empty'; | |
| $results = Benchmark::measure([ | |
| 'filled() helper' => fn() => $this->runIterations($iterations, fn() => filled($testVar)), | |
| '!empty()' => fn() => $this->runIterations($iterations, fn() => !empty($testVar)), | |
| ], 5); | |
| $this->storeResults('Filled Check -- filled()', $results); | |
| $bar->advance(); | |
| // blank() vs empty() | |
| $emptyVar = ''; | |
| $results = Benchmark::measure([ | |
| 'blank() helper' => fn() => $this->runIterations($iterations, fn() => blank($emptyVar)), | |
| 'empty()' => fn() => $this->runIterations($iterations, fn() => empty($emptyVar)), | |
| ], 5); | |
| $this->storeResults('Blank Check -- blank()', $results); | |
| $bar->advance(); | |
| } | |
| private function runIterations(int $iterations, callable $callback) | |
| { | |
| for ($i = 0; $i < $iterations; $i++) { | |
| $callback(); | |
| } | |
| } | |
| private function storeResults(string $operation, array $results) | |
| { | |
| $this->results[] = [ | |
| 'operation' => $operation, | |
| 'results' => $results | |
| ]; | |
| }private function displayAllResults() | |
| { | |
| $this->info("\n🚀 Benchmark Results Summary:"); | |
| $this->line(''); | |
| $tableData = []; | |
| foreach ($this->results as $benchmark) { | |
| $operation = $benchmark['operation']; | |
| $results = $benchmark['results']; | |
| // Identify helper and native by known patterns | |
| $helperKey = collect(array_keys($results))->first(fn($name) => | |
| Str::contains($name, ['Str::', 'Arr::', 'Collection::', 'value()', 'filled()', 'blank()']) | |
| ); | |
| $nativeKey = collect(array_keys($results))->first(fn($name) => $name !== $helperKey); | |
| $helperTime = $results[$helperKey]; | |
| $nativeTime = $results[$nativeKey]; | |
| $fastest = min($helperTime, $nativeTime); | |
| $formattedNative = number_format($nativeTime, 2).'s' . ($nativeTime === $fastest ? ' (fastest)' : ''); | |
| $formattedHelper = number_format($helperTime, 2).'s' . ($helperTime === $fastest ? ' (fastest)' : ''); | |
| $tableData[] = [ | |
| 'Category' => $operation, | |
| 'Native (e.g., ' . $nativeKey . ')' => $formattedNative, | |
| 'Helper (e.g., ' . $helperKey . ')' => $formattedHelper, | |
| ]; | |
| } | |
| $this->table( | |
| ['Category', 'Native', 'Helper'], | |
| $tableData | |
| ); | |
| $this->line(''); | |
| $this->info('💡 Tips:'); | |
| $this->line(' • Lower times are better'); | |
| $this->line(' • "(fastest)" marks the quicker implementation'); | |
| $this->line(' • Helper methods improve readability, but check performance for hot paths'); | |
| } | |
| // Manual implementations for comparison | |
| private function manualSlug(string $string): string | |
| { | |
| $string = strtolower($string); | |
| $string = preg_replace('/[^a-z0-9\-\s]/', '', $string); | |
| $string = preg_replace('/[\s\-]+/', '-', $string); | |
| return trim($string, '-'); | |
| } | |
| private function manualCamel(string $string): string | |
| { | |
| $string = strtolower($string); | |
| $string = preg_replace('/[^a-z0-9\s]/', ' ', $string); | |
| $words = explode(' ', $string); | |
| $camel = array_shift($words); | |
| foreach ($words as $word) { | |
| if (!empty($word)) { | |
| $camel .= ucfirst($word); | |
| } | |
| } | |
| return $camel; | |
| } | |
| private function manualArrayGet(array $array, string $key, $default = null) | |
| { | |
| $keys = explode('.', $key); | |
| $result = $array; | |
| foreach ($keys as $segment) { | |
| if (!is_array($result) || !array_key_exists($segment, $result)) { | |
| return $default; | |
| } | |
| $result = $result[$segment]; | |
| } | |
| return $result; | |
| } | |
| private function manualArrayFirst(array $array, callable $callback = null, $default = null) | |
| { | |
| foreach ($array as $key => $value) { | |
| if ($callback === null || $callback($value, $key)) { | |
| return $value; | |
| } | |
| } | |
| return $default; | |
| } | |
| private function manualFlatten(array $array): array | |
| { | |
| $result = []; | |
| foreach ($array as $item) { | |
| if (is_array($item)) { | |
| $result = array_merge($result, $this->manualFlatten($item)); | |
| } else { | |
| $result[] = $item; | |
| } | |
| } | |
| return $result; | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment