We have all been there. The feature works perfectly on your local machine. You push it, merge it, and deployment triggers. Ten minutes later, production crashes because of a Call to undefined method on a null object.
You are likely already using Laravel Pint for code style, which is great. However, Pint only checks how your code looks, not how it works. It is like spell-checking a book that has huge plot holes.
To build a truly stable application, especially for API stability, you need a "Swiss Cheese" model of defense. This involves multiple layers of static analysis, architectural enforcement, security scanning, and strict AI guidelines.
Here is how to set up a modern quality pipeline that catches bugs before they exist.
1. Static Analysis: Logic Checks Without Execution
The Problem: You rely on manual testing to find type errors.
The Solution: Tools that "read" your code and understand the flow of data without actually running the app.
Larastan (PHPStan for Laravel)
Larastan is the most impactful tool you can add. It understands Laravel's "magic" (facades, eloquence) and finds type errors, undefined variables, and incorrect method signatures.
Implementation:
composer require --dev larastan/larastan
Create a phpstan.neon file in your root. Start at Level 5. It is strict enough to be useful but forgiving enough for legacy codebases.
includes:
- vendor/larastan/larastan/extension.neon
parameters:
paths:
- app/
level: 5
Pro Tip: Do not try to fix 500 errors at once. Use a "baseline" file. This tells Larastan to ignore existing errors but report any new errors created from today onwards.
Rector
While Larastan finds bugs, Rector fixes bad habits. It instantly upgrades your code to newer PHP versions and Laravel best practices.
Implementation:
composer require --dev rector/rector
Run npx rector init. It can automatically turn old array syntax array() into [] or replace manual property assignments with PHP 8.2 readonly classes.
2. Enforce Architecture with Tests
The Problem: Spaghetti code. A junior dev puts complex tax calculation logic inside a Controller instead of a Service class.
The Solution: Pest Architecture Tests.
If your project follows a Service-Repository pattern, you should not just hope people follow it. You should enforce it. Pest allows you to write tests for your file structure.
Implementation:
First, ensure you are using Pest, then create tests/ArchTest.php:
<?php
test('controllers should not access models directly')
->expect('App\Http\Controllers')
->not->toUse('App\Models') // Force use of Services/Repositories
->ignoring('App\Http\Controllers\Auth');
test('controllers should not use inline validation')
->expect('App\Http\Controllers')
->not->toUse('Illuminate\Support\Facades\Validator');
test('globals are strictly prohibited')
->expect(['dd', 'dump', 'env'])
->not->toBeUsed();
Why this matters: This prevents "architecture drift." If someone tries to shortcut the Repository pattern, the build fails immediately.
3. Git Hooks: The First Line of Defense
The Problem: CI/CD takes 10 minutes to run. If you forgot a semicolon, you waste 10 minutes waiting for the build to fail.
The Solution: Husky + Lint Staged.
We want to run checks only on the files you just changed, right before you commit.
Implementation:
Since Laravel projects usually have a package.json for frontend assets, we can use Node tools to manage git hooks.
-
Install Husky and Lint Staged:
npm install husky lint-staged --save-dev npx husky init -
Configure
package.json:Add a configuration that tells the system to run Pint and Larastan on PHP files when they are staged for commit.
"lint-staged": { "**/*.php": [ "./vendor/bin/pint", "./vendor/bin/phpstan analyse --memory-limit=2G" ] } -
Update the pre-commit hook:
In
.husky/pre-commit, add:
npx lint-staged
Now, if you try to commit messy code, the commit is blocked. You fix it, and try again.
4. Security & Maintenance
The Problem: Your code is clean, but you are using a library with a known hack, or your server config is insecure.
The Solution: Enlightn & Composer Audit.
Enlightn
Enlightn is like a consultant in a box. It checks 120+ items covering performance, security, and reliability. For example, it checks if you are using env() outside of config files.
composer require --dev enlightn/enlightn
php artisan enlightn
Composer Audit
This is built into Composer. It checks your composer.lock against a database of known security vulnerabilities.
Add this to your CI pipeline:
- name: Check for security vulnerabilities
run: composer audit
5. The Brain: Strict AI Instructions
The Problem: AI assistants often write "Junior" level code, using anti-patterns like inline validation or direct model queries.
The Solution: Custom Instructions.
To fix this, create a .github/copilot-instructions.md file in your repository. This forces the AI to adopt the persona of a Senior Architect and follow strict rules.
Copy and paste these exact rules into that file:
Laravel Copilot Instructions
You are a Senior Laravel Architect. You prioritize clean architecture, SOLID principles, and maintainability. You strictly adhere to the Service-Repository pattern and "Fat Model, Skinny Controller" philosophy.
## π« STRICT PROHIBITIONS (Anti-Patterns)
### 1. No Inline Class Paths (FQCN)
**NEVER** use Fully Qualified Class Names inline within code blocks. Always import classes at the top of the file with `use`.
* β **Bad:** `public function index(\Illuminate\Http\Request $request)`
* β
**Good:**
php
use Illuminate\Http\Request;
// ...
public function index(Request $request)
### 2. No Direct Model Queries in Controllers
**NEVER** write Eloquent queries directly inside a Controller.
* β **Bad:** `$users = User::where('active', 1)->get();`
* β
**Good:** `$users = $this->userService->getActiveUsers();` or `$users = $this->userRepository->getAllActive();`
### 3. No Inline Validation
**NEVER** use `$request->validate([...])` inside a Controller method.
* β **Bad:** `$request->validate(['email' => 'required']);`
* β
**Good:** Create and type-hint a Form Request: `public function store(StoreUserRequest $request)`
### 4. No Raw JSON Responses
**NEVER** return raw Models, Arrays, or `response()->json()` for API data.
* β **Bad:** `return response()->json($user);`
* β
**Good:** Always use API Resources: `return new UserResource($user);`
### 5. No Private Controller Methods (Business Logic)
**AVOID** creating private helper methods inside Controllers to handle business logic. If a Controller method is getting too complex, the logic belongs in a Service, Action, or Job.
* β **Bad:** `private function calculateTax($order) { ... }` inside a Controller.
* β
**Good:** `$this->taxCalculator->calculate($order);` (Logic moved to a dedicated class).
### 6. No Logic in Blade/Views
**NEVER** place queries or complex PHP logic in Blade files.
* β **Bad:** `@foreach(User::all() as $user)`
* β
**Good:** Pass data from the Controller or ViewComposer.
---
## ποΈ ARCHITECTURE & CODING STANDARDS
### Service-Repository Pattern
* Controllers must **only** handle:
1. Receiving the Request (via FormRequest).
2. Delegating to a Service/Repository.
3. Returning a Resource/Response.
* All business logic lives in **Services**.
* All database logic lives in **Repositories** (or Scopes if lightweight).
### Type Safety & Modern PHP
* **Always** use strict types: `declare(strict_types=1);` at the top of every file.
* **Always** add return types to methods: `public function index(): AnonymousResourceCollection`.
* **Always** type-hint arguments.
### Code Quality Checklist
Before suggesting code, verify:
1. [ ] Are all classes imported with `use`? (No inline FQCN)
2. [ ] Does this belong in a Service?
3. [ ] Is validation handled in a FormRequest?
4. [ ] Is the response wrapped in a Resource?
5. [ ] Are magic strings/numbers replaced with Constants or Enums?
Conclusion
By implementing this pipeline, you shift from "hoping" code is good to guaranteeing it is.
- Copilot writes better code because of your instructions.
- Pint fixes the style.
- Larastan catches logic errors.
- Husky blocks bad commits.
- Pest enforces the architecture.
This is how you scale a codebase without scaling the technical debt.
Top comments (0)