1. Service Layer and Repository Pattern
Implement a clean service layer architecture with repository pattern for better code organization and maintainability. This approach demonstrates modern Laravel patterns including dependency injection, repositories, and service classes.
laravel-service-repository.mdc
model->find($id);
}
public function create(array $data): User
{
return $this->model->create($data);
}
public function update(User $user, array $data): bool
{
return $user->update($data);
}
public function delete(User $user): bool
{
return $user->delete();
}
}
namespace App\Services;
use App\DTOs\UserData;
use App\Events\UserCreated;
use App\Exceptions\UserServiceException;
use App\Models\User;
use App\Repositories\Contracts\UserRepositoryInterface;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
class UserService
{
public function __construct(
private UserRepositoryInterface $userRepository
) {
}
public function createUser(UserData $userData): User
{
try {
DB::beginTransaction();
$user = $this->userRepository->create([
'name' => $userData->name,
'email' => $userData->email,
'password' => Hash::make($userData->password),
]);
// Create associated profile
$user->profile()->create([
'bio' => $userData->bio ?? '',
'avatar' => $userData->avatar ?? null,
]);
event(new UserCreated($user));
DB::commit();
return $user;
} catch (\Exception $e) {
DB::rollBack();
throw new UserServiceException(
'Failed to create user: ' . $e->getMessage(),
previous: $e
);
}
}
public function updateUser(User $user, UserData $userData): bool
{
try {
DB::beginTransaction();
$updated = $this->userRepository->update($user, [
'name' => $userData->name,
'email' => $userData->email,
]);
if ($userData->password) {
$this->userRepository->update($user, [
'password' => Hash::make($userData->password),
]);
}
// Update profile
$user->profile->update([
'bio' => $userData->bio ?? $user->profile->bio,
'avatar' => $userData->avatar ?? $user->profile->avatar,
]);
DB::commit();
return $updated;
} catch (\Exception $e) {
DB::rollBack();
throw new UserServiceException(
'Failed to update user: ' . $e->getMessage(),
previous: $e
);
}
}
}
namespace App\DTOs;
class UserData
{
public function __construct(
public readonly string $name,
public readonly string $email,
public readonly ?string $password = null,
public readonly ?string $bio = null,
public readonly ?string $avatar = null,
) {
}
public static function fromRequest(Request $request): self
{
return new self(
name: $request->input('name'),
email: $request->input('email'),
password: $request->input('password'),
bio: $request->input('bio'),
avatar: $request->input('avatar'),
);
}
}
namespace App\Http\Controllers;
use App\DTOs\UserData;
use App\Services\UserService;
use App\Http\Requests\CreateUserRequest;
use App\Http\Resources\UserResource;
class UserController extends Controller
{
public function __construct(
private UserService $userService
) {
}
public function store(CreateUserRequest $request)
{
$userData = UserData::fromRequest($request);
$user = $this->userService->createUser($userData);
return new UserResource($user);
}
public function update(UpdateUserRequest $request, User $user)
{
$userData = UserData::fromRequest($request);
$updated = $this->userService->updateUser($user, $userData);
return response()->json([
'success' => $updated,
'user' => new UserResource($user->fresh()),
]);
}
}
Why This Rule Matters
- Separates business logic from controllers
- Makes code more testable with dependency injection
- Improves maintainability with clear architectural boundaries
- Enables better error handling and transaction management
- Makes the codebase more scalable
Using with Cursor
Cursor's AI capabilities can help you:
- Generate repository and service class templates
- Suggest appropriate method signatures and return types
- Identify opportunities for code extraction into services
- Debug dependency injection issues
- Convert controller logic to service layer patterns