步骤 23: 调整图片尺寸

在会议页面的设计中,照片被限定为不超过 200 像素宽和 150 像素高。如果上传的原始图片比这个尺寸大,那我们如何来优化图片,调整它们的大小,怎么样?

这个工作是加入评论工作流的不二之选,很可能把它加在评论验证之后,发布它之前。

我们来添加 ready 状态和 optimize 状态转移:

patch_file

  1. --- a/config/packages/workflow.yaml
  2. +++ b/config/packages/workflow.yaml
  3. @@ -16,6 +16,7 @@ framework:
  4. - potential_spam
  5. - spam
  6. - rejected
  7. + - ready
  8. - published
  9. transitions:
  10. accept:
  11. @@ -29,13 +30,16 @@ framework:
  12. to: spam
  13. publish:
  14. from: potential_spam
  15. - to: published
  16. + to: ready
  17. reject:
  18. from: potential_spam
  19. to: rejected
  20. publish_ham:
  21. from: ham
  22. - to: published
  23. + to: ready
  24. reject_ham:
  25. from: ham
  26. to: rejected
  27. + optimize:
  28. + from: ready
  29. + to: published

为这个新的工作流配置生成一个图形,用它来验证这是我们想要的流程:

  1. $ symfony console workflow:dump comment | dot -Tpng -o workflow.png

../_images/workflow-final.png

用 Imagine 来优化图片

图片优化需要用 GD (检查你本地安装的PHP是否启用了 GD 扩展)和 Imagine

  1. $ symfony composer req "imagine/imagine:^1.2"

可以用下面这个服务类来调整图片大小:

src/ImageOptimizer.php

  1. namespace App;
  2. use Imagine\Gd\Imagine;
  3. use Imagine\Image\Box;
  4. class ImageOptimizer
  5. {
  6. private const MAX_WIDTH = 200;
  7. private const MAX_HEIGHT = 150;
  8. private $imagine;
  9. public function __construct()
  10. {
  11. $this->imagine = new Imagine();
  12. }
  13. public function resize(string $filename): void
  14. {
  15. list($iwidth, $iheight) = getimagesize($filename);
  16. $ratio = $iwidth / $iheight;
  17. $width = self::MAX_WIDTH;
  18. $height = self::MAX_HEIGHT;
  19. if ($width / $height > $ratio) {
  20. $width = $height * $ratio;
  21. } else {
  22. $height = $width / $ratio;
  23. }
  24. $photo = $this->imagine->open($filename);
  25. $photo->resize(new Box($width, $height))->save($filename);
  26. }
  27. }

图片优化完成后,我们存储新的图片文件来代替原始图片。但你可能也会想要在某个地方保留原始图片。

在工作流里增加一个新步骤

修改工作流,让它可以处理新的状态:

patch_file

  1. --- a/src/MessageHandler/CommentMessageHandler.php
  2. +++ b/src/MessageHandler/CommentMessageHandler.php
  3. @@ -2,6 +2,7 @@
  4. namespace App\MessageHandler;
  5. +use App\ImageOptimizer;
  6. use App\Message\CommentMessage;
  7. use App\Repository\CommentRepository;
  8. use App\SpamChecker;
  9. @@ -21,10 +22,12 @@ class CommentMessageHandler implements MessageHandlerInterface
  10. private $bus;
  11. private $workflow;
  12. private $mailer;
  13. + private $imageOptimizer;
  14. private $adminEmail;
  15. + private $photoDir;
  16. private $logger;
  17. - public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, MailerInterface $mailer, string $adminEmail, LoggerInterface $logger = null)
  18. + public function __construct(EntityManagerInterface $entityManager, SpamChecker $spamChecker, CommentRepository $commentRepository, MessageBusInterface $bus, WorkflowInterface $commentStateMachine, MailerInterface $mailer, ImageOptimizer $imageOptimizer, string $adminEmail, string $photoDir, LoggerInterface $logger = null)
  19. {
  20. $this->entityManager = $entityManager;
  21. $this->spamChecker = $spamChecker;
  22. @@ -32,7 +35,9 @@ class CommentMessageHandler implements MessageHandlerInterface
  23. $this->bus = $bus;
  24. $this->workflow = $commentStateMachine;
  25. $this->mailer = $mailer;
  26. + $this->imageOptimizer = $imageOptimizer;
  27. $this->adminEmail = $adminEmail;
  28. + $this->photoDir = $photoDir;
  29. $this->logger = $logger;
  30. }
  31. @@ -64,6 +69,12 @@ class CommentMessageHandler implements MessageHandlerInterface
  32. ->to($this->adminEmail)
  33. ->context(['comment' => $comment])
  34. );
  35. + } elseif ($this->workflow->can($comment, 'optimize')) {
  36. + if ($comment->getPhotoFilename()) {
  37. + $this->imageOptimizer->resize($this->photoDir.'/'.$comment->getPhotoFilename());
  38. + }
  39. + $this->workflow->apply($comment, 'optimize');
  40. + $this->entityManager->flush();
  41. } elseif ($this->logger) {
  42. $this->logger->debug('Dropping comment message', ['comment' => $comment->getId(), 'state' => $comment->getState()]);
  43. }

注意 $photoDir 是自动注入的,因为在之前的步骤中我们已经在容器的 bind 配置里绑定了这个变量名:

config/packages/services.yaml

  1. services:
  2. _defaults:
  3. bind:
  4. $photoDir: "%kernel.project_dir%/public/uploads/photos"

在生产环境中存储上传的数据

我们已经在 .symfony.cloud.yaml 里为上传文件定义了一个特殊的可读写目录。但是挂载是本地的。如果我们想要让 web 容器和消息消费者的 worker 进程也可以访问同一个挂载,我们需要创建一个 文件服务

patch_file

  1. --- a/.symfony/services.yaml
  2. +++ b/.symfony/services.yaml
  3. @@ -19,3 +19,7 @@ varnish:
  4. vcl: !include
  5. type: string
  6. path: config.vcl
  7. +
  8. +files:
  9. + type: network-storage:1.0
  10. + disk: 256

把它用于照片上传目录:

patch_file

  1. --- a/.symfony.cloud.yaml
  2. +++ b/.symfony.cloud.yaml
  3. @@ -37,7 +37,7 @@ web:
  4. mounts:
  5. "/var": { source: local, source_path: var }
  6. - "/public/uploads": { source: local, source_path: uploads }
  7. + "/public/uploads": { source: service, service: files, source_path: uploads }
  8. hooks:
  9. build: |

在生产环境中,这么做就足以让该功能正常运行了。


This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.