68publishers/image-storage

68publishers/file-storage 扩展,可以即时生成图片等更多功能!

v1.3.0 2024-07-27 03:14 UTC

README

🌆 68publishers/file-storage 扩展,可以即时生成图片等更多功能!

基于 thephpleague/flysystemintervention/image

Checks Coverage Status Total Downloads Latest Version PHP Version

安装

安装 68publishers/image-storage 的最佳方式是使用 Composer

$ composer require 68publishers/image-storage

Nette 框架集成

首先,请阅读 68publishers/file-storage 的文档。

文件存储配置示例

每个 image-storage 都基于文件存储。因此,我们首先需要在文件存储扩展下注册我们的存储。以下是一个示例配置

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

存储配置选项

图像存储配置示例

现在我们可以注册 ImageStorageExtension 并定义 local 图像存储

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'

动画 GIF

Intervention/image 不支持动画 GIF,但此包附带一个自定义的 imagick 驱动程序,支持它。当您将值 68publishers.imagick 传递给 driver 选项时,将使用该驱动程序。

基本用法

基本用法与 file-storage 的用法类似。

持久化文件

文件持久化几乎与 file-storage 中的持久化相同,但源图像存储时不带文件扩展名。

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')
));

检查文件是否存在

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!';
}

删除文件

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]));

创建指向图像的链接

原始图像不可访问。如果您想访问原始图像,您必须使用修饰符 ['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();

还可以生成 srcset HTML 属性

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));

与 Latte 的用法

extensions:
    68publishers.image_storage.latte: SixtyEightPublishers\ImageStorage\Bridge\Nette\DI\ImageStorageLatteExtension

该扩展向 Latte 添加了这些功能

  • w_descriptor(...) - new SixtyEightPublishers\ImageStorage\Responsive\Descriptor\XDescriptor(...) 的快捷方式
  • x_descriptor(...) - new SixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor(...) 的快捷方式
  • w_descriptor_range(int $min, int $max, int $step) - SixtyEightPublishers\ImageStorage\Responsive\Descriptor\WDescriptor::fromRange($min, $max, $step) 的快捷方式
  • no_image(?string $noImageName = NULL, ?string $storageName = NULL) - 创建一个包含无图像文件路径的 FilInfo 对象

基本用法

{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="">

使用 <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 控制台命令

image-storage 扩展了命令 file-storage:clean 并添加了 cache-only 选项,因此命令现在看起来像这样

$ bin/console file-storage:clean [<storage>] [--namespace <value>] [--cache-only]

支持的图像格式和修饰符

图像格式

  • JPEG - .jpeg.jpg
  • 渐进式 JPEG - .pjpg
  • PNG - .png
  • GIF - .gif
  • WEBP - .webp
  • AVIF - .avif

修饰符

支持的 fits

  • contain - 保持宽高比,将图像调整到尽可能大,同时确保其尺寸小于或等于指定的尺寸。
  • stretch - 忽略输入的宽高比,拉伸至提供的两个维度。
  • fill - 保留宽高比,使用“信封”方式在必要时包含在提供的两个维度内。
  • crop-* - 保留宽高比,通过裁剪确保图片覆盖提供的两个维度。
    • crop-center
    • crop-left
    • crop-right
    • crop-top
    • crop-top-left
    • crop-top-right
    • crop-bottom
    • crop-bottom-left
    • crop-bottom-right

图像服务器

本地图像服务器

每个存储的默认图像服务器为 local。这意味着您的应用程序将处理请求,生成、存储和提供修改后的图像。如果为存储设置了 route: true 选项,则扩展会自动注册 ImageStoragePresenter 和路由。如果您禁用了此设置,则必须自己注册该演示者。

现在您必须修改Web服务器的配置。例如,如果Web服务器是Apache,则修改位于您www目录中的 .htaccess 文件。

# locale images
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(images\/)(.+) index.php [L]

只有在尚未生成静态文件时,应用程序才会被调用。否则,服务器将提供静态文件。

外部图像服务器:与AWS S3和image-storage-lambda的集成

图像存储可以与Amazon S3对象存储和包 68publishers/image-storage-lambda 集成。因此,您的图像存储可以完全无服务器!当然,您可以手动部署 image-storage-lambda 应用程序,并且还可以手动从 image-storage 同步选项到 image-storage-lambda

至少,您可以根据以下简单步骤进行部分集成

  1. 在S3上创建一个部署存储桶

在引导模式下部署AWS SAM应用程序(sam deploy --guided),将自动创建部署存储桶。但是应用程序将以非引导模式构建,因此我们必须手动创建存储桶。如果您不知道如何创建S3存储桶,请按照 Amazon文档 进行操作。我们建议在此存储桶上启用版本控制。

  1. 在您的应用程序中需要以下包 league/flysystem-aws-s3-v3(Flysystem的S3适配器)和 yosymfony/toml(由该包建议)
$ composer require league/flysystem-aws-s3-v3 yosymfony/toml
  1. 使用S3文件系统配置图像存储(示例:使用最小配置)
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\/'
  1. 注册和配置编译扩展 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
  1. image-storage-lambda 生成配置
$ php bin/console image-storage:lambda:dump-config

默认情况下,配置文件将放置在目录 app/config/image-storage-lambda/my-awesome-image-storage/samconfig.toml 中。请使用Git进行版本控制。

  1. 下载 image-storage-lambda,构建并部署!

首先根据此处定义的要求设置您的本地环境。然后在外部项目之外下载该包。

$ git clone https://github.com/68publishers/image-storage-lambda.git image-storage-lambda
$ cd ./image-storage-lambda

遗憾的是,SAM CLI目前不允许您定义指向 samconfig.toml 文件的路径(相关问题 aws/aws-sam-cli#1615)。因此,您必须将配置复制到 image-storage-lambda 应用程序的根目录。然后您可以构建并部署应用程序!

$ cp ../my-project/app/config/image-storage-lambda/my-awesome-image-storage/samconfig.toml samconfig.toml
$ sam build
$ sam deploy
  1. 在图像存储配置中将CloudFront URL设置为主机

在成功部署后,您的CloudFront分发URL将列在输出中。更多信息请在此处

# ...
68publishers.image_storage:
    storages:
        s3_images:
            config:
                host: {CLOUDFRONT URL}
# ...

贡献

在打开拉取请求之前,请使用以下命令检查您的更改

$ make init # to pull and start all docker images

$ make cs.check
$ make stan
$ make tests.all