Image::isGdAvailable()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 11
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
cc 1
eloc 3
nc 1
nop 0
dl 0
loc 11
ccs 0
cts 0
cp 0
crap 2
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
/**
6
 * This file is part of Esi\Utility.
7
 *
8
 * (c) 2017 - 2025 Eric Sizemore <[email protected]>
9
 *
10
 * For the full copyright and license information, please view
11
 * the LICENSE.md file that was distributed with this source code.
12
 */
13
14
namespace Esi\Utility;
15
16
use InvalidArgumentException;
17
use RuntimeException;
18
19
use function class_exists;
20
use function explode;
21
use function getimagesize;
22
use function image_type_to_mime_type;
23
24
/**
25
 * Image utilities.
26
 *
27
 * @since 2.0.0
28
 * @see Tests\ImageTest
29
 */
30
abstract class Image
31
{
32
    /**
33
     * Image type/mime strings to determine image type.
34
     *
35
     * @var string[][]
36
     */
37
    public const IMAGE_TYPES = [
38
        'jpg'  => ['image/jpg', 'image/jpeg'],
39
        'gif'  => ['image/gif'],
40
        'png'  => ['image/png'],
41
        'webp' => ['image/webp'],
42
    ];
43
44
    /**
45
     * Attempts to determine the image type. It tries to determine the image type with, in order
46
     * of preference: Exif, finfo, and getimagesize.
47
     *
48
     * @param string $imagePath File path to the image.
49
     *
50
     * @return false|string Returns the image type string on success, false on any failure.
51
     */
52 15
    public static function guessImageType(string $imagePath): false|string
53
    {
54
        /**
55
         * @var null|bool $hasFinfo
56
         */
57 15
        static $hasFinfo;
58
59 15
        if (!Filesystem::isFile($imagePath)) {
60 5
            throw new InvalidArgumentException('$imagePath not found or is not a file.');
61
        }
62
63
        // If Exif is available, let's start there. It's the fastest.
64
        //@codeCoverageIgnoreStart
65
        if (self::isExifAvailable()) {
66
            return self::guessImageTypeExif($imagePath);
67
        }
68
69
        $hasFinfo ??= class_exists('finfo');
70
71
        if ($hasFinfo) {
72
            // Next, let's try finfo
73
            return self::guessImageTypeFinfo($imagePath);
74
        }
75
76
        // Last resort: getimagesize can be pretty slow, especially compared to exif_imagetype
77
        // It may not return "mime". Theoretically, this should only happen for a file that is not an image.
78
        return self::guessImageTypeGetImageSize($imagePath);
79
        //@codeCoverageIgnoreEnd
80
    }
81
82
    /**
83
     * Check if the Exif extension is available on the server.
84
     */
85
    public static function isExifAvailable(): bool
86
    {
87
        /**
88
         * @var null|bool $hasExif
89
         */
90
        //@codeCoverageIgnoreStart
91
        static $hasExif;
92
93
        $hasExif ??= \extension_loaded('exif');
94
95
        return $hasExif;
96
        //@codeCoverageIgnoreEnd
97
    }
98
99
    /**
100
     * Check if the GD library is available on the server.
101
     */
102
    public static function isGdAvailable(): bool
103
    {
104
        /**
105
         * @var null|bool $hasGd
106
         */
107
        //@codeCoverageIgnoreStart
108
        static $hasGd;
109
110
        $hasGd ??= \extension_loaded('gd');
111
112
        return $hasGd;
113
        //@codeCoverageIgnoreEnd
114
    }
115
116
    /**
117
     * Checks if image has GIF format.
118
     *
119
     * @param string $imagePath File path to the image.
120
     *
121
     * @throws InvalidArgumentException If the image path provided is not valid.
122
     * @throws RuntimeException         If we are unable to determine the file type.
123
     */
124 3
    public static function isGif(string $imagePath): bool
125
    {
126 3
        $imageType = self::guessImageType($imagePath);
127
128 2
        if ($imageType === false) {
129 1
            throw new RuntimeException('Unable to determine the image type. Is it a valid image file?');
130
        }
131
132 1
        return Arrays::valueExists(self::IMAGE_TYPES['gif'], $imageType);
133
    }
134
135
    /**
136
     * Check if the GraphicsMagick library is available on the server.
137
     */
138
    public static function isGmagickAvailable(): bool
139
    {
140
        /**
141
         * @var null|bool $hasGmagick
142
         */
143
        //@codeCoverageIgnoreStart
144
        static $hasGmagick;
145
146
        $hasGmagick ??= \extension_loaded('gmagick');
147
148
        return $hasGmagick;
149
        //@codeCoverageIgnoreEnd
150
    }
151
152
    /**
153
     * Check if the ImageMagick library is available on the server.
154
     */
155
    public static function isImagickAvailable(): bool
156
    {
157
        /**
158
         * @var null|bool $hasImagick
159
         */
160
        //@codeCoverageIgnoreStart
161
        static $hasImagick;
162
163
        $hasImagick ??= \extension_loaded('imagick');
164
165
        return $hasImagick;
166
        //@codeCoverageIgnoreEnd
167
    }
168
169
    /**
170
     * Checks if image has JPG format.
171
     *
172
     * @param string $imagePath File path to the image.
173
     *
174
     * @throws InvalidArgumentException If the image path provided is not valid.
175
     * @throws RuntimeException         If we are unable to determine the file type.
176
     */
177 3
    public static function isJpg(string $imagePath): bool
178
    {
179 3
        $imageType = self::guessImageType($imagePath);
180
181 2
        if ($imageType === false) {
182 1
            throw new RuntimeException('Unable to determine the image type. Is it a valid image file?');
183
        }
184
185 1
        return Arrays::valueExists(self::IMAGE_TYPES['jpg'], $imageType);
186
    }
187
188
    /**
189
     * Checks if image has PNG format.
190
     *
191
     * @param string $imagePath File path to the image.
192
     *
193
     * @throws InvalidArgumentException If the image path provided is not valid.
194
     * @throws RuntimeException         If we are unable to determine the file type.
195
     */
196 3
    public static function isPng(string $imagePath): bool
197
    {
198 3
        $imageType = self::guessImageType($imagePath);
199
200 2
        if ($imageType === false) {
201 1
            throw new RuntimeException('Unable to determine the image type. Is it a valid image file?');
202
        }
203
204 1
        return Arrays::valueExists(self::IMAGE_TYPES['png'], $imageType);
205
    }
206
207
    /**
208
     * Checks if image has WEBP format.
209
     *
210
     * @param string $imagePath File path to the image.
211
     *
212
     * @throws InvalidArgumentException If the image path provided is not valid.
213
     * @throws RuntimeException         If we are unable to determine the file type.
214
     */
215 3
    public static function isWebp(string $imagePath): bool
216
    {
217 3
        $imageType = self::guessImageType($imagePath);
218
219 2
        if ($imageType === false) {
220 1
            throw new RuntimeException('Unable to determine the image type. Is it a valid image file?');
221
        }
222
223 1
        return Arrays::valueExists(self::IMAGE_TYPES['webp'], $imageType);
224
    }
225
226
    /**
227
     * Helper function for guessImageType().
228
     *
229
     * If the Exif extension is available, use Exif to determine mime type.
230
     *
231
     * @param string $imagePath File path to the image.
232
     *
233
     * @return false|string Returns the image type string on success, false on any failure.
234
     */
235
    private static function guessImageTypeExif(string $imagePath): false|string
236
    {
237
        //@codeCoverageIgnoreStart
238
        // Ignoring code coverage as if one method is available over another, the others won't be or need to be tested
239
        $imageType = @exif_imagetype($imagePath);
240
241
        return (\is_int($imageType) ? image_type_to_mime_type($imageType) : false);
242
        //@codeCoverageIgnoreEnd
243
    }
244
245
    /**
246
     * Helper function for guessImageType().
247
     *
248
     * If the FileInfo (finfo) extension is available, use finfo to determine mime type.
249
     *
250
     * @param string $imagePath File path to the image.
251
     *
252
     * @return false|string Returns the image type string on success, false on any failure.
253
     */
254
    private static function guessImageTypeFinfo(string $imagePath): false|string
255
    {
256
        //@codeCoverageIgnoreStart
257
        // Ignoring code coverage as if one method is available over another, the others won't be or need to be tested
258
        $finfo  = new \finfo(\FILEINFO_MIME);
259
        $result = $finfo->file($imagePath);
260
261
        if ($result === false) {
262
            // false means an error occurred
263
            return false;
264
        }
265
266
        [$mime, ] = explode('; ', $result);
267
268
        if (Strings::beginsWith($mime, 'image/')) {
269
            return $mime;
270
        }
271
272
        return false;
273
        //@codeCoverageIgnoreEnd
274
    }
275
276
    /**
277
     * Helper function for guessImageType().
278
     *
279
     * If the Exif extension is available, use Exif to determine mime type.
280
     *
281
     * @param string $imagePath File path to the image.
282
     *
283
     * @return false|string Returns the image type string on success, false on any failure.
284
     */
285
    private static function guessImageTypeGetImageSize(string $imagePath): false|string
286
    {
287
        //@codeCoverageIgnoreStart
288
        // Ignoring code coverage as if one method is available over another, the others won't be or need to be tested
289
        $imageSize = @getimagesize($imagePath);
290
291
        return $imageSize['mime'] ?? false;
292
        //@codeCoverageIgnoreEnd
293
    }
294
}
295