Laravel Caching: Boosting Performance with Redis and Memcached
Speed up your application with intelligent caching strategies.
The Service Container is Laravel's dependency injection container and the core of the framework. It's a powerful tool for managing class dependencies and performing dependency injection.
Think of it as:
Without Service Container:
class PaymentController {
public function process()
{
$paymentGateway = new StripePaymentGateway(config('services.stripe.secret'));
$emailService = new EmailService(config('mail'));
$logger = new FileLogger(storage_path('logs/payments.log'));
$paymentProcessor = new PaymentProcessor($paymentGateway, $emailService, $logger);
$paymentProcessor->process();
}
}
With Service Container:
class PaymentController {
public function process(PaymentProcessor $paymentProcessor)
{
$paymentProcessor->process(); // Dependencies automatically injected!
}
}
Simple Bindings
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Services\PaymentGateway;
use App\Services\StripePaymentGateway;
use App\Services\EmailService;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Bind interface to implementation
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
// Bind with closure
$this->app->bind(EmailService::class, function ($app) {
return new EmailService(config('mail'));
});
// Bind singleton (same instance always)
$this->app->singleton(Logger::class, function ($app) {
return new FileLogger(storage_path('logs/app.log'));
});
// Bind instance (specific instance)
$logger = new FileLogger(storage_path('logs/app.log'));
$this->app->instance(Logger::class, $logger);
}
}
public function register(): void
{
// Conditional binding
if ($this->app->environment('production')) {
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
} else {
$this->app->bind(PaymentGateway::class, TestPaymentGateway::class);
}
// Binding with dependencies
$this->app->bind(OrderProcessor::class, function ($app) {
return new OrderProcessor(
$app->make(PaymentGateway::class),
$app->make(InventoryService::class),
$app->make(ShippingService::class)
);
});
// Tagged bindings
$this->app->tag([StripeGateway::class, PayPalGateway::class], 'payment-gateways');
// Contextual binding
$this->app->when(ReportGenerator::class)
->needs(Exporter::class)
->give(PdfExporter::class);
}
Different Resolution Methods
<?php
// app/Http/Controllers/PaymentController.php
namespace App\Http\Controllers;
use App\Services\PaymentGateway;
use Illuminate\Support\Facades\App;
class PaymentController extends Controller
{
// Method 1: Dependency Injection (Recommended)
public function process(PaymentGateway $paymentGateway)
{
$paymentGateway->charge(1000);
}
// Method 2: Using app() helper
public function manualResolution()
{
$paymentGateway = app(PaymentGateway::class);
$paymentGateway->charge(1000);
}
// Method 3: Using App facade
public function facadeResolution()
{
$paymentGateway = App::make(PaymentGateway::class);
$paymentGateway->charge(1000);
}
// Method 4: Method injection with parameters
public function checkout(PaymentGateway $gateway, $amount)
{
return $gateway->charge($amount);
}
}
<?php
// app/Contracts/PaymentGateway.php
namespace App\Contracts;
interface PaymentGateway
{
public function charge($amount, $token);
public function refund($chargeId);
public function createCustomer($email);
}
// app/Services/StripePaymentGateway.php
namespace App\Services;
use App\Contracts\PaymentGateway;
use Stripe\Stripe;
use Stripe\Charge;
use Stripe\Customer;
class StripePaymentGateway implements PaymentGateway
{
protected $secretKey;
public function __construct($secretKey)
{
$this->secretKey = $secretKey;
Stripe::setApiKey($secretKey);
}
public function charge($amount, $token)
{
try {
$charge = Charge::create([
'amount' => $amount,
'currency' => 'usd',
'source' => $token,
'description' => 'Payment from Laravel App',
]);
return $charge->id;
} catch (\Exception $e) {
throw new \Exception("Payment failed: " . $e->getMessage());
}
}
public function refund($chargeId)
{
// Refund logic
}
public function createCustomer($email)
{
// Customer creation logic
}
}
<?php
// app/Services/EmailService.php
namespace App\Services;
use Illuminate\Contracts\Mail\Mailer;
class EmailService
{
protected $mailer;
protected $fromAddress;
public function __construct(Mailer $mailer, $fromAddress)
{
$this->mailer = $mailer;
$this->fromAddress = $fromAddress;
}
public function sendWelcomeEmail($user)
{
$this->mailer->send('emails.welcome', ['user' => $user], function ($message) use ($user) {
$message->to($user->email)
->from($this->fromAddress)
->subject('Welcome to Our Application!');
});
}
public function sendNotification($to, $subject, $template, $data = [])
{
$this->mailer->send($template, $data, function ($message) use ($to, $subject) {
$message->to($to)
->from($this->fromAddress)
->subject($subject);
});
}
}
<?php
// app/Providers/AppServiceProvider.php
namespace App\Providers;
use App\Contracts\PaymentGateway;
use App\Services\StripePaymentGateway;
use App\Services\EmailService;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
public function register(): void
{
// Payment Gateway binding
$this->app->bind(PaymentGateway::class, function ($app) {
return new StripePaymentGateway(
config('services.stripe.secret')
);
});
// Email Service binding
$this->app->singleton(EmailService::class, function ($app) {
return new EmailService(
$app->make('mailer'),
config('mail.from.address')
);
});
// Multiple payment gateways with tags
$this->app->bind('stripe.gateway', function ($app) {
return new StripePaymentGateway(config('services.stripe.secret'));
});
$this->app->bind('paypal.gateway', function ($app) {
return new PayPalPaymentGateway(config('services.paypal'));
});
$this->app->tag(['stripe.gateway', 'paypal.gateway'], 'payment-gateways');
}
public function boot(): void
{
// Boot logic here
}
}
<?php
// app/Services/OrderProcessor.php
namespace App\Services;
use App\Contracts\PaymentGateway;
use App\Services\EmailService;
use App\Services\InventoryService;
class OrderProcessor
{
protected $paymentGateway;
protected $emailService;
protected $inventoryService;
public function __construct(
PaymentGateway $paymentGateway,
EmailService $emailService,
InventoryService $inventoryService
) {
$this->paymentGateway = $paymentGateway;
$this->emailService = $emailService;
$this->inventoryService = $inventoryService;
}
public function processOrder($order, $paymentToken)
{
// Process payment
$chargeId = $this->paymentGateway->charge($order->total, $paymentToken);
// Update inventory
$this->inventoryService->updateStock($order->items);
// Send confirmation email
$this->emailService->sendOrderConfirmation($order->user, $order);
return $chargeId;
}
}
<?php
// app/Http/Controllers/OrderController.php
namespace App\Http\Controllers;
use App\Services\OrderProcessor;
use Illuminate\Http\Request;
class OrderController extends Controller
{
public function store(Request $request, OrderProcessor $orderProcessor)
{
$order = $this->createOrder($request);
try {
$chargeId = $orderProcessor->processOrder($order, $request->payment_token);
return response()->json([
'success' => true,
'order_id' => $order->id,
'charge_id' => $chargeId
]);
} catch (\Exception $e) {
return response()->json([
'success' => false,
'error' => $e->getMessage()
], 422);
}
}
}
<?php
// app/Providers/AppServiceProvider.php
public function register(): void
{
// Environment-based binding
if (config('payment.default') === 'stripe') {
$this->app->bind(PaymentGateway::class, StripePaymentGateway::class);
} else {
$this->app->bind(PaymentGateway::class, PayPalPaymentGateway::class);
}
// Feature flag binding
$this->app->bind(NotificationService::class, function ($app) {
if (feature('new-notification-system')) {
return new NewNotificationService();
}
return new LegacyNotificationService();
});
}
<?php
// app/Providers/AppServiceProvider.php
public function register(): void
{
// Different implementations for different classes
$this->app->when(OrderController::class)
->needs(ReportGenerator::class)
->give(OrderReportGenerator::class);
$this->app->when(UserController::class)
->needs(ReportGenerator::class)
->give(UserReportGenerator::class);
// Contextual binding with parameters
$this->app->when(ExportService::class)
->needs('$format')
->give('csv');
$this->app->when(AnalyticsService::class)
->needs('$timezone')
->giveConfig('app.timezone');
}
<?php
// app/Providers/AppServiceProvider.php
public function register(): void
{
$this->app->bind(Service::class, BaseService::class);
// Extend the binding to add functionality
$this->app->extend(Service::class, function ($service, $app) {
return new DecoratedService($service);
});
// Multiple extensions
$this->app->extend(Service::class, function ($service, $app) {
return new LoggedService($service, $app->make(Logger::class));
});
}
<?php
// app/Providers/ReportServiceProvider.php
namespace App\Providers;
use Illuminate\Contracts\Support\DeferrableProvider;
use Illuminate\Support\ServiceProvider;
class ReportServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function register(): void
{
$this->app->singleton(ReportGenerator::class, function ($app) {
return new ReportGenerator($app->make('cache'));
});
}
public function provides(): array
{
return [ReportGenerator::class];
}
}
<?php
// app/Services/CartService.php
namespace App\Services;
use App\Models\Product;
use Illuminate\Support\Collection;
class CartService
{
protected $session;
public function __construct(Session $session)
{
$this->session = $session;
}
public function addItem(Product $product, $quantity = 1)
{
$cart = $this->getCart();
if (isset($cart[$product->id])) {
$cart[$product->id]['quantity'] += $quantity;
} else {
$cart[$product->id] = [
'product_id' => $product->id,
'name' => $product->name,
'price' => $product->price,
'quantity' => $quantity,
];
}
$this->session->put('cart', $cart);
}
public function getCart(): Collection
{
return collect($this->session->get('cart', []));
}
public function getTotal(): float
{
return $this->getCart()->sum(function ($item) {
return $item['price'] * $item['quantity'];
});
}
public function clear()
{
$this->session->forget('cart');
}
}
// app/Services/DiscountService.php
namespace App\Services;
class DiscountService
{
protected $discounts = [];
public function addDiscount($discount)
{
$this->discounts[] = $discount;
}
public function calculateDiscounts($cartTotal)
{
$totalDiscount = 0;
foreach ($this->discounts as $discount) {
$totalDiscount += $discount->calculate($cartTotal);
}
return $totalDiscount;
}
}
// app/Services/OrderService.php
namespace App\Services;
use App\Models\Order;
use App\Contracts\PaymentGateway;
class OrderService
{
protected $paymentGateway;
protected $cartService;
protected $discountService;
protected $emailService;
public function __construct(
PaymentGateway $paymentGateway,
CartService $cartService,
DiscountService $discountService,
EmailService $emailService
) {
$this->paymentGateway = $paymentGateway;
$this->cartService = $cartService;
$this->discountService = $discountService;
$this->emailService = $emailService;
}
public function createOrder($user, $paymentToken)
{
$cart = $this->cartService->getCart();
$subtotal = $this->cartService->getTotal();
$discount = $this->discountService->calculateDiscounts($subtotal);
$total = $subtotal - $discount;
// Process payment
$chargeId = $this->paymentGateway->charge($total, $paymentToken);
// Create order
$order = Order::create([
'user_id' => $user->id,
'total' => $total,
'charge_id' => $chargeId,
'status' => 'completed',
]);
// Add order items
foreach ($cart as $item) {
$order->items()->create([
'product_id' => $item['product_id'],
'quantity' => $item['quantity'],
'price' => $item['price'],
]);
}
// Clear cart
$this->cartService->clear();
// Send confirmation
$this->emailService->sendOrderConfirmation($user, $order);
return $order;
}
}
<?php
// app/Providers/EcommerceServiceProvider.php
namespace App\Providers;
use App\Services\CartService;
use App\Services\DiscountService;
use App\Services\OrderService;
use Illuminate\Support\ServiceProvider;
class EcommerceServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(CartService::class, function ($app) {
return new CartService($app->make('session'));
});
$this->app->singleton(DiscountService::class, function ($app) {
$discountService = new DiscountService();
// Register default discounts
$discountService->addDiscount(new PercentageDiscount(10)); // 10% off
$discountService->addDiscount(new FixedDiscount(500)); // $5 off
return $discountService;
});
$this->app->bind(OrderService::class, function ($app) {
return new OrderService(
$app->make(PaymentGateway::class),
$app->make(CartService::class),
$app->make(DiscountService::class),
$app->make(EmailService::class)
);
});
}
public function boot(): void
{
// Boot logic if needed
}
}
<?php
// tests/Feature/OrderProcessingTest.php
namespace Tests\Feature;
use App\Contracts\PaymentGateway;
use App\Services\OrderService;
use Tests\TestCase;
use Illuminate\Foundation\Testing\RefreshDatabase;
class OrderProcessingTest extends TestCase
{
use RefreshDatabase;
public function test_order_processing_with_mock_payment_gateway()
{
// Mock the payment gateway
$paymentGateway = $this->mock(PaymentGateway::class);
$paymentGateway->shouldReceive('charge')
->once()
->with(1000, 'valid_token')
->andReturn('ch_123456');
// Swap the binding in container
$this->app->instance(PaymentGateway::class, $paymentGateway);
// Test order processing
$orderService = app(OrderService::class);
$user = \App\Models\User::factory()->create();
$order = $orderService->createOrder($user, 'valid_token');
$this->assertNotNull($order);
$this->assertEquals('ch_123456', $order->charge_id);
}
public function test_order_processing_with_real_dependencies()
{
// Use real implementations
$orderService = app(OrderService::class);
$user = \App\Models\User::factory()->create();
// Add items to cart
$cartService = app(CartService::class);
$product = \App\Models\Product::factory()->create(['price' => 1000]);
$cartService->addItem($product);
// Use test payment gateway
$this->app->bind(PaymentGateway::class, TestPaymentGateway::class);
$order = $orderService->createOrder($user, 'test_token');
$this->assertNotNull($order);
$this->assertEquals('completed', $order->status);
}
}
// tests/Unit/ContainerTest.php
class ContainerTest extends TestCase
{
public function test_container_binding_resolution()
{
// Test interface binding
$this->app->bind(TestInterface::class, TestImplementation::class);
$instance = app(TestInterface::class);
$this->assertInstanceOf(TestImplementation::class, $instance);
}
public function test_singleton_binding()
{
$this->app->singleton(SingletonService::class, function ($app) {
return new SingletonService();
});
$instance1 = app(SingletonService::class);
$instance2 = app(SingletonService::class);
$this->assertSame($instance1, $instance2);
}
public function test_contextual_binding()
{
$this->app->when(ConsumerA::class)
->needs(Dependency::class)
->give(ImplementationA::class);
$this->app->when(ConsumerB::class)
->needs(Dependency::class)
->give(ImplementationB::class);
$consumerA = app(ConsumerA::class);
$consumerB = app(ConsumerB::class);
$this->assertInstanceOf(ImplementationA::class, $consumerA->dependency);
$this->assertInstanceOf(ImplementationB::class, $consumerB->dependency);
}
}
<?php
// Use interfaces for loose coupling
interface CacheRepository
{
public function get($key);
public function put($key, $value, $minutes);
public function forget($key);
}
class RedisCache implements CacheRepository
{
public function get($key) { /* ... */ }
public function put($key, $value, $minutes) { /* ... */ }
public function forget($key) { /* ... */ }
}
class FileCache implements CacheRepository
{
public function get($key) { /* ... */ }
public function put($key, $value, $minutes) { /* ... */ }
public function forget($key) { /* ... */ }
}
// Binding
$this->app->bind(CacheRepository::class, RedisCache::class);
<?php
// Good: Each service has one responsibility
class PaymentProcessor
{
public function process(Payment $payment) { /* ... */ }
}
class InventoryManager
{
public function updateStock(Order $order) { /* ... */ }
}
class NotificationService
{
public function sendOrderConfirmation(Order $order) { /* ... */ }
}
// Bad: One class doing everything
class OrderManager
{
public function processOrder($order)
{
// Processes payment
// Updates inventory
// Sends emails
// Updates database
}
}
<?php
// Good: Dependency injection
class OrderService
{
public function __construct(PaymentGateway $gateway) { /* ... */ }
}
// Bad: Static method calls
class OrderService
{
public function processOrder($order)
{
PaymentGateway::charge($order->total);
EmailService::sendConfirmation($order);
// Hard to test and mock
}
}
<?php
// Only load when needed
class HeavyServiceProvider extends ServiceProvider implements DeferrableProvider
{
public function provides(): array
{
return [HeavyService::class];
}
}
<?php
// Use closures for expensive operations
$this->app->singleton(ReportService::class, function ($app) {
return new ReportService($app->make(HeavyDependency::class));
});
// Resolve only when needed
public function generateReport()
{
$reportService = app(ReportService::class); // Only instantiated here
return $reportService->generate();
}
You've now mastered the Laravel Service Container - the heart of the framework! In our next post, we'll explore Laravel Service Providers: How to Register Your Services in the Container to learn how to organize your service registrations.