68publishers / image-storage
Extension for 68publishers/file-storage that can generate images on-the-fly and more!
Fund package maintenance!
www.buymeacoffee.com/68publishers
Installs: 3 834
Dependents: 2
Suggesters: 1
Security: 0
Stars: 1
Watchers: 3
Forks: 0
Open Issues: 1
Requires
- php: ^8.1
- ext-json: *
- 68publishers/file-storage: ^1.1.1
- intervention/image: ^2.7.2
Requires (Dev)
- ext-imagick: *
- friendsofphp/php-cs-fixer: ^3.13
- kubawerlos/php-cs-fixer-custom-fixers: ^3.21
- latte/latte: ^3.0
- league/flysystem-aws-s3-v3: ^3.0
- league/flysystem-memory: ^3.10
- mockery/mockery: ^1.5
- nette/application: ^3.1.8
- nette/bootstrap: ^3.1
- nette/di: ^3.0.10
- nette/http: ^3.2.1
- nette/tester: ^2.4.3
- phpstan/phpstan: ^1.9
- phpstan/phpstan-nette: ^1.1
- roave/security-advisories: dev-latest
- symfony/console: ^5.0 | ^6.0
- yosymfony/toml: ^1.0.4
Suggests
- latte/latte: For usage with Latte templates.
- league/flysystem-aws-s3-v3: For generation of SAM configuration files when you're using AWS S3 and 68publishers/image-storage-lambda.
- nette/di: For an integration with Nette Framework.
- symfony/console: If you want to use a console commands.
- yosymfony/toml: For generation of SAM configuration files when you're using AWS S3 and 68publishers/image-storage-lambda.
Conflicts
- 68publishers/doctrine-bridge: <1.0.0
- latte/latte: <3.0
- nette/di: <3.0.10
- nette/http: <3.2.1
- nette/schema: <1.1
- symfony/console: <5.0
- yosymfony/toml: <1.0.4
- dev-master
- v1.3.0
- v1.2.1
- v1.2.0
- v1.1.0
- v1.0.2
- v1.0.1
- v1.0.0
- v0.5.8
- v0.5.7
- v0.5.6
- v0.5.5
- v0.5.4
- v0.5.3
- v0.5.2
- v0.5.1
- v0.5
- v0.4.10
- v0.4.9
- v0.4.8
- v0.4.7
- v0.4.6
- v0.4.5
- v0.4.4
- v0.4.3
- v0.4.2
- v0.4.1
- v0.4
- v0.3.1
- v0.3
- v0.2.3
- v0.2.2
- v0.2.1
- v0.2
- v0.1.2
- v0.1.1
- v0.1
- dev-dependabot/composer/intervention/image-tw-3.4.0
- dev-feature/no-image-latte-function
This package is auto-updated.
Last update: 2024-10-27 03:52:31 UTC
README
🌆 Extension for 68publishers/file-storage that can generate images on-the-fly and more!
Based on thephpleague/flysystem and intervention/image
Installation
The best way to install 68publishers/image-storage is using Composer:
$ composer require 68publishers/image-storage
Integration into Nette Framework
Firstly, please read a documentation of 68publishers/file-storage.
File storage configuration example
Each image-storage is based on file-storage. so firstly we need to register our storage under the file-storage extension. Here is an example configuration:
68publishers.file_storage: storages: local: config: base_path: /images signature_key: my-arbitrary-private-key allowed_pixel_density: [ 1, 2, 3 ] allowed_resolutions: [ 50x50, 200x200, 300x300, 200x, x200 ] allowed_qualities: [ 50, 80, 100 ] encode_quality: 90 cache_max_age: 31536000 filesystem: adapter: League\Flysystem\Local\LocalFilesystemAdapter(%wwwDir%/images) assets: assets/image/noimage.png: noimage/default.png # copy the default no-image assets/image/noimage_user.png: noimage/user.png # copy the default no-image for users
Storage config options
Image storage configuration example
Now we can register the ImageStorageExtension
and define the local
image-storage:
extensions: 68publishers.image_storage: SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\ImageStorageExtension 68publishers.image_storage: driver: gd # "gd" or "imagick" or "68publishers.imagick", the default is "gd" storages: local: source_filesystem: adapter: League\Flysystem\Local\LocalFilesystemAdapter(%appDir%/../private-data/images) config: [] # an optional config for source filesystem adapter server: local # "local" or "external", the default is "local" route: yes # registers automatically ImageServer presenter into your Router. The option can be applied only if the "server" option is set to "local" and the option "base_path" is set in the FileStorage config no_image: default: noimage/default.png user: noimage/user.png no_image_patterns: user: '^user_avatar\/' # the noimage "user" will be used for missing files with paths that matches this regex presets: my_preset: w: 150 ar: '2x1.5'
Animated GIFs
Animated GIFs are not supported by intervention/image but this package comes with a custom imagick
driver that supports it.
The driver is used when you pass a value 68publishers.imagick
into a driver
option.
Basic usage
Basic usage is similar to usage of the file-storage
.
Persisting files
Files persisting is almost the same as persisting in the file-storage
but source images are stored without a file extension.
use SixtyEightPublishers\ImageStorage\ImageStorageInterface; /** @var ImageStorageInterface $storage */ # Create resource from local file or url: $resource = $storage->createResourceFromFile( $storage->createPathInfo('test/my-image.jpeg'), __DIR__ . '/path/to/my-image.jpeg' ); $storage->save($resource); # Create resource from a file that is stored in storage: $resource = $storage->createResource( $storage->createPathInfo('test/my-image') ); # Copy to the new location $storage->save($resource->withPathInfo( $storage->createPathInfo('test/my-image-2') ));
Check a file existence
use SixtyEightPublishers\ImageStorage\ImageStorageInterface; /** @var ImageStorageInterface $storage */ $pathInfo = $storage->createPathInfo('test/my-image'); if ($storage->exists($pathInfo)) { echo 'source image exists!'; } if ($storage->exists($pathInfo->withModifiers(['w' => 150]))) { echo 'cached image with width 150 in JPEG (default) format exists!'; } if ($storage->exists($pathInfo->withModifiers(['w' => 150])->withExtension('webp'))) { echo 'cached image with width 150 in WEBP format exists!'; }
Deleting files
use SixtyEightPublishers\ImageStorage\ImageStorageInterface; use SixtyEightPublishers\ImageStorage\Persistence\ImagePersisterInterface; /** @var ImageStorageInterface $storage */ # delete all cached images only: $storage->delete($storage->createPathInfo('test/my-image'), [ ImagePersisterInterface::OPTION_DELETE_CACHE_ONLY => TRUE, ]); # delete cached images and source image: $storage->delete($storage->createPathInfo('test/my-image')); # delete only cached image with 200px width in PNG format $storage->delete($storage->createPathInfo('test/my-image.png')->withModifiers(['w' => 200]));
Create links to images
An original images are not accessible. If you want to access an original image you must request it with a modifier ['original' => TRUE]
.
use SixtyEightPublishers\ImageStorage\ImageStorageInterface; /** @var ImageStorageInterface $storage */ $pathInfo = $storage->createPathInfo('test/my-image.png') ->withModifiers(['original' => TRUE]) ->withVersion(time()); # /images/test/original/my-image.png?_v=1611837352 echo $storage->link($pathInfo); # /images/test/original/my-image.webp?_v=1611837352 echo $storage->link($pathInfo->withExtension('webp')); # /images/test/ar:2x1,w:200/my-image.webp?_v=1611837352&_s={GENERATED_SIGNATURE_TOKEN} echo $storage->link($pathInfo->withExtension('webp')->withModifiers(['w' => 200, 'ar' => '2x1'])); # you can also wrap PathInfo to FileInfo object: $fileInfo = $storage->createFileInfo($pathInfo); # /images/test/original/my-image.png?_v=1611837352 echo $fileInfo->link(); # /images/test/original/my-image.webp?_v=1611837352 echo $fileInfo->withExtension('webp')->link(); # /images/test/ar:2x1,w:200/my-image.webp?_v=1611837352&_s={GENERATED_SIGNATURE_TOKEN} echo $fileInfo->withExtension('webp')->withModifiers(['w' => 200, 'ar' => '2x1'])->link();
The HTML attribute srcset
can be also generated:
use SixtyEightPublishers\ImageStorage\ImageStorageInterface; use SixtyEightPublishers\ImageStorage\Responsive\Descriptor\XDescriptor; use SixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor; /** @var ImageStorageInterface $storage */ $pathInfo = $storage->createPathInfo('test/my-image.png') ->withModifiers(['w' => 200, 'ar' => '2x1']) ->withVersion(time()); /* /images/test/ar:2x1,pd:1,w:200/my-image.png?_v=1611837352&_s={TOKEN} , /images/test/ar:2x1,pd:2,w:200/my-image.png?_v=1611837352&_s={TOKEN} 2.0x, /images/test/ar:2x1,pd:3,w:200/my-image.png?_v=1611837352&_s={TOKEN} 3.0x */ echo $storage->srcSet($pathInfo, new XDescriptor(1, 2, 3)); /* /images/test/ar:2x1,w:200/my-image.png?_v=1611837352&_s={TOKEN} 200w, /images/test/ar:2x1,w:400/my-image.png?_v=1611837352&_s={TOKEN} 400w, /images/test/ar:2x1,w:600/my-image.png?_v=1611837352&_s={TOKEN} 600w, /images/test/ar:2x1,w:800/my-image.png?_v=1611837352&_s={TOKEN} 800w */ echo $storage->srcSet($pathInfo, new WDescriptor(200, 400, 600, 800)); # you can also wrap PathInfo to FileInfo object: $fileInfo = $storage->createFileInfo($pathInfo); echo $fileInfo->srcSet(new XDescriptor(1, 2, 3)); echo $fileInfo->srcSet(new WDescriptor(200, 400, 600, 800));
Usage with Latte
extensions: 68publishers.image_storage.latte: SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\ImageStorageLatteExtension
The extension adds these functions into the Latte:
w_descriptor(...)
- a shortcut fornew SixtyEightPublishers\ImageStorage\Responsive\Descriptor\XDescriptor(...)
x_descriptor(...)
- a shortcut fornew SixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor(...)
w_descriptor_range(int $min, int $max, int $step)
- a shortcut forSixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor::fromRange($min, $max, $step)
no_image(?string $noImageName = NULL, ?string $storageName = NULL)
- creates a FilInfo object that contains path to no-image file
Basic usage:
{varType SixtyEightPublishers\ImageStorage\FileInfoInterface $fileInfo} {* Note: method FileInfo::__toString() calls ::link() internally *} <img src="{$fileInfo->link()}" alt=""> <img srcset="{$fileInfo->srcSet(x_descriptor(1, 2, 3))}" src="{$fileInfo}" alt=""> {* Create FileInfo from string *} {var $fileInfo = file_info('test/my-image.png')->withModifiers(['o' => 90, 'ar' => '2x1'])} <img srcset="{$fileInfo->srcSet(w_descriptor(400, 800, 1200))}" src="{$fileInfo}" alt=""> {* Create default NoImage if the variable doesn't exists *} {var $fileInfo = ($fileInfo ?? no_image())->withModifiers(['o' => 90, 'ar' => '2x1'])} <img srcset="{$fileInfo->srcSet(w_descriptor(400, 800, 1200))}" src="{$fileInfo}" alt="">
An advanced example with a tag <picture>
:
{var $large = file_info('test/my-image.jpeg')->withModifiers([w => 1172, ar => '1x0.29'])} {var $medium = $large->withModifiers([w => 768, ar => '1x0.59'])} <picture> <source srcset="{$large->withExt('webp')->srcSet(w_descriptor_range(768, 1172 * 3, 200))}" media="(min-width: 768px)" sizes="(min-width: 1188px) calc(1188px - 2 * 0.5rem), (min-width: 992px) calc(100vw - 2 * 0.5rem), calc(100vw - 2 * 1.5rem)" type="image/webp"> <source srcset="{$large->srcSet(w_descriptor_range(768, 1172 * 3, 200))}" media="(min-width: 768px)" sizes="(min-width: 1188px) calc(1188px - 2 * 0.5rem), (min-width: 992px) calc(100vw - 2 * 0.5rem), calc(100vw - 2 * 1.5rem)"> <source srcset="{$medium->withExt('webp')->srcSet(w_descriptor_range(320, 768 * 3, 200))}" sizes="(min-width: 576px) calc(100vw - 2 * 1.5rem), calc(100vw - 2 * 0.5rem)" type="image/webp"> <source srcset="{$medium->srcSet(w_descriptor_range(320, 768 * 3, 200))}" sizes="(min-width: 576px) calc(100vw - 2 * 1.5rem), calc(100vw - 2 * 0.5rem)"> <img src="{$large}" alt=""> </picture>
Symfony Console commands
The image-storage extends a command file-storage:clean
with an option cache-only
so the command now looks like this:
$ bin/console file-storage:clean [<storage>] [--namespace <value>] [--cache-only]
Supported image formats and modifiers
Image formats
- JPEG -
.jpeg
or.jpg
- Progressive JPEG -
.pjpg
- PNG -
.png
- GIF -
.gif
- WEBP -
.webp
- AVIF -
.avif
Modifiers
Supported fits
contain
- Preserving aspect ratio, resize the image to be as large as possible while ensuring its dimensions are less than or equal to both those specified.stretch
- Ignore the aspect ratio of the input and stretch to both provided dimensions.fill
- Preserving aspect ratio, contain within both provided dimensions using "letterboxing" where necessary.crop-*
- Preserving aspect ratio, ensure the image covers both provided dimensions by cropping to fit.crop-center
crop-left
crop-right
crop-top
crop-top-left
crop-top-right
crop-bottom
crop-bottom-left
crop-bottom-right
Image server
Local image server
The default image server for each storage is local
. That means your application will handle requests and generate, store and serve modified images.
The extension automatically registers ImageStoragePresenter and Routes for local storages if the route: true
option is set for the storage.
If you have this setting disabled, you must register the Presenter yourself.
Now you must modify the configuration of a web server. For example, if the webserver is Apache then modify a file .htaccess
that is located in your www directory.
# locale images RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d RewriteRule ^(images\/)(.+) index.php [L]
The Application will be called only if a static file has not yet been generated. Otherwise, the server will serve the static file.
External image server: an integration with AWS S3 and image-storage-lambda
The image storage can be integrated with the Amazon S3 object storage and the package 68publishers/image-storage-lambda. So your image storage can be completely serverless!
Of course, you can deploy the image-storage-lambda
application manually and also synchronize options from the image-storage
with the image-storage-lambda
manually.
At least you can follow these simple steps for a partial integration:
- Create a deployment bucket on the S3
When you deploy the AWS SAM application in guide mode (sam deploy --guided
) the deployment bucket will be created automatically. But the application will be built in a non-guided mode so we must create the bucket manually.
If you don't know how to create an S3 bucket please follow the Amazon documentation. We recommend to enable versioning on this bucket.
- Required packages
league/flysystem-aws-s3-v3
(the S3 adapter for Flysystem) andyosymfony/toml
(suggested by this package) in your application
$ composer require league/flysystem-aws-s3-v3 yosymfony/toml
- Configure the image storage with the S3 filesystem (an example with a minimal configuration):
services: s3_client: class: Aws\S3\S3Client([... your S3 config ...]) autowired: no 68publishers.file_storage: storages: s3_images: config: # configure what you want but omit the `host` option for now filesystem: adapter: League\Flysystem\AwsS3V3\AwsS3V3Adapter(@s3_client, my-awesome-cache-bucket) # the bucket doesn't exists at this point # if you have your own no-images: assets: %assetsDir%/noimage: noimage 68publishers.image_storage: storages: s3_images: source_filesystem: adapter: League\Flysystem\AwsS3V3\AwsS3V3Adapter(@s3_client, my-awesome-source-bucket) # the bucket doesn't exists at this point server: external # if you have your own no-images: no_image: default: noimage/default.png user: noimage/user.png no_image_patterns: user: '^user_avatar\/'
- Register and configure the compiler extension
ImageStorageLambdaExtension
extensions: 68publishers.image_storage.lambda: SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\ImageStorageLambdaExtension 68publishers.image_storage.lambda: output_dir: %appDir%/config/image-storage-lambda # the default path stacks: s3_images: s3_bucket: {NAME OF YOUR DEPLOYMENT BUCKET FROM THE STEP 1} region: eu-central-1 # optional settings: stack_name: my-awesome-image-storage # the storage name is used by default version: 2.0 # default is 1.0 s3_prefix: custom-prefix # the stack_name is used by default confirm_changeset: yes # must be changeset manually confirmed during deploy? the default value is false capabilities: CAPABILITY_IAM # default, CAPABILITY_IAM or CAPABILITY_NAMED_IAM # optional, automatically detected from AwsS3V3Adapter by default source_bucket_name: source-bucket-name cache_bucket_name: cache-bucket-name
- Generate configuration for the
image-storage-lambda
$ php bin/console image-storage:lambda:dump-config
The configuration file will be placed by default in a directory app/config/image-storage-lambda/my-awesome-image-storage/samconfig.toml
. Keep this file versioned in the Git.
- Download
image-storage-lambda
, build and deploy!
Firstly setup your local environment by requirements defined here. Then download the package outside your project.
$ git clone https://github.com/68publishers/image-storage-lambda.git image-storage-lambda
$ cd ./image-storage-lambda
Unfortunately SAM CLI doesn't allow you to define a path to your samconfig.toml
file (related issue aws/aws-sam-cli#1615) at this moment.
So you must copy the config to the root of the image-storage-lambda
application.
And then you can build and deploy the application!
$ cp ../my-project/app/config/image-storage-lambda/my-awesome-image-storage/samconfig.toml samconfig.toml $ sam build $ sam deploy
- Set the CloudFront URL as a host in the image storage config
The URL of your CloudFront distribution is listed in Outputs after a successful deployment. More information are here.
# ... 68publishers.image_storage: storages: s3_images: config: host: {CLOUDFRONT URL} # ...
Contributing
Before opening a pull request, please check your changes using the following commands
$ make init # to pull and start all docker images
$ make cs.check
$ make stan
$ make tests.all