Проекты Laravel и Symfony — отличные места для использования агентов ИИ. Не потому, что PHP «прост» (это не так), а потому, что серверные части PHP содержат много реальной бизнес-логики. Контроллеры, сервисы, консольные команды, задания очередей, репозитории Doctrine, модели Eloquent, прослушиватели событий, валидаторы, политики, миграции и тесты — все сосуществует в одном репозитории, а отношения между ними представляют собой именно тот контекст, загрузка которого обходится человеку дорого, а отображение внимательному агенту обходится дешево.
Лучшее использование агента в кодовой базе PHP — это не «сгенерировать мне случайный код». Это ближе к:
Help me understand this backend flow. Help me protect behavior. Help me find missing tests. Help me review risky queries. Help me document what this code actually does.
Именно здесь ИИ становится партнером по кодовой базе, а не наборщиком кода. Хороший агент Laravel/Symfony должен работать как внимательный старший помощник. Он проверяет файлы, отображает потоки, объясняет побочные эффекты, предлагает тесты, просматривает запросы ORM и редактирует код только тогда, когда вы явно разрешаете это. В этой статье рассматриваются практические рабочие процессы для такого типа агентов таким образом, чтобы не превратить вашу кодовую базу в игру в угадайку.
Начните с понимания, а не редактирования
Хороший агент должен сначала ответить: «Что делает этот код?» прежде чем он спросит: «Какой код мне написать?» Этот порядок имеет большее значение в PHP, чем в большинстве языков, потому что Laravel и Symfony скрывают большую часть поведения за фасадами, сервисными контейнерами и прослушивателями событий. Чтение контроллера изолированно почти ничего не говорит вам о том, что на самом деле происходит, когда срабатывает маршрут.
Возьмите этот контроллер Laravel:
final class SubscriptionController
{
public function cancel(Request $request, int $subscriptionId): JsonResponse
{
$subscription = Subscription::query()
->where('user_id', $request->user()->id)
->findOrFail($subscriptionId);
$this->subscriptionService->cancel($subscription, $request->boolean('immediately'));
return response()->json([
'status' => $subscription->status,
'ends_at' => $subscription->ends_at?->toISOString(),
]);
}
}
Слабая подсказка — «рефакторить этот контроллер». Он переводит агента в режим редактирования еще до того, как он поймет, за что отвечает код. Гораздо лучший стартовый ход — это что-то вроде:
Analyze this Laravel controller and explain the backend flow. Please identify: - the HTTP entry point, - request inputs, - authorization assumptions, - service calls, - database reads/writes, - events/jobs/emails that may happen downstream, - response shape, - behavior that should be protected by tests. Do not edit code yet.
Это намного безопаснее, и на такой запрос агент действительно может хорошо ответить. В ответ может появиться что-то вроде:
The controller scopes the subscription by current user. The `immediately` request parameter changes cancellation behavior. The response shape includes `status` and `ends_at`. The service may contain important side effects. Tests should protect user scoping and response shape.
Теперь вы понимаете поверхность риска перед рефакторингом: что такое публичный контракт, что скрыто и где тесты должны проходить в первую очередь.
Laravel Flow: контроллеры, сервисы, задания, команды
Проекты Laravel обычно имеют несколько типов точек входа для одной и той же функции: Routes/api.php , HTTP-контроллер, команда Artisan, запланированная команда, задание очереди, прослушиватель событий, контроллер веб-перехватчика, класс уведомлений. Одна и та же бизнес-операция часто проходит по нескольким из этих путей, и удивительные ошибки обитают именно там, где эти пути расходятся.
Поэтому вопрос «найти каждую точку входа» очень важен для агента:
Find every entry point related to subscription cancellation. Search for: - routes, - controllers, - services, - jobs, - commands, - event listeners, - notifications, - tests. Group the results by execution path. For each path, explain what triggers it and what side effects it may cause.
Это важно, поскольку замена только контроллера может не изменить реальный путь производства. Например:
final class CancelExpiredTrialsCommand extends Command
{
protected $signature = 'subscriptions:cancel-expired-trials';
public function handle(): int
{
Subscription::query()
->where('status', 'trialing')
->where('trial_ends_at', '<', now())
->each(fn (Subscription $subscription) =>
$this->subscriptionService->cancel($subscription, immediately: true)
);
return self::SUCCESS;
}
}
Один и тот же SubscriptionService::cancel() вызывается как из контроллера API, так и из запланированной команды, что означает, что ваши тесты должны охватывать оба пути. Агент, который отображает для вас граф вызовов, уловит это, даже если grep этого не сделает.
Symfony Flow: контроллеры, сервисы, команды, доктрина
Проекты Symfony делают зависимости более явными с помощью сервисов и внедрения конструкторов, что является хорошей новостью для агента. Меньше магии, за которой можно гоняться. Типичный контроллер выглядит так:
final class CancelSubscriptionController
{
public function __construct(
private readonly SubscriptionCanceller $subscriptionCanceller,
private readonly SubscriptionRepository $subscriptionRepository,
) {}
public function __invoke(Request $request, string $id): JsonResponse
{
$subscription = $this->subscriptionRepository->findOwnedByUser(
$id,
$this->getUser()->getId(),
);
if (!$subscription) {
throw $this->createNotFoundException();
}
$this->subscriptionCanceller->cancel(
subscription: $subscription,
immediately: $request->query->getBoolean('immediately'),
);
return $this->json([
'status' => $subscription->status()->value,
'endsAt' => $subscription->endsAt()?->format(DATE_ATOM),
]);
}
}
Полезная подсказка для такого типа файла:
Analyze this Symfony controller and related services. Explain: - which services are injected, - which repository methods are used, - how authorization is enforced, - where Doctrine flush likely happens, - which domain events or messages may be dispatched, - which response fields are public API contracts, - what tests should exist before refactoring.
Агент может помочь вам отслеживать путь от контроллера к сервису и репозиторию, но он всегда должен проверять файлы и никогда не предполагать поведение только на основе имен классов. Слои предметной области полны сервисов, которые выглядят общими и делают что-то очень специфическое.
Создание тестов PHPUnit, защищающих поведение
ИИ может генерировать тесты быстро, но быстрота не является целью. Случайных тестов недостаточно. Вам нужны тесты, защищающие поведение, такие, которые громко терпят неудачу, когда рефакторинг случайно изменяет побочный эффект или контракт ответа.
Для Laravel это выглядит примерно так:
public function test_user_can_cancel_own_subscription(): void
{
Queue::fake();
Mail::fake();
$user = User::factory()->create();
$subscription = Subscription::factory()->create([
'user_id' => $user->id,
'status' => 'active',
]);
$response = $this
->actingAs($user)
->postJson("/api/subscriptions/{$subscription->id}/cancel", [
'immediately' => true,
]);
$response
->assertOk()
->assertJsonStructure([
'status',
'ends_at',
]);
$this->assertDatabaseHas('subscriptions', [
'id' => $subscription->id,
'status' => 'canceled',
]);
}
В Symfony та же идея была воплощена в WebTestCase:
public function testUserCanCancelOwnSubscription(): void
{
$client = static::createClient();
$user = UserFactory::createOne();
$subscription = SubscriptionFactory::createOne([
'user' => $user,
'status' => SubscriptionStatus::Active,
]);
$client->loginUser($user->_real());
$client->request(
'POST',
sprintf('/api/subscriptions/%s/cancel', $subscription->getId()),
['immediately' => true],
);
self::assertResponseIsSuccessful();
$payload = json_decode($client->getResponse()->getContent(), true);
self::assertArrayHasKey('status', $payload);
self::assertArrayHasKey('endsAt', $payload);
}
Когда вы просите агента выполнить подобные тесты, заранее сообщите ему правила:
Generate PHPUnit tests for this backend flow. Rules: - Protect current behavior before refactoring. - Include authorization tests. - Include failure cases. - Include response shape checks. - Include database assertions. - Mock external services, but do not mock the domain logic. - Explain why each test exists.
Эта последняя строка является важной. Проверка без указания причины часто является просто шумом. Он закрепляет поведение, которое никто не выбрал намеренно, и следующий рефакторинг должен с этим бороться.
Обзор красноречивых запросов
Агенты ИИ полезны для проверки запросов Eloquent, поскольку код ORM может скрывать проблемы SQL, которые хорошо выглядят на месте вызова. Возьмем этот пример:
$orders = Order::query()
->where('status', 'paid')
->whereDate('created_at', now()->toDateString())
->get();
foreach ($orders as $order) {
echo $order->customer->email;
}
Это выглядит просто, но имеет две общие проблемы. Во-первых, whereDate() может помешать эффективному использованию индекса, поскольку он применяет к столбцу выражение даты, поэтому обычный индекс created_at использовать невозможно. Во-вторых, $order->customer внутри цикла запускает один дополнительный запрос на каждый заказ. Учебник N+1.
Более безопасная перезапись:
$start = now()->startOfDay();
$end = now()->endOfDay();
$orders = Order::query()
->with('customer')
->where('status', 'paid')
->whereBetween('created_at', [$start, $end])
->get();
foreach ($orders as $order) {
echo $order->customer->email;
}
Подсказка, которая приведет вас туда:
Review this Eloquent query for performance. Check: - possible N+1 queries, - missing eager loading, - functions applied to indexed columns, - filtering order, - pagination issues, - whether an index may help, - whether the query loads too many rows. Suggest the safest change first. Do not change behavior silently.
Агент должен объяснить компромисс, а не просто переписать код. «Более безопасный» запрос, который незаметно удаляет строку или меняет порядок, на самом деле не безопаснее.
Обзор запросов Doctrine
Доктрина также может скрывать проблемы с производительностью, но в несколько иных формах. Типичный вызов репозитория:
$orders = $orderRepository->findBy([
'status' => OrderStatus::Paid,
]);
foreach ($orders as $order) {
echo $order->getCustomer()->getEmail();
}
Если клиент загружается с отложенной загрузкой (по умолчанию в Doctrine), это запускает один запрос для заказов, а затем один запрос для каждого клиента. Версия построителя запросов, которая извлекает оба за один раз:
$orders = $entityManager->createQueryBuilder()
->select('o', 'c')
->from(Order::class, 'o')
->join('o.customer', 'c')
->where('o.status = :status')
->setParameter('status', OrderStatus::Paid)
->getQuery()
->getResult();
Полезная подсказка агента для Doctrine:
Review this Doctrine query and related entity mappings. Check: - lazy loading that may cause N+1 queries, - missing joins, - hydration cost, - pagination behavior, - indexes needed by WHERE/JOIN columns, - whether the query loads unnecessary associations. Return: - current behavior, - likely SQL shape, - performance risks, - safest improvement, - tests or profiling checks.
Обнаружение проблем N+1
Запросы N+1 — одна из самых простых ошибок производительности, которую агенту легче всего обнаружить, если вы задаете правильный вопрос. Пример Laravel:
$users = User::query()->where('active', true)->get();
return $users->map(fn (User $user) => [
'id' => $user->id,
'team' => $user->team->name,
]);
Лучше, с быстрой загрузкой:
$users = User::query()
->with('team')
->where('active', true)
->get();
Эквивалент Symfony/Doctrine:
$users = $userRepository->findActiveUsers();
foreach ($users as $user) {
$teamName = $user->getTeam()->getName();
}
Лучше, с объединением:
$queryBuilder
->select('u', 't')
->from(User::class, 'u')
->join('u.team', 't')
->where('u.active = true');
Подсказка, которая постоянно их улавливает:
Analyze this code for possible N+1 queries. Look for: - relationship access inside loops, - lazy-loaded Doctrine associations, - Eloquent relationship properties, - serializers that access relations, - API resources that access nested data, - notifications or exports that loop over models. For each possible N+1: - show the line, - explain why it may trigger extra queries, - suggest eager loading or query change, - mention possible memory trade-offs.
Компромисс памяти стоит отметить вслух. Стремительная загрузка всего не всегда корректна. Конечная точка списка, которая извлекает 10 000 строк, не должна также увлажнять каждый связанный объект. Правильное решение зависит от места вызова.
Рефакторинг устаревшего PHP небольшими шагами
Устаревший PHP часто имеет большие методы обслуживания, которые делают пять вещей одновременно. Что-то вроде:
public function processRefund(int $orderId, int $amount): void
{
$order = Order::findOrFail($orderId);
if ($order->status !== 'paid') {
throw new RuntimeException('Order is not refundable.');
}
if ($amount > $order->paid_amount) {
throw new RuntimeException('Refund amount is too high.');
}
$response = $this->gateway->refund($order->transaction_id, $amount);
if (!$response->successful()) {
Log::warning('Refund failed', ['order_id' => $order->id]);
throw new RuntimeException('Refund failed.');
}
$order->refunds()->create([
'amount' => $amount,
'gateway_reference' => $response->reference(),
]);
$order->update([
'refunded_amount' => $order->refunded_amount + $amount,
]);
event(new OrderRefunded($order));
Mail::to($order->user)->queue(new RefundProcessedMail($order));
}
Не просите агента «переписать это начистоту». Эта формулировка побуждает его изобретать абстракции, не соответствующие вашей предметной области. Сначала попросите его провести анализ:
Analyze this legacy PHP method for safe refactoring. First: - summarize current behavior, - list validation rules, - list side effects, - identify external services, - identify events/emails/jobs, - suggest characterization tests, - propose a small-step refactor plan. Do not change code yet.
Хороший план от агента может выглядеть так:
1. Add tests for non-paid order, amount too high, gateway failure, successful refund. 2. Extract refund eligibility checks into a private method. 3. Extract gateway refund call into a small method. 4. Keep event and email behavior unchanged. 5. Only then consider a dedicated RefundService.
Вот как ИИ помогает, не создавая хаоса: небольшие, проверяемые шаги в таком порядке, который сохраняет неизменным поведение между каждым коммитом.
Обновление документации для серверных потоков
Агенты ИИ отлично подходят для документирования, поскольку они могут читать пути кода и суммировать их, а это именно та работа, которую люди склонны пропускать. Настоящая подсказка:
Create developer documentation for this backend flow. Include: - entry points, - request parameters, - main services, - database tables changed, - events/jobs/emails triggered, - external APIs called, - failure modes, - tests that cover the flow. Use plain English. Do not invent behavior. If something is uncertain, mark it as uncertain.
Вывод выглядит примерно так:
# Subscription Cancellation Flow
## Entry Points
- `POST /api/subscriptions/{id}/cancel`
- `subscriptions:cancel-expired-trials` scheduled command
## Main Service
`SubscriptionService::cancel()` handles cancellation rules.
## Side Effects
- Updates `subscriptions.status`
- May set `subscriptions.ends_at`
- Dispatches `SubscriptionCanceled`
- Queues cancellation email
## External APIs
The payment gateway may be called for immediate cancellation.
## Tests
- `CancelSubscriptionTest`
- `CancelExpiredTrialsCommandTest`
Этот вид документа является золотым для адаптации и обслуживания. Он отображает поведение, которое уже есть в коде, и никому не нужно перечитывать весь модуль.
Практическая настройка агента для команд PHP
Полезному PHP-агенту не нужен неограниченный доступ. Хорошо работает шаблон, представляющий собой многоуровневый набор инструментов. По умолчанию только чтение, безопасное выполнение там, где это окупается, и доступ для записи только после подтверждения:
Read-only tools: - search codebase, - read file, - list routes, - inspect composer.json, - inspect migrations, - inspect tests. Safe execution tools: - run PHPUnit, - run PHPStan/Psalm, - run PHP CS Fixer in dry-run mode, - run Doctrine schema validation, - run Laravel route:list. Write tools with approval: - edit files, - create tests, - update docs, - create PR summary. Blocked by default: - deploy, - run arbitrary shell, - access production secrets, - modify .env, - change CI secrets, - merge PRs.
Это дает агенту достаточно возможностей, чтобы помочь, но недостаточно, чтобы повредить систему. Агент, который может просматривать ваш код и запускать тесты, уже является старшим сотрудником; тот, у которого есть ключи развертывания, - это просто обуза.
Заключительные мысли
Лучший рабочий процесс Laravel/Symfony с ИИ — это не «ИИ пишет код, разработчик надеется, что он работает». Это ближе к:
AI maps the flow. AI finds risks. AI suggests tests. AI reviews queries. AI documents behavior. Developer decides and approves changes.
Именно здесь ИИ естественным образом вписывается в процесс разработки серверной части. Используйте агенты для понимания сервисов, контроллеров, заданий, команд, Doctrine, Eloquent, тестов и документации. Пусть они сокращают время загрузки контекста. Не позволяйте им заменять суждение.
В PHP-проектах самый ценный агент — это не тот, кто пишет больше всего кода. Именно он помогает вам менять меньше кода и более безопасно.
Первоначально опубликовано на nazarboyko.com .



Комментарии (0)
Пока нет комментариев — будьте первым.