- Knowledge
- technology
- OOP
- Tips
- Programming
- Tips
- Tutorial
- SEO
- Ranking
- Knowledge
- Special Day
- Seo
- Bug
- Data science
- Seo
- artificial intelligence
- Machine Learning
- Robotics
- happyNewYear2021
- newYearEve
- 2021
- Automation
- Smart Home
- Career
- Best Practices
- Git
- Logging
- Web Fundamentals
- DNS
- HTTPS
- Performance
- AI Tools
- ChatGPT
- Claude
- Gemini
- Laravel
- Eloquent
- MySQL
- HTTPS
- TLS
- Web Security
- Certificates
- Developer Life
- Debugging
- Docker
- DevOps
- Transactions
- Queues
- LLMs
- AI
- AI Coding
- Developer Tools
- React Native
- Expo
- Kate PMS
- Mobile Apps
- Laravel
- Authentication
- Sanctum
- Cookies
- API Design
- Payments
- Idempotency
- DeepSeek
- Open Source AI
- LLMs
- AI News
- Git
- Version Control
- AI Coding
- Prompting
- PHP
- Checklist
- MCP
- AI Agents
- OpenAI
- Architecture
- Microservices
- Modular Monolith
- Estimation
- Developer Life
- Project Planning
- Humour
- OAuth
- OpenID Connect
- Authentication
- Embeddings
- Vector Search
- RAG
- pgvector
- OpenAI
- GPT-4.1
- Codex CLI
- Events
- Testing
- Clean Code
- Maintainability
- Code Review
- Webhooks
- API
- Security
- Claude Code
- Workflow
- AI
- LLM
- Prompt Injection
- Mobile
- React
- Networking
- TCP
- UDP
- HTTP/3
- CLAUDE.md
- AWS
- Cloud Security
- Backups
- PHPUnit
- Software Engineering
- Leadership
- Communication
- RAG
- Embeddings
- AI Engineering
- IT Infrastructure
- Networking
- Access Control
- CI/CD
- GitHub Actions
- Gemini CLI
- Claude Code
- JavaScript
- Async/Await
- Node.js
- Promises
- Security
- Cryptography
- Passwords
- MySQL
- Database
- Vibe Coding
- Software Quality
- DNS
- Code Reading
- Onboarding
- Productivity
- Background Jobs
- Developer Humour
- Estimates
- Dev Life
- JWT
- o3-mini
- DeepSeek R1
- Rate Limiting
- Kate PMS
- E-Signing
- Audit Trail
- REST
- GraphQL
- API Design
- Laravel 12
- Upgrade Guide
- Open Source
- Self-Hosting
- Task Scheduling
- Cron
- Secrets
- CORS
- PHP
- PHP-FPM
- OPcache
- GitHub Copilot
- Software Architecture
- Engineering
- TypeScript
- JavaScript
- Type Safety
- AI Security
- React Native
- Product Design
- AI Agents
- Kiro
- Queues
- Redis
- RabbitMQ
- AWS SQS
- Nginx
- Apache
- GPT-5
- gpt-oss
- Clean Code
- Architecture
- Naming
- Documentation
- Career
- ADR
- Teamwork
- Supply Chain
- Kate HRM
- HR Software
- Permissions
- System Design
- Pagination
- SSH
- Linux
- Big O
- Databases
- Laravel Boost
- MCP
- Developer Skills
- Validation
- Databases
- Indexes
- Code Quality
- Deployment
- Developer Humour
- Feature Flags
- Code Review
- Pull Requests
- Docker
- Cursor
- Authorization
- RBAC
- Gemini
- Long Context
- PHP 8.4
- Caching
- Dependency Injection
- Web Performance
- Browser
- CSS
- Database
- Migrations
- ChatGPT
- AI for Developers
- Monitoring
- On-Call
- REST
- Backend
- SQL
- NoSQL
- Database Design
- Coding Agents
- Claude 4
- API Resources
- REST API
- Load Balancing
- Scaling
- AWS
- AI Tools
- Claude
- Sora 2
- CTE
- 2FA
- TOTP
- Programming Languages
- Prompts
- Developer Workflow
- API Gateway
- APIs
- Passport
- API Auth
- Learning
- Burnout
- Developer Growth
- Web Development
- SEO
- Kate Mall
- ChatGPT Atlas
- Agent Skills
- Middleware
- Laravel 12
- Collections
- Context Window
- Monitoring
- Commit Messages
- Self Review
- Growth
- Regex
- Programming Basics
- Text Processing
- Database Design
- Normalization
- Linux
- Server Security
- Linux Foundation
- Open Standards
- Legacy Code
- Documentation
- AI Workflow
- File Uploads
- Test Data
- Hashing
- Performance
- Caching
- Enums
- Scope Creep
- Estimation
- Codex
- Gemini CLI
- Timezones
- Carbon
- Bugs
- PHP 8.5
- Gemini 3
- GPT-5.1
- Data Integrity
- Event Loop
- Async
- Opus 4.5
- AI Models
- React
- Forms
- Frontend
- Backups
- AI Images
- DALL-E
- Midjourney
- Race Conditions
- Concurrency
- Legacy Code
- Refactoring
- Senior Engineer
- Scope
- LLM
- CDN
- Web
- Sub-Agents
- Soft Deletes
- Audit Log
- Concurrency
- AI Learning
- NestJS
- AI Evals
- Policies
- SPF DKIM DMARC
- Unicode
- UTF-8
- Knowledge Graph
- Value Objects
- Technical Debt
- Feature Flags
- Laravel Pennant
- Deployment
- Copilot
- Composer
- Dependencies
- Artisan
- Automation
- AWS S3
- Object Storage
- Cloud
- Small Language Models
- Ollama
- Production
- Sessions
- HTTP
- Mentoring
- SQL
- Virtual Machines
- Web Development
- HTTP/2
- QUIC
- Web Performance
- AI Integration
- LLM API
- SOLID
- OOP
- Hosting
- Serverless
- Merge Conflicts
- Temperature
- AI Development
- Reverse Proxy
- Nginx
- Infrastructure
- Verification
- Passkeys
- WebAuthn
- Teams
- Communication
- Stakeholders
- Monorepo
- CI/CD
- Versioning
- JSON Schema
- Livewire
- Inertia
- Meetings
- Distributed Systems
- Privacy
- Full-Stack
- T-Shaped Skills
- Money
- Notifications
- Web Security
- HTTP Headers
- CSP
- Function Calling
- Load Testing
- k6
- Data Extraction
- Debugging
- WebSockets
- SSE
- Real-Time
- Laravel Reverb
- Infrastructure as Code
- Terraform
- Side Projects
- Laravel Pint
- OpenAPI
- Swagger
- UX
- Multimodal
- Jest
- Pair Programming
- APIs
- Rate Limiting
- Resilience
- Dev Humour
- Design Tokens
- JWT
- API Keys
- Sessions
- PHPStan
- Rector
- Incidents
- Reporting
- Dashboards
- Zero Trust
- IAM
- Search
- Laravel Scout
- Junior Developers
- Mentoring
- Images
- WebP
- AVIF
- Bug Reports
- Let's Encrypt
- Design Docs
- Software Design
- Observers
- Replication
- Accountability
- Data Structures
- Reliability
- LLM Memory
- Error Handling
- Payments
- Payment Gateway
- Webhooks
- PCI DSS
- Observability
- OpenTelemetry
- Personal Brand
- Writing
- Conventions
- Dates
- Scheduling
- Disaster Recovery
- Compression
- Brotli
- Deadlines
- Developer Habits
- State Machines
- Tech Roles
- UUID
- ULID
- Horizon
- Planning
- Engineering Culture
- Ownership
- Soft Skills
- Socialite
- Cost Control
- Collations
- Unicode
- Octane
- PostgreSQL
SOLID Principles With Practical PHP Examples (and When to Ignore Them)
About Post
Most of us learned SOLID with rectangles, squares and ducks. Then we went back to work, opened a 900-line controller, and had no idea which duck applied.
The opposite problem is just as real. Someone reads about SOLID on a Friday, and by Monday a simple "send invoice" feature has an interface, an abstract class, a factory, a strategy and a provider, all with exactly one implementation.
SOLID is useful. But it's a set of answers to specific pains, and it only makes sense when you can recognise the pain. So for each principle, here's the smell in real PHP, a small fix, and the point where I'd stop.
S: Single Responsibility
A class should have one reason to change. Not "do one thing" (that's too vague), but serve one kind of change request.
The smell
class InvoiceController extends Controller
{
public function store(Request $request)
{
// 1. validate input (changes when the form changes)
// 2. calculate totals and VAT (changes when finance rules change)
// 3. render the PDF (changes when the design changes)
// 4. email it to the tenant (changes when the wording changes)
}
}
Four different people can ask you to change this method for four different reasons. Every change risks breaking the other three, and none of it can be reused from a queued job or a console command.
The fix
public function store(StoreInvoiceRequest $request, CreateInvoice $createInvoice)
{
$invoice = $createInvoice->handle($request->validated());
$invoice->tenant->notify(new InvoiceIssued($invoice));
return new InvoiceResource($invoice);
}
Validation moves to a form request, the business logic to an action class, the email to a notification, and the PDF to whatever builds it. The controller just coordinates.
When to stop: a 30-line controller method that's only ever called from one place doesn't need splitting into five classes. Split when the reasons to change actually start colliding.
O: Open/Closed
Open for extension, closed for modification. Adding a new case shouldn't mean editing tested code that already works.
The smell
public function charge(Payment $payment): void
{
match ($payment->method) {
'card' => $this->chargeCard($payment),
'bank_transfer' => $this->recordTransfer($payment),
'cheque' => $this->recordCheque($payment),
// every new method means editing this class again
};
}
The fix
interface PaymentMethod
{
public function charge(Payment $payment): void;
}
final class PaymentProcessor
{
/** @param array<string, PaymentMethod> $methods */
public function __construct(private array $methods) {}
public function charge(Payment $payment): void
{
$this->methods[$payment->method]->charge($payment);
}
}
A new payment method is now a new class plus one line of registration in a service provider. The processor never changes.
When to stop: a match with three stable cases is perfectly fine and easier to read. Reach for this when the list keeps growing or each branch is getting long.
L: Liskov Substitution
A subclass must be usable anywhere its parent is, without surprises. This is the one people skip, and it causes the strangest bugs.
The smell
class Notifier
{
public function send(User $user, string $message): void { /* email */ }
}
class SmsNotifier extends Notifier
{
public function send(User $user, string $message): void
{
if (! $user->phone) {
throw new LogicException('User has no phone number');
}
// send SMS
}
}
Code written against Notifier never expected an exception for a missing phone. Swap in SmsNotifier and a loop over all users dies halfway through.
Here's the subtle part: PHP checks that the signatures are compatible, but it can't check the promises. A subclass that throws where the parent didn't, ignores an argument, or returns a "special" null breaks Liskov while compiling perfectly.
The fix
Make the contract honest. Either the interface says up front that some users can't be reached (for example a canReach(User $user): bool method callers check first), or SmsNotifier handles the case itself by skipping and logging. Either way, the caller never has to know which implementation it got.
I: Interface Segregation
Don't force classes to implement methods they don't need.
The smell
interface Exportable
{
public function toCsv(): string;
public function toPdf(): string;
public function toExcel(): string;
}
class AuditLog implements Exportable
{
public function toPdf(): string
{
throw new BadMethodCallException('Not supported');
}
// ...
}
A method that throws "not supported" is a sign the interface is too big. It also breaks Liskov, which is why these two principles often appear together.
The fix
Split it: ExportsCsv, ExportsPdf, ExportsExcel. A class implements what it really supports, and code that only needs CSV asks only for ExportsCsv.
Laravel does this well. ShouldQueue, ShouldBeUnique and ShouldBroadcast are tiny interfaces you opt into one at a time, not one giant Job contract.
D: Dependency Inversion
Depend on abstractions, not on concrete details. High-level business code shouldn't know which SMS provider or storage service you picked this year.
The smell
class SendRentReminder
{
public function handle(Tenant $tenant): void
{
$client = new AcmeSmsClient(config('services.acme.key'));
$client->send($tenant->phone, 'Your rent is due soon.');
}
}
You can't test this without sending a real SMS, and changing provider means editing business logic.
The fix
class SendRentReminder
{
public function __construct(private SmsSender $sms) {}
public function handle(Tenant $tenant): void
{
$this->sms->send($tenant->phone, 'Your rent is due soon.');
}
}
// In a service provider:
$this->app->bind(SmsSender::class, AcmeSmsSender::class);
Laravel's container injects the right implementation, and in tests you bind a fake that records messages instead of sending them.
When to stop: you don't need an interface in front of Eloquent, the request object or every internal class. Put abstractions at the boundaries that really vary or are painful to test: payment gateways, SMS, storage, external APIs, AI providers.
My rule of thumb: apply a SOLID principle when you can name the pain it removes today. "We might need it one day" is how simple apps end up with 40 files per feature.
The cheat sheet
| Principle | The smell | Typical fix in Laravel |
|---|---|---|
| Single Responsibility | One class changes for many unrelated reasons | Form requests, action classes, notifications |
| Open/Closed | A match or if chain you edit for every new case | Interface plus registered implementations |
| Liskov Substitution | A subclass that throws or behaves "specially" | Honest contracts, no surprise exceptions |
| Interface Segregation | Methods that throw "not supported" | Small, opt-in interfaces |
| Dependency Inversion | new SomeClient() inside business logic | Constructor injection and container bindings |
SOLID isn't a checklist to pass. It's a vocabulary for explaining why some code hurts to change. Use it to name the pain, fix that, and stop there.
Which principle do you see broken most often in real codebases? My vote goes to the single-responsibility controller that also sends emails.

Be first to comment it...