- 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
Laravel's Service Container Explained: Who Calls new on Your Classes?
About Post
Here's a question that sounds silly until you try to answer it: who calls new on your controllers?
You write public function __construct(private InvoiceService $invoices), you never create an InvoiceService anywhere, and yet it shows up, fully built, with its own dependencies already inside it. It feels like magic. It isn't. It's the service container, and once you see how it works, a lot of Laravel stops feeling mysterious and starts feeling like a tool you control.
The problem it solves
Start with the version most of us wrote early on:
class InvoiceService
{
public function charge(Invoice $invoice): Payment
{
$gateway = new StripeGateway(config('services.stripe.secret'));
return $gateway->charge($invoice->amount, $invoice->currency);
}
}
It works. It's also welded shut. You can't test charge() without hitting a real payment API, you can't switch gateways without editing this class, and every class that needs a gateway repeats the same new plus config lookup.
Dependency injection is the boring fix: don't build what you need, ask for it.
class InvoiceService
{
public function __construct(private PaymentGateway $gateway) {}
public function charge(Invoice $invoice): Payment
{
return $this->gateway->charge($invoice->amount, $invoice->currency);
}
}
Now InvoiceService doesn't know or care which gateway it gets. Somebody else decides. In Laravel, that somebody is the container.
Auto-wiring: the part that feels like magic
When Laravel needs an object (a controller, a job, a listener, a form request), it asks the container to resolve it. For a concrete class, the container uses PHP's Reflection API to read the constructor, looks at each type hint, resolves those classes the same way, and keeps going down the tree until everything is built.
That's why this works with zero configuration:
class ContractPdfService
{
public function __construct(
private PdfRenderer $renderer,
private ContractRepository $contracts,
) {}
}
$service = app(ContractPdfService::class); // fully built, dependencies included
Auto-wiring has two limits worth knowing:
- Interfaces. The container can't guess which class implements
PaymentGateway. You'll seeTarget [App\Contracts\PaymentGateway] is not instantiable. - Scalars. A constructor that wants
string $apiKeycan't be auto-wired either. There's no class to reflect on.
Both are solved by telling the container what you want. That's a binding.
Bindings: teaching the container
Bindings live in the register() method of a service provider, usually AppServiceProvider:
public function register(): void
{
$this->app->bind(PaymentGateway::class, function ($app) {
return new StripeGateway(config('services.stripe.secret'));
});
}
Now any class in your app that type-hints PaymentGateway gets a StripeGateway. Switching providers later means changing this one closure, not hunting through the codebase. If the implementation can be auto-wired itself, you can skip the closure: $this->app->bind(PaymentGateway::class, StripeGateway::class).
The scalar problem has a neat answer since Laravel 11: contextual attributes. Instead of a binding, put the attribute on the parameter.
use Illuminate\Container\Attributes\Config;
class StripeGateway implements PaymentGateway
{
public function __construct(
#[Config('services.stripe.secret')] private string $secret,
) {}
}
bind, singleton or scoped?
This is where real bugs come from, so it's worth a table.
| Method | What you get | Good for |
|---|---|---|
bind | A new instance every time it's resolved | Stateful or cheap objects |
singleton | One instance for the life of the app process | Expensive, stateless clients (an HTTP client, an SDK) |
scoped | One instance per request or queued job, then reset | Per-request state (current tenant, request context) |
The gotcha: "the life of the app process" means something different in different places. In classic PHP-FPM, the process forgets everything after each request, so a singleton that holds state looks harmless. Run the same code in a long-lived queue worker or under Laravel Octane, and that singleton survives between jobs or requests. If it caches "the current user" or "the current tenant", the next job gets the previous one's data.
Rule of thumb: if the object holds anything about who or what is being processed right now, it's scoped or bind, never singleton.
The real payoff: testing
Because your code asks the container for a PaymentGateway instead of building one, a test can put something else in the container first. Laravel's mock() helper does exactly that:
use Mockery\MockInterface;
public function test_paying_an_invoice_records_a_payment(): void
{
$this->mock(PaymentGateway::class, function (MockInterface $mock) {
$mock->shouldReceive('charge')->once()->andReturn(new ChargeResult('ch_test', 'succeeded'));
});
$invoice = Invoice::factory()->create();
$this->actingAs($invoice->tenant->user)
->postJson("/api/invoices/{$invoice->id}/pay")
->assertOk();
$this->assertDatabaseHas('payments', ['invoice_id' => $invoice->id]);
}
No network, no test keys, no flaky third party. The controller, the service and the database code are all real. Only the edge of the system is swapped. If you prefer a hand-written fake over Mockery, $this->instance(PaymentGateway::class, new FakeGateway) does the same job.
The test of good design: if swapping a dependency in a test needs anything more than one line of container setup, the class is probably building its own dependencies somewhere. Find the new.
Mistakes I see with the container
- Calling
app()everywhere. Pulling dependencies out of the container inside methods (app(PaymentGateway::class)->charge(...)) works, but it hides what a class needs. Constructor injection makes dependencies visible at a glance. Keepapp()for the rare places injection can't reach. - Resolving things in
register(). Insideregister(), other providers may not have registered their bindings yet. Bind inregister(), use services inboot(). - Interfaces for everything. You don't need an interface for every class. Add one where there's a real seam: external services, payment gateways, SMS providers, storage. A concrete class can still be mocked when you need to.
- Stateful singletons. Covered above, and worth repeating, because it's the one that only fails in production workers.
If you remember one thing
The container is a factory that reads your constructors. Ask for what you need in the constructor, bind interfaces where you want a choice, pick scoped over singleton when state is involved, and your tests get easy almost by accident.
What's the strangest container bug you've chased? Mine always seem to involve a singleton that remembered a little too much.

Be first to comment it...