← Назад к вопросам

Как строишь запрос на один эндпоинт?

2.0 Middle🔥 201 комментариев
#Другое

Комментарии (1)

🐱
deepseek-v3.2PrepBro AI6 апр. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Проектирование эндпоинта в PHP Backend

При проектировании запроса к одному эндпоинту я придерживаюсь комплексного подхода, который охватывает все слои приложения — от получения HTTP:запроса до формирования ответа. Вот ключевые этапы и принципы, которые я применяю на практике.

1. Определение семантики эндпоинта (HTTP:метод и URL)

Первым делом выбираю соответствующий HTTP:метод согласно REST:ful принципам:

  • GET — получение ресурса (например, /api/users/{id})
  • POST — создание нового ресурса (/api/users)
  • PUT / PATCH — полное или частичное обновление (/api/users/{id})
  • DELETE — удаление ресурса (/api/users/{id})

URL:строительство следует принципам иерархичности и читаемости:

// Примеры хорошо структурированных эндпоинтов
GET /api/v1/users/123
PUT /api/v1/articles/456/comments/789
POST /api/v1/orders

2. Валидация и санитизация входных данных

Это критически важный этап, где я применяю многоуровневую проверку:

<?php
// Пример валидации в Laravel-стиле (аналогично в других фреймворках)
public function store(Request $request)
{
    $validated = $request->validate([
        'email' => 'required|email|max:255|unique:users',
        'name' => 'required|string|min:2|max:100',
        'age' => 'required|integer|min:18|max:120',
        'tags' => 'array|max:5',
        'tags.*' => 'string|distinct'
    ]);
    
    // Дополнительная бизнес-Cлогика валидация
    if ($this->userService->isEmailBanned($validated['email'])) {
        abort(422, 'Email is not allowed');
    }
    
    return response()->json(['data' => $validated], 201);
}

3. Работа с бизнес-Cлогикой и данными

После валидации запрос проходит через сервисный слой:

<?php
// Service class with business logic
class UserService
{
    public function createUser(array $data): User
    {
        // Транзакция для гарантии целостности данных
        return DB::transaction(function () use ($data) {
            $user = User::create([
                'email' => $data['email'],
                'name' => $data['name'],
                // Хеширование пароля перед сохранением
                'password' => Hash::make($data['password']),
                'status' => 'pending'
            ]);
            
            // Отправка события для обработки side:effects
            event(new UserRegistered($user));
            
            // Создание связанных записей
            $user->profile()->create(['age' => $data['age']]);
            
            return $user;
        });
    }
}

4. Авторизация и аутентификация

Реализую проверку прав доступа через middleware или policy:

<?php
// Использование Laravel Policies
public function update(User $user, Post $post)
{
    // Проверка через Policy
    $this->authorize('update', $post);
    
    // Или через Gates
    if (!Gate::allows('edit-post', $post)) {
        abort(403, 'Unauthorized action');
    }
    
    // Или через JWT:токены для API
    $token = JWTAuth::parseToken()->authenticate();
    if ($token->cannot('update', $post)) {
        return response()->json(['error' => 'Forbidden'], 403);
    }
}

5. Обработка исключений и ошибок

Создаю централизованный обработчик ошибок:

<?php
// App\Exceptions\Handler.php
public function render($request, Throwable $exception)
{
    if ($exception instanceof ModelNotFoundException) {
        return response()->json([
            'message' => 'Resource not found',
            'errors' => ['id' => 'Invalid resource identifier']
        ], 404);
    }
    
    if ($exception instanceof ValidationException) {
        return response()->json([
            'message' => 'Validation failed',
            'errors' => $exception->errors()
        ], 422);
    }
    
    // Логирование для дебаггинга
    Log::error('API Error', [
        'exception' => $exception->getMessage(),
        'trace' => $exception->getTraceAsString()
    ]);
    
    return parent::render($request, $exception);
}

6. Форматирование ответа

Использую трансформеры или ресурсы для единообразного формата ответа:

<?php
// Laravel Resource
class UserResource extends JsonResource
{
    public function toArray($request)
    {
        return [
            'data' => [
                'id' => $this->id,
                'type' => 'users',
                'attributes' => [
                    'name' => $this->name,
                    'email' => $this->email,
                    'created_at' => $this->created_at->toISOString()
                ],
                'relationships' => [
                    'profile' => new ProfileResource($this->profile)
                ]
            ],
            'links' => [
                'self' => route('users.show', $this->id)
            ],
            'meta' => [
                'version' => '1.0'
            ]
        ];
    }
}

7. Оптимизация производительности

Ключевые техники для быстрых эндпоинтов:

  • Eager:loading отношений для избежания N+1 проблемы:
User::with(['profile', 'posts.comments'])->find($id);
  • Пагинация для больших наборов данных:
$users = User::paginate(20, ['*'], 'page', $request->input('page', 1));
  • Кэширование часто запрашиваемых данных:
$user = Cache::remember("user_{$id}", 3600, function () use ($id) {
    return User::with('profile')->find($id);
});

8. Документирование и тестирование

Завершаю процесс созданием:

  • PHPUnit или Pest тестов:
<?php
public function test_user_can_be_retrieved()
{
    $user = User::factory()->create();
    
    $response = $this->getJson("/api/v1/users/{$user->id}");
    
    $response->assertStatus(200)
             ->assertJsonStructure([
                 'data' => ['id', 'attributes' => ['name', 'email']]
             ]);
}
  • Swagger/OpenAPI документации через аннотации:
/**
 * @OA\Get(
 *     path="/api/v1/users/{id}",
 *     @OA\Parameter(name="id", in="path", required=true),
 *     @OA\Response(response=200, description="Success"),
 *     @OA\Response(response=404, description="Not found")
 * )
 */

Заключение

Построение качественного эндпоинта — это баланс между безопасностью, производительностью и поддерживаемостью кода. Я всегда придерживаюсь принципа "хороший API:дизайн — это продуманный API:дизайн", где каждый эндпоинт имеет четкую ответственность, последовательную структуру ответов и комплексную обработку edge:cases. Современные PHP:фреймворки предоставляют отличные инструменты для реализации этих принципов, но важно понимать底层 процессы, чтобы создавать действительно надежные и масштабируемые решения.