Недавно упоминал, что работаю над CRMкой для турагентств и турагентов, которую написал давно. Соответственно, Laravel уже очень старый. Я писал, что 6, а при проверке оказалось, что вообще 5, и это несколько сковывает мои возможности. В общем, решил осовременить систему, чтобы проще было работать и интегрировать более современные библиотеки (типа Horizon например). Поставил, короче, задачу обновиться за выходные. Ну и делюсь с вами полученным опытом.
А то в чатах иногда пишут, что практических примеров кодинга с ИИ маловато. Вот — самый настоящий практический пример.
Без лишних прелюдий, дано:
- Legacy-код на Laravel 5;
- набор из 580 тестов (код разрабатывался по TDD);
- Cursor AI бесплатная версия — чтобы было видно, что с ней, на автовыборе модели, тоже можно.
Нужно: обновить код до Laravel 12 так, чтобы он не сломался.
TL/DR если не хочется читать технические подробности:
- ИИшечка помогает, но есть нюансы (внутри заметки).
- Без ИИшечки я бы ещё очень долго это откладывал.
- А без пакета заботливо написанных при разработке тестов скорее всего вообще не стал бы.
- Задача была выполнена.
- Суммарное время выполнения составило около 8-9 часов.
Итак, нам нужно обновить Laravel 5 до 12. Делаем мы это по шагам, то есть так: 5 → 6 → 7 → 8 → 9 → 10 → 11 → 12, всего 7 обновлений. Для каждого разработчики Ларавела заботливо написали roadmap по апгрейду. Вот например для апгрейда до Laravel 6: https://laravel.com/docs/6.x/upgrade
Что мы делаем.
Мы открываем курсор и говорим ему:
Вот инструкция по апгрейду моего проекта на Laravel 5 до Laravel 6.
<тут просто копипастим весь текст документа>
Пожалуйста, прочитай, и давай проведем апгрейд следующим образом.
Сначала составь список действий.
Затем иди по нему шаг за шагом. После каждого шага останавливайся и спрашивай меня.
Пожалуйста, не вставляй в список никаких изменений, которых нет в приложенном документе. А список действий в документе проверь очень тщательно, чтобы ничего не забыть.
Когда будешь запускать команды, запомни, что composer, phpunit и всё остальное находятся в docker-контейнере, который называется "mycontainer", и проект тоже в нём. Директория - /var/www
Этот набор инструкций появился постепенно, так сказать вымучен в работе по обновлению, в связи со следующими моментами:
- Если не останавливаться, Cursor в своем энтузиазме может менять и править то что не нужно, и может забыть часть своего же списка.
- Если не попросить ничего от себя не добавлять, нейросеть забегает вперед и добавляет шаги из следующих апдейтов (скажем, изменения от Laravel 8 к апдейту в Laravel 6). Которые, разумеется не будут работать.
- Если не попросить, иишечка может забыть пунктик-другой из документа, особенно если они оформлены не очень удачно (скажем, не получили отдельное упоминание в оглавлении).
- Cursor AI прекрасно умеет работать с докером. Если ему заранее сказать, то он будет генерить команды типа
docker exec -w /var/www mycontainer composer update
docker exec -w /var/www mycontainer vendor/bin/phpunit
и подбирать их выдачу, так что не придется копировать ошибки обратно в чат руками.
В результате получаем список задач, например, для апгрейда 9 → 10:
Here’s a clear, step-by-step upgrade plan for moving your Laravel 9 project to Laravel 10, strictly following the official upgrade guide you provided. I’ll break down the process into actionable steps, and after each step, I’ll stop and ask for your confirmation before proceeding.
Laravel 9 → 10 Upgrade Plan
1. Pre-Upgrade Preparation
- [ ] Ensure your local environment and Docker container meet the new requirements:
- PHP 8.1.0+ (inside the docturist container)
- Composer 2.2.0+
- [ ] Commit all current changes and back up your project and database.
2. Update Composer Dependencies
- [ ] Update composer.json:
- laravel/framework to ^10.0
- laravel/sanctum to ^3.2
- doctrine/dbal to ^3.0
- spatie/laravel-ignition to ^2.0
- laravel/passport to ^11.0 (if used)
- laravel/ui to ^4.0 (if used)
- nunomaduro/collision to ^7.0
- phpunit/phpunit to ^10.0
- [ ] Remove or set "minimum-stability": "stable" in composer.json.
- [ ] Run composer update inside the docturist container.
3. PHPUnit 10 Compatibility
- [ ] Remove processUncoveredFiles from
in phpunit.xml if present.
4. Codebase Review & Refactorings
Medium/High Impact
- [ ] Database Expressions: Search for any (string) DB::raw(...) or __toString on DB expressions and update to use getValue().
- [ ] Model $dates Property: Replace any $dates property in models with $casts.
- [ ] Monolog 3: Check for direct Monolog usage or third-party logging packages and update as needed.
- [ ] Redis Cache Tags: If using Redis cache tags, review and refactor as recommended.
- [ ] Service Mocking: Replace MocksApplicationServices trait and related methods with Event::fake, Bus::fake, Notification::fake.
- [ ] The Language Directory: No action needed unless you want to publish the lang directory.
Low Impact
- [ ] Closure Validation Rule Messages: Check for closure validation rules and update $fail usage if needed.
- [ ] Form Request after Method: Rename any after method in Form Requests.
- [ ] Public Path Binding: If customizing public path, use app()->usePublicPath().
- [ ] Query Exception Constructor: Update manual QueryException throws.
- [ ] Rate Limiter Return Values: Check for RateLimiter::attempt usage.
- [ ] Redirect::home: Replace with Redirect::route('home').
- [ ] Bus::dispatchNow: Replace with Bus::dispatchSync.
- [ ] registerPolicies: Remove manual call from AuthServiceProvider.
- [ ] ULID Columns: Check migrations for ulid() usage.
Optional
- [ ] Middleware Aliases: Optionally rename $routeMiddleware to $middlewareAliases in Http/Kernel.php.
5. Test & Validate
- [ ] Run the test suite (phpunit) inside the container.
- [ ] Manually test the application for any issues.
Would you like to proceed with Step 1: Pre-Upgrade Preparation? Or do you want to review or adjust the plan above before we start?
И дальше идем вместе с Курсором по списку. Зависимости решаются легко, проблем не возникает (кроме того что composer 1 жрёт памяти караул и обновления делает минут по 15). Курсор видит все жалобы композера и грамотно меняет версии пакетов одну за другой, удаляет старое, добавляет новое.
После обновлений, Курсор пройдет по изменениям кода. Ищет что нужно менять он тоже неплохо и быстро — особенно если проверяемое изменение проект не затрагивает. Но серьёзную проблему составили обновления, вроде простые, но обширные. Например, изменение сериализации дат или переход к фабрикам-объектам от вызовов factory()
. Дело в том, что ии хорошо умеет автоматически находить проблемные места в коде. Но ему не хватает контекста запомнить все что он нашёл.
Поэтому на запрос «исправь все фабрики в тестах и файлах фабрик» он бодро рапортует, что всё готово. Но по факту исправляет за один прогон процентов 10%. Помогает с этим более точный запрос: сначала просим его найти все файлы с проблемой и вывести список. А потом уже пройти по ним и исправить один за другим. Хотя если изменений сотни, все равно пару-тройку раз приходится его заставлять повторить операцию.
Когда шаги по апгрейду закончены, Курсор предложит запустить тесты. Можно запустить самостоятельно, а можно и его попросить — в таком случае я его прошу сгруппировать ошибки и вывести список с идеями, откуда они взялись. Поскольку ошибки при апгрейде, если они будут, чаще всего будут связаны с изменениями во фреймворке. Поэтому стоит ожидать, что множество тестов зафейлится с одинаковыми ошибками. Хотя бывают и разовые косяки. Главное в конце увидеть вот это:
Ну и потом, как говорится, rinse and repeat смыть и повторить. Повторить этот цикл еще 6 раз.
С чем в процессе еще удалось столкнуться.
Во-первых, тесты конечно здорово спасают. Потому что инструкции хоть и хорошие, но не всеобъемлющие. Например, в одном из апгрейдов обновляются до 5-й версии пакеты Симфони, и разумеется в документацию не могут попасть все их изменения. У меня вот например использовался класс Process — он поменял сигнатуру конструктора и тестик зафейлился. Для того мы их и пишем.
Также ИИ прошляпил изменение метода getOriginal()
на моделях. Раньше оно использовалось для получения оригинального значения атрибута, в обход геттеров и attribute-методов. Но в Laravel 7 этот метод стал возвращать уже значение атрибута, из-за чего тесты некоторых атрибутов ушли в рекурсию. Для получения исходного значения стало нужно использовать метод getRawOriginal()
. Наверно у Тейлора и компании были веские причины для такого переименования. Но чисто по-человечески понять, почему getOriginal()
не возвращает оригинальное значение, сложновато.
Самым долгим изменением оказался переход к Laravel 8 и далее 9. Дело в том, что у меня для автокомплита незатейливый драйвер поиска под mysql к Laravel Scout, но он поддерживался только до Laravel 7. А в L8 — только драйверы, предполагающие установку стороннего софта. А, как мне думается, такие вещи не следует смешивать: делаешь апгрейд — поапгрейдь, потом меняй софт. Здесь Курсор подсказал неправильно: нашёл какую-то малопопулярную замену на гитхабе, поставил, она встала как-то криво… Ну а надо было просто посмотреть в апгрейд до Laravel 9 — в нём автокомплит с драйвером database уже добавили в коробку Scout.
Пришлось переход 7 → 8 → 9 проходить, игнорируя около 20 тестов поиска, т.к. они не работали из-за отсутсвия нужного драйвера. Ну а когда дошли до 9 — логику и тестов, и самого поиска, пришлось конечно подрихтовать.
Также ближе к концу удалось подцепить гейзенбаг, связанный с копированием файлов и мокингом файловой системы. Курсор и подсказать, как его победить, смог, хоть и со скрипом. И просветить меня про гейзенбаги.
Заключение
Что еще. Курсор может иногда сбоить и портить тесты. Может пару методов просто взять и переименовать, как бы невзначай. Ещё в процессе нужно регулярно открывать новые чаты вместо старых, иначе иишечка начинает тупить и зацикливаться. В остальном, всё прошло довольно неплохо. И мой legacy-код теперь как бы не такой уж и legacy. А на продакшн всё встало вообще как по маслу. То есть вот буквально git pull origin production
и composer install
. И всё.
Главный вывод, который следует сделать — пишите, ребята, тесты. Не устану повторять. В следующий раз напишу что-нибудь про TDD.