Это болючая задача которую мне приходится решать на каждом проекте.
"Готовое" решение для симфони - liip imagine bundle, но пока все конфиги изучишь - неделя пройдет.
Я обычно подключаю вручную:
1. свой класс отвечающий за масштабирование + обрезку + смену формата на webp
2. 2 маршрута API - один называю blueprint, второй image. Блюпринт выдает JSON-ом все возможные ссылки с предустановленными параметрами, а image - отдает картинку
3. и оборачиваю это в phpleague flysystem, чтобы при случае переключиться на амазоновское, дропбоксовое или свое собственное хранилище, а изначально использовать родную файловую систему
4. для обрезки подключаю gumlet, он может не идеален, но немного проще чем использовать встроенный gd или устанавливаемый imagick, где придется учить настройки, гумлет все-таки немного преднастроен по качеству и сжатию (под капотом все равно gd или imagick используется, просто обертка)
Вот пример на тестовом сайте:
/api/v1/uploads/image/2024/02/000029_1605538227_415880_big1.jpg?presets=group.all
/uploads/image/size-original/2024/02/000029_1605538227_415880_big1.jpg
/uploads/image/size-jpg2webp/2024/02/000029_1605538227_415880_big1.webp
/uploads/image/size-jpg2webp-100-100/2024/02/000029_1605538227_415880_big1.webp
Но там много моментов, которые так просто не описать.
1. Во первых сначала масштабирование. Тут придется думать по длинной стороне, короткой стороне, или по aspect ratio.
2. Потом обрезка, если идеально привести не удалось. Отдельно придется подумать где точка центра - вверху-центре или в центре-центре.
3. Потом после обрезки скорее всего ты получишь raw content изображения, чтобы его сохранить в файл нужно его поймать ob_start()/ob_get_clean(), и обеспечить чтобы входящая ссылка всегда приводила к созданию картинки с тем же путем, и если она уже есть - не тратила время а отдавала готовую. Опять же - если сделать "произвольный размер" - то твой сервер очень легко нагнуть, сделав мини-ддос, чтобы он сгенерировал все возможные сочетания размеров от 0 до 2000 по X/Y, получится 4 миллиона картинок. Поэтому так или иначе надо предусматривать ключ или пароль, позволяющий это делать, чтобы снаружи не долбили ерундой.
4. Потом форматы. Родное изображение было в .jpg, а итоговый хочется webp. Получается что итоговое изображение будет либо с двумя расширениями (и пхпшная функция pathinfo еще заставит попотеть), либо оригинальное разрешение будет получено из jpg2webp, а итоговое можно указать любое, но работать должно только если оно webp.
В общем, гемор что надо тебя ждет. Но реализуемо.