sebastian/lines-of-code
Count lines of code in PHP source with sebastian/lines-of-code. A lightweight library for analyzing PHP files and reporting LOC metrics, useful for tooling, CI checks, and development workflows. Install easily via Composer as a runtime or dev dependency.
Install via Composer as a dev dependency:
composer require --dev sebastian/lines-of-code
First use case: Count LOC in your src/ directory from an Artisan command:
use SebastianBergmann\LinesOfCode\Counter;
$counter = new Counter();
$result = $counter->count(base_path('src'));
// Output results
echo "Total LOC: {$result->linesOfCode}\n";
echo "Comment lines: {$result->commentLinesOfCode}\n";
echo "Non-comment lines: {$result->nonCommentLinesOfCode}\n";
Where to look first:
- name: Enforce LOC limits
run: |
php -r "require 'vendor/autoload.php'; \
\$counter = new \SebastianBergmann\LinesOfCode\Counter(); \
\$result = \$counter->count('src'); \
if (\$result->linesOfCode > 5000) exit(1);"
// app/Console/Commands/CountLoc.php
protected $signature = 'loc:count {--dir=src : Directory to scan}';
public function handle() {
$counter = new Counter();
$result = $counter->count(base_path($this->option('dir')));
$this->table(['Metric', 'Value'], [
['Total LOC', $result->linesOfCode],
['Comments', $result->commentLinesOfCode],
['Code Lines', $result->nonCommentLinesOfCode],
]);
}
public function testNoMassivePr() {
$counter = new Counter();
$result = $counter->count('src');
$this->assertLessThan(10000, $result->linesOfCode,
"Codebase exceeds sustainable size");
}
Directory Scanning: Use glob() to target specific patterns:
$files = glob(base_path('src/**/*.php'));
$result = $counter->count($files);
Caching Results (for CI):
$cacheKey = 'loc:src';
$result = cache()->remember($cacheKey, now()->addHours(1), function() use ($counter) {
return $counter->count(base_path('src'));
});
Laravel Event Listener (track model complexity):
// app/Listeners/TrackModelLoc.php
public function handle() {
$counter = new Counter();
$result = $counter->count(app_path('Models'));
event(new ModelLocUpdated($result));
}
Multi-Directory Comparison:
$results = [
'src' => $counter->count(base_path('src')),
'tests' => $counter->count(base_path('tests')),
];
// app/Providers/AppServiceProvider.php
public function register() {
$this->app->singleton(Counter::class, function() {
return new Counter();
});
}
// app/Console/Commands/AsyncLocCount.php
public function handle() {
dispatch(new CountLocJob(base_path('src')));
}
// app/Helpers/LocHelper.php
if (!function_exists('loc_metrics')) {
function loc_metrics($directory) {
$counter = app(Counter::class);
return $counter->count($directory);
}
}
PHP Version Mismatch:
FROM php:8.4-cli
WORKDIR /app
COPY . .
RUN composer install
CMD ["php", "artisan", "loc:count"]
Parser Conflicts:
nikic/php-parser may conflict with other tools (e.g., PHPStan).composer.json:
"require-dev": {
"nikic/php-parser": "5.0.0"
}
Comment Line Edge Cases:
// comment /* block */) were historically double-counted (fixed in v5.0.1).// inline /* block */ comment
Performance with Large Codebases:
$counter->count(glob(base_path('src/*/'))); // Process subdirs separately
False Negatives:
<<<EOD) may not be counted as code.Inspect Raw Results:
$result = $counter->count('src');
dd($result->getFiles()); // Array of File objects with per-file stats
Check Parser Errors:
try {
$result = $counter->count('src');
} catch (\SebastianBergmann\LinesOfCode\ParserError $e) {
report($e);
throw new \RuntimeException("LOC parsing failed", 0, $e);
}
Compare with cloc:
cloc and verify:
cloc src/ --by-file --include-ext=php
Custom File Filters:
$counter = new Counter();
$counter->addFilter(function($file) {
return strpos($file, 'Tests/') === false; // Exclude tests
});
Post-Processing Results:
$result = $counter->count('src');
$metrics = [
'density' => $result->nonCommentLinesOfCode / $result->linesOfCode,
'growth' => $result->linesOfCode - old('loc.src', 0),
];
Integrate with Laravel Scout:
// Track LOC as a searchable metric
Loc::create([
'directory' => 'src',
'lines_of_code' => $result->linesOfCode,
'timestamp' => now(),
]);
Case-Sensitive Paths:
src/ vs Src/ on case-insensitive filesystems).Symlink Handling:
$counter->setFollowSymlinks(false);
Memory Limits:
# GitHub Actions
env:
PHP_MEMORY_LIMIT: 2G
Track LOC Over Time:
// Store in database
DB::table('loc_history')->insert([
'directory' => 'src',
'lines_of_code' => $result->linesOfCode,
'created_at' => now(),
]);
Slack Alerts for Spikes:
if ($result->linesOfCode > old('loc.src', 0) * 1.5) {
Notifiable::route('slack', config('services.slack.webhook'))
->notify(new LocSpikeAlert($result));
}
Exclude Vendors/Generated Code:
$counter->addFilter(function($file) {
return !str_contains($file, ['vendor/', 'bootstrap/cache/']);
});
Benchmark Against Baselines:
$baseline = 5000; // From initial audit
if ($result->linesOfCode > $baseline * 1.2) {
// Trigger review process
}
How can I help you explore Laravel packages today?