# Service Pattern Services encapsulate business logic and coordinate between multiple models or external systems. ## When to Use Services Use services for: - Complex business logic involving multiple models - External API integrations - Operations requiring multiple steps - Reusable functionality across controllers **Don't use services for:** - Simple CRUD operations (use Actions) - Single-model operations - View logic (use View Models) ## Basic Service ```php validateReadyForPublish($post); // Update post $post->update([ 'status' => 'published', 'published_at' => now(), 'published_by' => $user->id, ]); // Generate SEO metadata $this->generateSeoMetadata($post); // Notify subscribers $this->notifySubscribers($post); // Update search index $post->searchable(); return $post->fresh(); } protected function validateReadyForPublish(Post $post): void { if (empty($post->title)) { throw new ValidationException('Post must have a title'); } if (empty($post->content)) { throw new ValidationException('Post must have content'); } if (!$post->featured_image) { throw new ValidationException('Post must have a featured image'); } } protected function generateSeoMetadata(Post $post): void { if (empty($post->meta_description)) { $post->meta_description = str($post->content) ->stripTags() ->limit(160); } if (empty($post->og_image)) { GenerateOgImageJob::dispatch($post); } $post->save(); } protected function notifySubscribers(Post $post): void { NotifySubscribersJob::dispatch($post); } } ``` **Usage:** ```php $service = app(PostPublishingService::class); $publishedPost = $service->publish($post, auth()->user()); ``` ## Service with Constructor Injection ```php apiUrl}/events", [ 'api_key' => $this->apiKey, 'event' => 'pageview', 'url' => $url, 'meta' => $meta, ]); } public function getPageViews(string $url, int $days = 30): int { return Cache::remember( "analytics.pageviews.{$url}.{$days}", now()->addHour(), fn () => Http::get("{$this->apiUrl}/stats", [ 'api_key' => $this->apiKey, 'url' => $url, 'days' => $days, ])->json('views') ); } } ``` **Service Provider:** ```php $this->app->singleton(AnalyticsService::class, function () { return new AnalyticsService( apiKey: config('analytics.api_key'), apiUrl: config('analytics.api_url') ); }); ``` ## Service Contracts Define interfaces for flexibility: ```php client->paymentIntents->create([ 'amount' => $amount, 'currency' => $currency, 'metadata' => $meta, ]); return new PaymentResult( success: $intent->status === 'succeeded', transactionId: $intent->id, amount: $intent->amount, currency: $intent->currency ); } // ... other methods } ``` ## Service with Dependencies ```php inventory->available($order->items)) { return ProcessingResult::failed('Insufficient inventory'); } // Reserve inventory $this->inventory->reserve($order->items); try { // Charge payment $payment = $this->payment->charge( amount: $order->total, currency: $order->currency, meta: ['order_id' => $order->id] ); if (!$payment->success) { $this->inventory->release($order->items); return ProcessingResult::failed('Payment failed'); } // Update order $order->update([ 'status' => 'paid', 'transaction_id' => $payment->transactionId, 'paid_at' => now(), ]); // Send confirmation $this->email->send( to: $order->customer->email, template: 'order-confirmation', data: compact('order', 'payment') ); return ProcessingResult::success($order); } catch (\Exception $e) { $this->inventory->release($order->items); throw $e; } } } ``` ## Service with Events ```php update([ 'status' => 'scheduled', 'publish_at' => $publishAt, ]); // Dispatch event event(new PostScheduled($post, $publishAt)); // Queue job to publish PublishScheduledPostJob::dispatch($post) ->delay($publishAt); } public function publishScheduledPost(Post $post): void { if ($post->status !== 'scheduled') { throw new InvalidStateException('Post is not scheduled'); } $post->update([ 'status' => 'published', 'published_at' => now(), ]); event(new PostPublished($post)); } } ``` ## Testing Services ```php create(); $post = Post::factory()->create(['status' => 'draft']); $result = $service->publish($post, $user); $this->assertEquals('published', $result->status); $this->assertNotNull($result->published_at); $this->assertEquals($user->id, $result->published_by); } public function test_validates_post_before_publishing(): void { $service = app(PostPublishingService::class); $user = User::factory()->create(); $post = Post::factory()->create([ 'title' => '', 'status' => 'draft', ]); $this->expectException(ValidationException::class); $service->publish($post, $user); } public function test_generates_seo_metadata(): void { $service = app(PostPublishingService::class); $user = User::factory()->create(); $post = Post::factory()->create([ 'content' => 'Long content here...', 'meta_description' => null, ]); $result = $service->publish($post, $user); $this->assertNotNull($result->meta_description); } } ``` ## Best Practices ### 1. Single Responsibility ```php // ✅ Good - focused service class EmailVerificationService { public function sendVerificationEmail(User $user): void {} public function verify(string $token): bool {} public function resend(User $user): void {} } // ❌ Bad - too broad class UserService { public function create() {} public function sendEmail() {} public function processPayment() {} public function generateReport() {} } ``` ### 2. Dependency Injection ```php // ✅ Good - injected dependencies public function __construct( protected EmailService $email, protected PaymentGateway $payment ) {} // ❌ Bad - hard-coded dependencies public function __construct() { $this->email = new EmailService(); $this->payment = new StripeGateway(); } ``` ### 3. Return Types ```php // ✅ Good - explicit return type public function process(Order $order): ProcessingResult { return new ProcessingResult(...); } // ❌ Bad - no return type public function process(Order $order) { return [...]; } ``` ### 4. Error Handling ```php // ✅ Good - handle errors gracefully public function process(Order $order): ProcessingResult { try { $result = $this->payment->charge($order->total); return ProcessingResult::success($result); } catch (PaymentException $e) { Log::error('Payment failed', ['order' => $order->id, 'error' => $e->getMessage()]); return ProcessingResult::failed($e->getMessage()); } } ``` ## Learn More - [Actions Pattern →](/patterns-guide/actions) - [Repository Pattern →](/patterns-guide/repositories)