Laravel Migrations: How to Create and Modify Your Database Tables
Learn to version control your database schema with Laravel migrations.
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.