Статьи

Glide: простое динамическое изменение размера изображения по требованию

Glide каяки изображение

Glide — это библиотека обработки изображений, построенная на основе Intervention . Его цель — облегчить обработку изображений по требованию. Это причудливый способ сказать, что он создает изображения по запросу, если они не существуют.

Например, у вас может быть большой, высококачественный портрет для вашего профиля пользователя. Но при открытии на мобильном устройстве загрузка 10 МБ образа не только расточительна, но и медленна. Вместо этого вы можете добавить медиа-запрос в ваш CSS, например, так:

@media (min-width : 400px) { .profilePic { background-image : url('/images/myprofile.jpg') ; } } @media (max-width : 400px) { .profilePic { background-image : url('/images/myprofile-320.jpg') ; } } 

Это позволит всем устройствам шириной менее 400 пикселей загружать фоновое изображение меньшего размера, что ускоряет загрузку. Но изменение размера каждого изображения, которое может понадобиться в приложении, вручную утомительно, отнимает много времени и подвержено ошибкам. Вот где Glide входит.

Glide может быть настроен для ответа на пропущенные запросы изображений (такие как несуществующий myprofile-320.jpg из приведенного выше примера) путем создания их из заранее определенного источника. Короче говоря, если запрошенное изображение не существует, но его источник существует, запрошенное изображение создается из него. Более того, изображение может быть сохранено в кэш, чтобы будущие запросы не вызывали библиотеку и не тратили драгоценные ресурсы ЦП на повторный рендеринг.

Давайте настроим это.

Если вы хотите продолжить, не стесняйтесь использовать наше приложение без фреймворка и Homestead Improved для быстрой настройки среды и примера приложения для его тестирования.

Бутстрапирование

Шаг первый — установка Glide:

 composer require league / glide 

Затем нам нужно настроить сервер Glide. В приведенном выше проекте NOFW это происходит в app/config.php где все службы настроены для будущего внедрения через PHP-DI. Вот как это делается:

 // ... 'glide' = > function ( ) { $server = League\ Glide \ ServerFactory : : create ( [ 'source' = > new League \ Flysystem \ Filesystem ( new League \ Flysystem \ Adapter \ Local ( __DIR__ . '/../assets/image' ) ) , 'cache' = > new League \ Flysystem \ Filesystem ( new League \ Flysystem \ Adapter \ Local ( __DIR__ . '/../public/static/image' ) ) , 'driver' = > 'gd' , ] ) ; return $server ; } , // ... 

Теперь мы можем при необходимости вставить (или вытащить) экземпляр glide в контроллер. Значения source и cache определяют, где находятся исходные изображения и где должны храниться сгенерированные изображения, соответственно, в то время как ключ driver указывает, что встроенное расширение манипуляции изображениями GD PHP должно использоваться для модификации изображений.

Вы должны изменить пути к изображениям, чтобы применить их к своим собственным.

Хотя они и являются основными, есть и множество других настроек — не стесняйтесь изучать их на досуге.

Маршрутизация

Далее нам нужно определить маршрут, который будет запускаться при запросе изображения, которое не существует. Приложение без фреймворка использует fastRoute , поэтому определить маршрут так же просто, как добавить следующее в app/routes.php :

 ['GET', '/static/image/{image}', ['MyControllers\Controllers\ImageController', 'renderImage']] 

То, что когда-либо {image} в маршруте оценивается, будет передано как $image в метод renderImage ImageController (о котором мы напишем далее).

обработка

Давайте ImageController сейчас:

 <?php namespace MyControllers \ Controllers ; use League \ Glide \ Server ; use Standard \ Abstracts \ Controller ; class ImageController extends Controller { /** * @Inject("glide") * @var Server */ private $glide ; public function renderImage ( $image ) { $width = $this - > getWidth ( $image ) ; $imageSource = str_replace ( '-' . $width , '' , $image ) ; $this - > glide - > outputImage ( $imageSource , [ 'w' = > $width ] ) ; } /** * @param string $imagePath * @return int */ private function getWidth ( string $imagePath ) : int { $fragments = explode ( '-' , $imagePath ) ; return ( int ) explode ( '.' , $fragments [ 1 ] ) [ 0 ] ; } } 

Экземпляр сервера Glide автоматически внедряется в контроллер с аннотацией @Inject PHP-DI. Если это сбивает с толку или интересно, пожалуйста, смотрите документацию . Затем renderImage получает путь к изображению, вызывает внутренний метод getWidth который извлекает ширину из имени изображения и, наконец, изменяет размеры и отображает изображение.

Обратите внимание, что мы не используем параметры $ _GET для передачи параметров изменения размера в соответствии с документами Glide — это потому, что я предпочитаю, чтобы URL-адреса моего изображения выглядели как реальные URL-адреса для статических ресурсов, чтобы впоследствии их можно было использовать в качестве реальных статических ресурсов.

Это работает, но проницательные читатели могут заметить проблему …

обеспечение

Как таковое, приложение уязвимо для атак массового редактирования изображений. Кто-то может написать цикл из тысячи изображений разного размера, добавить их к каждому изображению, которое они могут получить с вашего сайта, и заставить ваш сервер конвертировать в произвольные и бессмысленные размеры 24/7. Документы Glide рекомендуют подписывать URL-адреса, чтобы проходить только авторизованные URL-адреса, но это сложно и очень трудно достичь при работе с внешними таблицами стилей. Мы должны были бы предварительно обработать наш CSS и заново создать все URL-адреса изображений внутри него — сложная процедура, еще более усложненная из-за того, что у нас, вероятно, есть и другие, не связанные с этим этапы компиляции внешнего интерфейса (см. Здесь ) ,

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

 <?php namespace MyControllers \ Controllers ; use League \ Glide \ Server ; use Standard \ Abstracts \ Controller ; class ImageController extends Controller { /** * @Inject("glide") * @var Server */ private $glide ; private $allowedWidths = [ 320 , 768 , 992 , 1280 , 1920 , ] ; public function renderImage ( $image ) { $width = $this - > getWidth ( $image ) ; $imageSource = str_replace ( '-' . $width , '' , $image ) ; $this - > glide - > outputImage ( $imageSource , [ 'w' = > $width ] ) ; } /** * @param string $imagePath * @return int */ private function getWidth ( string $imagePath ) : int { $fragments = explode ( '-' , $imagePath ) ; if ( count ( $fragments ) < 2 ) { header ( ( $_SERVER [ 'SERVER_PROTOCOL' ] ? ? 'HTTP/1.0' ) . ' 500 ' . 'Server Error ImgErr00' ) ; die ( "Nope! Image fragments missing." ) ; } $width = ( int ) explode ( '.' , $fragments [ 1 ] ) [ 0 ] ; if ( ! in_array ( $width , $this - > allowedWidths ) ) { header ( ( $_SERVER [ 'SERVER_PROTOCOL' ] ? ? 'HTTP/1.0' ) . ' 500 ' . 'Server Error ImgErr01' ) ; die ( "Nope! Disallowed width." ) ; } return $width ; } } 

Метод getWidth был расширен за getWidth некоторых примитивных проверок — первая для того, чтобы убедиться, что изображение имеет расширение размера (поэтому больше не нужно генерировать изображения оригинального размера! — это также экономит полосу пропускания) — и вторая, чтобы убедиться, что размер разрешен, сравнив его со $allowedWidths массива $allowedWidths в контроллере.

Естественно, это может быть расширено за счет высоты, режима изменения размера и всех других вариантов Glide.

Примечание о сохранении с Glide

Наши изображения теперь безопасно изменяются и отображаются.

К сожалению, Glide — это «только PHP», так как невозможно, чтобы сервер указывал на изображение непосредственно после его генерации. Это потому, что сгенерированные изображения всегда выглядят так:

 - images/ - myprofile.jpg/ lhgn3q489uncdue7b9qdny98xq3 

… А не запрошенный myprofile-320.jpg . Имя изображения фактически будет папкой, а изображение будет анонимным файлом в этой папке. Это означает, что для того, чтобы вернуть изображение myprofile-320.jpg , PHP всегда должен будет вызывать Glide, который всегда должен проверять, существует ли изображение, а затем обслуживать его или генерировать, если его нет.

Как правило, это не является нарушителем соглашения из-за изображений, которые обслуживаются с очень длительными заголовками:

  header('Cache-Control:'.'max-age=31536000, public'); header('Expires:'.date_create('+1 years')->format('D, d MYH:i:s').' GMT'); 

… и тот факт, что один PHP-запрос на обслуживание изображения перед этим, опять же, с использованием этих заголовков с очень длинным сроком действия, не так уж много накладных расходов, но это то, что вы могли бы иметь в виду при планировании следующего приложения с большим трафиком , Ситуация может быть улучшена, если поставить перед собой зону притяжения и даже более мощный сервер кэширования, такой как Varnish , но это история для другого дня.

С другой стороны …

Чтобы полностью обойти цикл PHP в последующих рендерах, вы можете сделать что-то вроде этого:

 public function renderImage ( $image ) { $width = $this - > getWidth ( $image ) ; $imageSource = str_replace ( '-' . $width , '' , $image ) ; $imagePath = $this - > glide - > makeImage ( $imageSource , [ 'w' = > $width ] ) ; $imageBase = $this - > glide - > getCache ( ) - > read ( $imagePath ) ; $this - > glide - > getCache ( ) - > put ( $image , $imageBase ) ; $this - > glide - > outputImage ( $imageSource , [ 'w' = > $width ] ) ; } 

makeImage создает и сохраняет изображение, но возвращает только его путь. Затем мы читаем содержимое изображения с указанным путем из кеша. Наконец, мы повторно сохраняем изображение под первоначально запрошенным именем, а затем выводим его, как делали раньше. Таким образом, только этот первый вызов будет дорогостоящим (несколько операций ввода-вывода и преобразование), и все будущие вызовы этого URL-адреса изображения будут идти прямо к изображению — полностью обходя PHP. Фактически, если вы выключите PHP с помощью sudo service php-fpm stop , изображение все равно будет загружаться.

Вывод

В этом руководстве мы рассмотрели простоту использования Glide , пакета для работы с изображениями, который может генерировать измененные изображения по требованию, избавляя вас от необходимости создавать их перед развертыванием приложения.

Вы используете Glide? Альтернативный подход возможно? Дайте нам знать!