vendor/contao/core-bundle/src/Image/Studio/ImageResult.php line 146

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. /*
  4. * This file is part of Contao.
  5. *
  6. * (c) Leo Feyer
  7. *
  8. * @license LGPL-3.0-or-later
  9. */
  10. namespace Contao\CoreBundle\Image\Studio;
  11. use Contao\CoreBundle\Image\ImageFactoryInterface;
  12. use Contao\CoreBundle\Image\PictureFactory;
  13. use Contao\CoreBundle\Image\PictureFactoryInterface;
  14. use Contao\Image\DeferredImageInterface;
  15. use Contao\Image\DeferredResizerInterface;
  16. use Contao\Image\Image;
  17. use Contao\Image\ImageDimensions;
  18. use Contao\Image\ImageInterface;
  19. use Contao\Image\PictureConfiguration;
  20. use Contao\Image\PictureInterface;
  21. use Contao\Image\ResizeOptions;
  22. use Psr\Container\ContainerInterface;
  23. use Symfony\Component\Filesystem\Path;
  24. class ImageResult
  25. {
  26. private ContainerInterface $locator;
  27. private ?ResizeOptions $resizeOptions;
  28. private string $projectDir;
  29. /**
  30. * @var string|ImageInterface
  31. */
  32. private $filePathOrImageInterface;
  33. /**
  34. * @var int|string|array|PictureConfiguration|null
  35. */
  36. private $sizeConfiguration;
  37. /**
  38. * Cached picture.
  39. */
  40. private ?PictureInterface $picture = null;
  41. /**
  42. * Cached image dimensions.
  43. */
  44. private ?ImageDimensions $originalDimensions = null;
  45. /**
  46. * @param string|ImageInterface $filePathOrImage
  47. * @param array|PictureConfiguration|int|string|null $sizeConfiguration
  48. *
  49. * @internal Use the Contao\CoreBundle\Image\Studio\Studio factory to get an instance of this class
  50. */
  51. public function __construct(ContainerInterface $locator, string $projectDir, $filePathOrImage, $sizeConfiguration = null, ?ResizeOptions $resizeOptions = null)
  52. {
  53. $this->locator = $locator;
  54. $this->projectDir = $projectDir;
  55. $this->filePathOrImageInterface = $filePathOrImage;
  56. $this->sizeConfiguration = $sizeConfiguration;
  57. $this->resizeOptions = $resizeOptions;
  58. }
  59. /**
  60. * Creates a picture with the defined size configuration.
  61. */
  62. public function getPicture(): PictureInterface
  63. {
  64. if (null !== $this->picture) {
  65. return $this->picture;
  66. }
  67. // Unlike the Contao\Image\PictureFactory the PictureFactoryInterface
  68. // does not know about ResizeOptions. We therefore check if the third
  69. // argument of the "create" method allows setting them.
  70. $canHandleResizeOptions = static function (PictureFactoryInterface $factory): bool {
  71. if ($factory instanceof PictureFactory) {
  72. return true;
  73. }
  74. $createParameters = (new \ReflectionClass($factory))
  75. ->getMethod('create')
  76. ->getParameters()
  77. ;
  78. if (!isset($createParameters[2])) {
  79. return false;
  80. }
  81. $type = $createParameters[2]->getType();
  82. return $type instanceof \ReflectionNamedType && ResizeOptions::class === $type->getName();
  83. };
  84. $factory = $this->pictureFactory();
  85. $arguments = [$this->filePathOrImageInterface, $this->sizeConfiguration];
  86. if (null !== $this->resizeOptions && $canHandleResizeOptions($factory)) {
  87. $arguments[] = $this->resizeOptions;
  88. }
  89. return $this->picture = $this->pictureFactory()->create(...$arguments);
  90. }
  91. /**
  92. * Returns the "sources" part of the current picture.
  93. */
  94. public function getSources(): array
  95. {
  96. return $this->getPicture()->getSources($this->projectDir, $this->staticUrl());
  97. }
  98. /**
  99. * Returns the "img" part of the current picture.
  100. */
  101. public function getImg(): array
  102. {
  103. return $this->getPicture()->getImg($this->projectDir, $this->staticUrl());
  104. }
  105. /**
  106. * Returns the "src" attribute of the image. This will return a URL by
  107. * default. Set $asPath to true to get a relative file path instead.
  108. */
  109. public function getImageSrc(bool $asPath = false): string
  110. {
  111. if ($asPath) {
  112. /** @var Image $image */
  113. $image = $this->getPicture()->getImg()['src'];
  114. return Path::makeRelative($image->getPath(), $this->projectDir);
  115. }
  116. return $this->getImg()['src'] ?? '';
  117. }
  118. /**
  119. * Returns the image dimensions of the base resource.
  120. */
  121. public function getOriginalDimensions(): ImageDimensions
  122. {
  123. if (null !== $this->originalDimensions) {
  124. return $this->originalDimensions;
  125. }
  126. if ($this->filePathOrImageInterface instanceof ImageInterface) {
  127. return $this->originalDimensions = $this->filePathOrImageInterface->getDimensions();
  128. }
  129. return $this->originalDimensions = $this
  130. ->imageFactory()
  131. ->create($this->filePathOrImageInterface)
  132. ->getDimensions()
  133. ;
  134. }
  135. /**
  136. * Returns the file path of the base resource.
  137. *
  138. * Set $absolute to true to return an absolute path instead of a path
  139. * relative to the project dir.
  140. */
  141. public function getFilePath(bool $absolute = false): string
  142. {
  143. $path = $this->filePathOrImageInterface instanceof ImageInterface
  144. ? $this->filePathOrImageInterface->getPath()
  145. : $this->filePathOrImageInterface;
  146. return $absolute ? $path : Path::makeRelative($path, $this->projectDir);
  147. }
  148. /**
  149. * Synchronously processes images if they are deferred.
  150. *
  151. * This will make sure that the target files physically exist instead of
  152. * being generated by the Contao\CoreBundle\Controller\ImagesController
  153. * on first access.
  154. */
  155. public function createIfDeferred(): void
  156. {
  157. $picture = $this->getPicture();
  158. $candidates = [];
  159. foreach (array_merge([$picture->getImg()], $picture->getSources()) as $source) {
  160. $candidates[] = $source['src'] ?? null;
  161. foreach ($source['srcset'] ?? [] as $srcset) {
  162. $candidates[] = $srcset[0] ?? null;
  163. }
  164. }
  165. $deferredImages = array_filter(
  166. $candidates,
  167. static fn ($image): bool => $image instanceof DeferredImageInterface
  168. );
  169. if (empty($deferredImages)) {
  170. return;
  171. }
  172. $resizer = $this->locator->get('contao.image.legacy_resizer');
  173. if (!$resizer instanceof DeferredResizerInterface) {
  174. throw new \RuntimeException('The "contao.image.legacy_resizer" service does not support deferred resizing.');
  175. }
  176. foreach ($deferredImages as $deferredImage) {
  177. $resizer->resizeDeferredImage($deferredImage);
  178. }
  179. }
  180. private function imageFactory(): ImageFactoryInterface
  181. {
  182. return $this->locator->get('contao.image.factory');
  183. }
  184. private function pictureFactory(): PictureFactoryInterface
  185. {
  186. return $this->locator->get('contao.image.picture_factory');
  187. }
  188. private function staticUrl(): string
  189. {
  190. return $this->locator->get('contao.assets.files_context')->getStaticUrl();
  191. }
  192. }