tomasvotruba/bladestan
Bladestan adds PHPStan-powered static analysis for Laravel Blade templates. Install as a dev dependency and include its extension if needed. Provides a custom “blade” error formatter showing clickable template paths and where errors are rendered.
Installation:
composer require --dev tomasvotruba/bladestan
Add it to your composer.json under require-dev to ensure it runs in CI/CD pipelines.
Configuration:
phpstan.neon:
includes:
- ./vendor/tomasvotruba/bladestan/config/extension.neon
First Run: Analyze your Blade templates with the custom error formatter:
vendor/bin/phpstan analyze --error-format=blade
This will show clickable template paths and line numbers in errors, e.g.:
Line 15 app/Views/post_codex.blade.php: Call to undefined method App\Entity\Post::getContent().
Problem: A Blade template calls a non-existent method on a passed model:
@{{ post.getContent() }} <!-- post is an App\Entity\Post, but getContent() doesn't exist -->
Solution:
vendor/bin/phpstan analyze --error-format=blade
getContent() to Post model.post->content).Pre-Commit Hooks:
Add Bladestan to your pre-commit script (e.g., using husky or laravel-pint):
# .husky/pre-commit
vendor/bin/phpstan analyze --error-format=blade --level=5
CI/CD Pipeline: Run Bladestan in your CI (e.g., GitHub Actions) to block template errors:
# .github/workflows/ci.yml
- name: Run Bladestan
run: vendor/bin/phpstan analyze --error-format=blade --level=5
IDE Integration:
--error-format=blade for clickable links.Override Bladestan’s default rules in phpstan.neon:
parameters:
level: 5
paths:
- app
- resources/views
excludePaths:
- tests
blade:
# Ignore specific undefined methods (e.g., legacy code)
ignoredUndefinedMethods:
- App\Models\Post::getLegacyContent
# Treat @error directives as strict
strictErrorDirectives: true
Target only critical templates (e.g., emails or admin views):
vendor/bin/phpstan analyze --error-format=blade resources/views/emails resources/views/admin
Bladestan supports Livewire components. To validate a component’s Blade template:
<!-- resources/views/livewire/counter.blade.php -->
<div>Count: {{ $count }}</div>
Run Bladestan to ensure $count is properly passed to the component.
laravel-shift/blade-style for linting/formatting.pint to ensure formatted templates are also valid:
./vendor/bin/pint && ./vendor/bin/phpstan analyze --error-format=blade
Laravel Packages:
Bladestan automatically analyzes Blade templates in published package views (e.g., vendor/package-name/resources/views).
Ensure your package’s composer.json includes:
"extra": {
"laravel": {
"views": "resources/views"
}
}
Dynamic Blade Includes:
For @include directives with dynamic paths (e.g., @include($dynamicView)), Bladestan may not resolve the template. Workaround:
Mailables: Bladestan supports Laravel Mailables. To analyze a mail template:
// app/Mail/OrderShipped.php
public function build()
{
return $this->view('emails.orders.shipped');
}
Run Bladestan to catch undefined variables in resources/views/emails/orders/shipped.blade.php.
False Positives with Dynamic Data:
$user->data->method() where data is an array).@php directives to cast data or add type hints in your controllers:
// Controller
public function show(User $user)
{
return view('user.profile', [
'userData' => $user->data instanceof Arrayable ? $user->data->toArray() : [],
]);
}
Caching Issues:
vendor/bin/phpstan analyze --error-format=blade --no-cache
Livewire Component Namespaces:
config/livewire.php has the correct component_namespace:
'component_namespace' => 'App\\Livewire',
Non-HTML Templates:
.txt or .md mail templates).@php to validate data:
@php
if (!method_exists($order, 'getTotal')) {
throw new \RuntimeException('Order::getTotal() is missing!');
}
@endphp
Verbose Output:
Run Bladestan with --verbose to debug parsing issues:
vendor/bin/phpstan analyze --error-format=blade --verbose
Isolating Template Issues: Narrow down problems by analyzing a single file:
vendor/bin/phpstan analyze --error-format=blade resources/views/post_codex.blade.php
Custom Error Formatter Quirks:
--error-format=blade flag may not work in all PHPStan versions. Fallback:
vendor/bin/phpstan analyze | grep -A 2 -B 2 "rendered in:"
Custom Rules: Extend Bladestan by creating a custom PHPStan rule for Blade-specific logic:
// app/Rules/CustomBladeRule.php
use PHPStan\Rules\Rule;
use Bladestan\BladeNode;
class CustomBladeRule implements Rule
{
public function getNodeTypeNames(): array
{
return [BladeNode::class];
}
public function processNode(Node $node): array
{
// Add custom logic here
return [];
}
}
Register it in phpstan.neon:
services:
- Bladestan\BladeNode
- App\Rules\CustomBladeRule
Ignoring Specific Errors:
Suppress false positives by adding ignores to phpstan.neon:
parameters:
blade:
ignoredUndefinedMethods:
- App\Models\Post::legacyMethod
ignoredUndefinedVariables:
- $someDynamicVar
Post-Processing Errors: Use PHPStan’s error formatter API to customize Bladestan’s output. Example:
// app/Formatters/CustomBladeFormatter.php
use PHPStan\Error\Error;
use PHPStan\Output\ErrorFormatter;
class CustomBladeFormatter implements ErrorFormatter
{
public
How can I help you explore Laravel packages today?