Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 3
CRAP
0.00% covered (danger)
0.00%
0 / 1
ProcessImageJob
0.00% covered (danger)
0.00%
0 / 62
0.00% covered (danger)
0.00%
0 / 3
132
0.00% covered (danger)
0.00%
0 / 1
 getJobType
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 execute
0.00% covered (danger)
0.00%
0 / 18
0.00% covered (danger)
0.00%
0 / 1
20
 createImage
0.00% covered (danger)
0.00%
0 / 43
0.00% covered (danger)
0.00%
0 / 1
42
1<?php
2declare(strict_types=1);
3
4namespace App\Job;
5
6use App\Utility\SettingsManager;
7use Cake\Queue\Job\Message;
8use Exception;
9use Imagick;
10use Interop\Queue\Processor;
11
12/**
13 * ProcessImageJob Class
14 *
15 * This class is responsible for processing image resizing as a background job.
16 * It receives image paths and resize specifications, processes the images using Imagick,
17 * and logs the process.
18 */
19class ProcessImageJob extends AbstractJob
20{
21    /**
22     * Get the human-readable job type name for logging
23     *
24     * @return string The job type description
25     */
26    protected static function getJobType(): string
27    {
28        return 'image processing';
29    }
30
31    /**
32     * Executes the image processing task.
33     *
34     * This method processes an image based on the provided message arguments. It retrieves the folder path,
35     * file name, and image ID from the message and processes the image for each specified size.
36     *
37     * @param \Cake\Queue\Job\Message $message The message containing the arguments for image processing.
38     * @return string|null Returns Processor::ACK on successful processing, or Processor::REJECT on error.
39     */
40    public function execute(Message $message): ?string
41    {
42        if (!extension_loaded('imagick')) {
43            $this->log(
44                'Imagick extension is not loaded',
45                'error',
46                ['group_name' => static::class],
47            );
48
49            return Processor::REJECT;
50        }
51
52        if (!$this->validateArguments($message, ['folder_path', 'file', 'id'])) {
53            return Processor::REJECT;
54        }
55
56        $folderPath = $message->getArgument('folder_path');
57        $file = $message->getArgument('file');
58        $id = $message->getArgument('id');
59
60        $imageSizes = SettingsManager::read('ImageSizes', []);
61
62        return $this->executeWithErrorHandling($id, function () use ($folderPath, $file, $imageSizes) {
63            foreach ($imageSizes as $width) {
64                $this->createImage($folderPath, $file, intval($width));
65            }
66
67            return true;
68        }, "Path: {$folderPath}{$file}");
69    }
70
71    /**
72     * Creates a resized or copied version of an image based on target width.
73     *
74     * This method ensures that a directory for the processed image exists, creating it if necessary.
75     * It checks if the original image exists and whether a processed version already exists.
76     * If the original image is smaller than or equal to the target width, it copies the original
77     * without resizing to preserve quality. Otherwise, it resizes the image to the specified width
78     * while maintaining the aspect ratio using the Imagick library.
79     *
80     * The method includes extensive error checking and logging throughout the process:
81     * - Validates and creates necessary directories
82     * - Checks for existence of source and destination files
83     * - Compares original and target dimensions
84     * - Handles image processing with proper resource cleanup
85     *
86     * @param string $folder The directory where the original image is stored
87     * @param string $file The name of the image file to be processed
88     * @param int $width The target width for the processed image
89     * @throws \Exception If the directory for the processed image cannot be created
90     * @return void
91     * @uses \Imagick For image processing operations
92     * @logs
93     * - Error if the directory cannot be created
94     * - Error if the original image is not found
95     * - Info if the processed image already exists (skips processing)
96     * - Info when original image is copied without resizing (smaller than target)
97     * - Info upon successful resizing of larger images
98     * - Error if any exception occurs during processing
99     * @note All logs are grouped under 'App\Job\ProcessImageJob'
100     */
101    private function createImage(string $folder, string $file, int $width): void
102    {
103        // Make sure folder for size exists
104        // Ensure the folder path ends with a directory separator
105        $folder = rtrim($folder, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR;
106
107        // Create the full path including the width
108        $sizeFolder = $folder . $width . DIRECTORY_SEPARATOR;
109
110        // Check if the directory exists, if not, create it
111        if (!is_dir($sizeFolder)) {
112            if (!mkdir($sizeFolder, 0755, true)) {
113                throw new Exception("Failed to create directory: $sizeFolder");
114            }
115        }
116
117        if (!file_exists($folder . $file)) {
118            throw new Exception("Original image not found for resizing: {$folder}{$file}");
119        }
120
121        if (file_exists($sizeFolder . $file)) {
122            $this->log(
123                sprintf('Skipped resizing, image already exists. Path: %s', $sizeFolder . $file),
124                'info',
125                ['group_name' => static::class],
126            );
127
128            return;
129        }
130
131        $imagick = new Imagick($folder . $file);
132        $originalWidth = $imagick->getImageWidth();
133
134        // Check if the original image is smaller than the target width
135        if ($originalWidth <= $width) {
136            // Just copy the original file without resizing
137            copy($folder . $file, $sizeFolder . $file);
138            $imagick->clear();
139
140            $this->log(
141                sprintf(
142                    'Original smaller than target width, copied. Original: %s, Saved: %s (Original width: %dpx)',
143                    $folder . $file,
144                    $sizeFolder . $file,
145                    $originalWidth,
146                ),
147                'info',
148                ['group_name' => static::class],
149            );
150        } else {
151            // Resize the image since it's larger than the target width
152            $imagick->resizeImage($width, 0, Imagick::FILTER_LANCZOS, 1);
153            $imagick->writeImage($sizeFolder . $file);
154            $imagick->clear();
155
156            $this->log(
157                sprintf(
158                    'Successfully resized image. Original: %s, Resized: %s, Width: %dpx (Orig. width: %dpx)',
159                    $folder . $file,
160                    $sizeFolder . $file,
161                    $width,
162                    $originalWidth,
163                ),
164                'info',
165                ['group_name' => static::class],
166            );
167        }
168    }
169}