Bind vs Singleton vs Instance

Learn the differences between bind(), singleton(), and instance() methods and when to use each

The Service Container offers different methods for registering services, each controlling the lifecycle of objects differently.

bind() - Creates a NEW instance every time
• Use when you need a fresh object for each request
• Example: Request validators, temporary calculators

singleton() - Creates ONE instance for the entire request
• The same instance is reused throughout the request lifecycle
• Use for services that maintain state or are expensive to create
• Example: Database connections, cache managers, configuration loaders

instance() - Binds an EXISTING object instance
• You provide an already-created object
• Useful when you need to configure an object before binding it
• Example: Pre-configured API clients

Real-World Example:
In a reporting system, you might use bind() for report generators (each report is independent), singleton() for the database connection (reuse the same connection), and instance() for a pre-configured PDF renderer.

Code Examples

bind() - New Instance Every Time
✓ Best Practice php
<?php
// In AppServiceProvider
public function register(): void
{
    // Each time ReportGenerator is requested, create a new instance
    $this->app->bind(ReportGenerator::class, function ($app) {
        return new ReportGenerator(
            $app->make(DatabaseConnection::class),
            $app->make(PdfRenderer::class)
        );
    });
}

// Usage in controller:
class ReportController extends Controller
{
    public function salesReport(ReportGenerator $generator)
    {
        return $generator->generate('sales'); // Fresh instance #1
    }
    
    public function inventoryReport(ReportGenerator $generator)
    {
        return $generator->generate('inventory'); // Fresh instance #2
    }
}

// Each method gets a DIFFERENT instance of ReportGenerator
// Memory: Higher (multiple instances)
// Use Case: When each usage should be independent

💡 bind() creates a new instance every time it's resolved. If you resolve ReportGenerator 10 times in a request, you get 10 different objects. Good for stateless services or when you need isolation.

singleton() - One Instance Per Request
✓ Best Practice php
<?php
// In AppServiceProvider
public function register(): void
{
    // Create only ONE instance per request lifecycle
    $this->app->singleton(CacheManager::class, function ($app) {
        return new CacheManager(
            config('cache.driver'),
            config('cache.prefix')
        );
    });
}

// Usage in controller:
class ProductController extends Controller
{
    public function index(CacheManager $cache)
    {
        $products = $cache->remember('products', fn() => Product::all());
        // $cache is instance #1
    }
    
    public function show($id, CacheManager $cache)
    {
        $product = $cache->remember("product.{$id}", fn() => Product::find($id));
        // $cache is THE SAME instance #1 (singleton!)
    }
}

// Both methods share the SAME CacheManager instance
// Memory: Lower (single instance)
// Performance: Better (no repeated initialization)
// Use Case: Database connections, cache, config loaders

💡 singleton() creates the instance once and reuses it for all subsequent resolutions within the same request. Perfect for expensive-to-create objects or services that maintain state.

instance() - Bind Existing Object
💡 Solution php
<?php
// In AppServiceProvider
public function register(): void
{
    // Create and configure an object BEFORE binding it
    $s3Client = new S3Client([
        'credentials' => [
            'key'    => config('aws.key'),
            'secret' => config('aws.secret'),
        ],
        'region' => config('aws.region'),
        'version' => 'latest',
    ]);
    
    // Additional configuration
    $s3Client->setTimeout(30);
    $s3Client->setRetries(3);
    
    // Bind the already-created, pre-configured instance
    $this->app->instance(S3Client::class, $s3Client);
}

// Now anywhere in your app:
class FileUploadController extends Controller
{
    public function upload(Request $request, S3Client $s3)
    {
        // $s3 is the exact same pre-configured instance
        // from the service provider
        $s3->putObject([
            'Bucket' => 'my-bucket',
            'Key'    => $request->file('image')->getClientOriginalName(),
            'Body'   => $request->file('image')->get(),
        ]);
    }
}

💡 instance() lets you create and configure an object yourself, then bind it to the container. Useful when you need complex configuration that's easier to do procedurally.

Comparison: All Three Methods
💡 Solution php
<?php
// SCENARIO: Analytics Tracker

// 1. bind() - New tracker for each event (isolated)
$this->app->bind(AnalyticsTracker::class, function ($app) {
    return new AnalyticsTracker();
});
// Result: Track event A, track event B = 2 different trackers
// Use when: Events should be independent

// 2. singleton() - One tracker for entire request (shared state)
$this->app->singleton(AnalyticsTracker::class, function ($app) {
    return new AnalyticsTracker();
});
// Result: Track event A, track event B = same tracker (can batch events!)
// Use when: You want to collect all events and send them together

// 3. instance() - Pre-configured tracker
$tracker = new AnalyticsTracker();
$tracker->setApiKey(config('analytics.api_key'));
$tracker->setEndpoint(config('analytics.endpoint'));
$tracker->enableBatching(true);
$this->app->instance(AnalyticsTracker::class, $tracker);
// Result: Everyone gets this exact configured tracker
// Use when: Complex setup needed before binding

// Memory comparison (100 resolutions):
// bind()      → 100 objects created → ~10MB
// singleton() → 1 object created   → ~0.1MB
// instance()  → 1 object created   → ~0.1MB

💡 Quick decision guide: Use bind() for stateless services, singleton() for expensive or stateful services, and instance() when you need to pre-configure an object before binding.