├── .gitignore ├── README.md ├── composer.json ├── composer.lock ├── phpunit.xml.dist ├── src └── Bravo3 │ └── ImageManager │ ├── Encoders │ ├── AbstractEncoder.php │ ├── AbstractFilesystemEncoder.php │ ├── EncoderInterface.php │ ├── ImagickEncoder.php │ └── InterventionEncoder.php │ ├── Entities │ ├── Image.php │ ├── ImageCropDimensions.php │ ├── ImageDimensions.php │ ├── ImageMetadata.php │ ├── ImageVariation.php │ └── Interfaces │ │ └── SerialisableInterface.php │ ├── Enum │ ├── ImageFormat.php │ └── ImageOrientation.php │ ├── Exceptions │ ├── BadImageException.php │ ├── ImageManagerException.php │ ├── InvalidImageMetadataException.php │ ├── IoException.php │ ├── NoSupportedEncoderException.php │ ├── NotExistsException.php │ ├── NotPermittedException.php │ └── ObjectAlreadyExistsException.php │ ├── Services │ ├── DataInspector.php │ ├── ImageInspector.php │ └── ImageManager.php │ └── Traits │ └── FriendTrait.php └── tests ├── Bravo3 └── ImageManager │ └── Tests │ ├── Entities │ └── ImageTest.php │ ├── Resources │ ├── FriendlyClass.php │ ├── UnfriendlyClass.php │ ├── actually_a_png.jpg │ ├── animated.gif │ ├── image.jpg │ ├── image.png │ ├── not_an_image.png │ ├── sample_pdf.pdf │ ├── sample_pdf2.pdf │ └── transparent.png │ ├── Services │ └── ImageManagerTest.php │ └── Traits │ └── FriendTraitTest.php └── bootstrap.php /.gitignore: -------------------------------------------------------------------------------- 1 | !.gitkeep 2 | /vendor/ 3 | /bin/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Image Manager 2 | ============= 3 | A PHP 5.4 image manager intended for cloud use. This image manager is designed to be low-level and work with 'keys' - 4 | not directly attach to an entity. 5 | 6 | Features 7 | -------- 8 | * Easily push and pull images to any remote filesystem (eg Amazon S3) 9 | * Request an image with specific dimensions - allow the manager to transparently create & store this variation 10 | * Request that an image dimension exists (will be created if it doesn't), allowing for the storage device to be used as a CDN end-point 11 | * Use a caching service to maintain a knowledge base of image dimensions available to improve performance 12 | * Load & save images from memory or a file 13 | * Convert image format & quality with ease 14 | * Customisable encoders 15 | * GD and Imagick support 16 | * PDF support (via Imagick) 17 | 18 | Examples 19 | -------- 20 | ### Storing an image 21 | 22 | // Use the local filesystem as a fake remote (replace with S3, etc) 23 | $im = new ImageManager(new Filesystem(new LocalAdapter('/tmp/images'))); 24 | 25 | // Load local "image.png" and give it a key of 'content_123_image_1' (using the filename is suitable too) 26 | $image = $im->loadFromFile('image.png', 'content_123_image_1'); 27 | 28 | // Save it on the remote 29 | $im->push($image); 30 | 31 | ### Retrieving an image 32 | 33 | $image = new Image('content_123_image_1'); 34 | $im->pull($image); 35 | 36 | // Save to local filesystem 37 | $im->save($image, '/tmp/image.png'); 38 | 39 | // Output to client 40 | echo $image->getData(); 41 | 42 | ### Retrieving an automatic variation 43 | 44 | // Define a dimension that the image will fit in a height of 200px 45 | $dimensions = new ImageDimensions(null, 200); 46 | 47 | // Create a specification for a JPEG format, quality 75% and use the above dimensions 48 | $image = new ImageVariation('content_123_image_1', ImageFormat::JPEG(), 75, $dimensions); 49 | 50 | // Automatically create the variation when we pull 51 | $im->pull($image); 52 | 53 | if (!$im->isPersistent()) { 54 | // Make sure our new variation exists on the remote 55 | $im->push($image); 56 | } 57 | 58 | echo $image->getData(); 59 | 60 | ### Create a variation manually 61 | 62 | $source = $im->loadFromFile('image.png', 'content_123_image_1'); 63 | $resized = $im->createVariation($source, ImageFormat::JPEG(), 90, new ImageDimensions(100, 100)); 64 | $im->push($resized); 65 | 66 | ### Check if a variation exists 67 | 68 | $image = new ImageVariation('content_123_image_1', ImageFormat::JPEG(), 75, $dimensions); 69 | 70 | if (!$im->exists($image)) { 71 | $im->pull($image); // Creates the variation 72 | $im->push($image); // Save it on the remote 73 | } 74 | 75 | echo ''; 76 | 77 | Caching 78 | ------- 79 | Because remote storage services have a moderate degree of lag while talking to, it's probably not appropriate to do 80 | "exists" checks on every image variation during page generation. To avoid this you can either pre-render the image and 81 | assume it will exist, or using a quick caching mechanic to store an inventory of all images available. 82 | 83 | Your caching mechanic MUST be persistent - if a cache key is lost the image manager will assume the remote file does 84 | not exist. Using a database or disk backed key/value storage is recommended (eg Redis). 85 | 86 | To use caching, just include a \Bravo3\Cache\PoolInterface implementation in the ImageManager's constructor. 87 | 88 | Encoders 89 | -------- 90 | By default the image manager will use the InterventionEncoder (@see http://image.intervention.io/), which supports 91 | base image formats. You can switch this for the ImagickEncoder which also allows for PDF documents to be used as an 92 | input. 93 | 94 | To *add* support for native Imagick, but use Intervention where possible (thus giving you PDF support): 95 | 96 | $im = new ImageManager(...); 97 | $im->addEncoder(new ImagickEncoder()); 98 | 99 | To use only the Imagick encoder: 100 | 101 | $im = new ImageManager($filesystem, $cache_pool, [new ImagickEncoder()]); 102 | 103 | Future Considerations 104 | --------------------- 105 | * Allow for image manipulations (eg add text, rotate, etc) 106 | * Allow for customisable variation naming schemes 107 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "bravo3/image-manager", 3 | "type": "library", 4 | "description": "A PHP 5.4 library to control dynamic image assets in a cloud environment", 5 | "keywords": ["image", "aws", "google cloud", "azure"], 6 | "license": "MIT", 7 | "authors": [ 8 | { 9 | "name": "Jordon Scott", 10 | "email": "jordon@novatek.com.au" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-0": { 15 | "": ["src/", "tests/"] 16 | } 17 | }, 18 | "require": { 19 | "php": ">=5.4.0", 20 | "psr/log": "~1.0", 21 | "bravo3/cache": "~0.1.1", 22 | "eloquent/enumeration": "~5.1", 23 | "intervention/image": "~1.6.2", 24 | "knplabs/gaufrette": "~0.1.7", 25 | "symfony/filesystem": "~2.4|^3.0" 26 | }, 27 | "require-dev": { 28 | "phpunit/phpunit": ">=4.0.0" 29 | }, 30 | "minimum-stability": "beta", 31 | "scripts": { 32 | "post-install-cmd": [ 33 | ], 34 | "post-update-cmd": [ 35 | ] 36 | }, 37 | "config": { 38 | "bin-dir": "bin" 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "864a4d4e4bf9112cd8de2f247f061071", 8 | "content-hash": "ef1708351fb385fb90cb52156a812882", 9 | "packages": [ 10 | { 11 | "name": "bravo3/cache", 12 | "version": "0.1.4", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/bravo3/cache.git", 16 | "reference": "2d78cb0e19bf3a659bd3d98caeae7eec9bce84a6" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/bravo3/cache/zipball/2d78cb0e19bf3a659bd3d98caeae7eec9bce84a6", 21 | "reference": "2d78cb0e19bf3a659bd3d98caeae7eec9bce84a6", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.4.0", 26 | "psr/log": "~1.0" 27 | }, 28 | "require-dev": { 29 | "bravo3/orm": "^0.5.1", 30 | "phpunit/phpunit": ">=4.0.0", 31 | "predis/predis": "^1.0.3" 32 | }, 33 | "suggest": { 34 | "bravo3/orm": "For Bravo3/ORM support", 35 | "predis/predis": "For Redis support" 36 | }, 37 | "type": "library", 38 | "autoload": { 39 | "psr-0": { 40 | "": "src/" 41 | } 42 | }, 43 | "notification-url": "https://packagist.org/downloads/", 44 | "license": [ 45 | "MIT" 46 | ], 47 | "authors": [ 48 | { 49 | "name": "Jordon Scott", 50 | "email": "jordon@novatek.com.au" 51 | } 52 | ], 53 | "description": "A PHP 5.4 cache interface and various implementations including Redis and Bravo3/ORM", 54 | "keywords": [ 55 | "cache", 56 | "orm", 57 | "predis", 58 | "redis" 59 | ], 60 | "time": "2015-11-10 04:54:37" 61 | }, 62 | { 63 | "name": "eloquent/enumeration", 64 | "version": "5.1.1", 65 | "source": { 66 | "type": "git", 67 | "url": "https://github.com/eloquent/enumeration.git", 68 | "reference": "0242859435d9b135939816858348556d3cde9e3c" 69 | }, 70 | "dist": { 71 | "type": "zip", 72 | "url": "https://api.github.com/repos/eloquent/enumeration/zipball/0242859435d9b135939816858348556d3cde9e3c", 73 | "reference": "0242859435d9b135939816858348556d3cde9e3c", 74 | "shasum": "" 75 | }, 76 | "require": { 77 | "php": ">=5.3" 78 | }, 79 | "require-dev": { 80 | "icecave/archer": "dev-develop", 81 | "phpunit/phpunit": "^4", 82 | "sami/sami": "^3" 83 | }, 84 | "type": "library", 85 | "autoload": { 86 | "psr-4": { 87 | "Eloquent\\Enumeration\\": "src" 88 | } 89 | }, 90 | "notification-url": "https://packagist.org/downloads/", 91 | "license": [ 92 | "MIT" 93 | ], 94 | "authors": [ 95 | { 96 | "name": "Erin Millard", 97 | "email": "ezzatron@gmail.com", 98 | "homepage": "http://ezzatron.com/" 99 | } 100 | ], 101 | "description": "An enumeration implementation for PHP.", 102 | "homepage": "https://github.com/eloquent/enumeration", 103 | "keywords": [ 104 | "class", 105 | "enum", 106 | "enumeration", 107 | "multiton", 108 | "set", 109 | "type" 110 | ], 111 | "time": "2015-11-03 22:21:38" 112 | }, 113 | { 114 | "name": "illuminate/support", 115 | "version": "v4.2.17", 116 | "target-dir": "Illuminate/Support", 117 | "source": { 118 | "type": "git", 119 | "url": "https://github.com/illuminate/support.git", 120 | "reference": "db61f3f6d507ce417ca993e1f93585db7efd8b12" 121 | }, 122 | "dist": { 123 | "type": "zip", 124 | "url": "https://api.github.com/repos/illuminate/support/zipball/db61f3f6d507ce417ca993e1f93585db7efd8b12", 125 | "reference": "db61f3f6d507ce417ca993e1f93585db7efd8b12", 126 | "shasum": "" 127 | }, 128 | "require": { 129 | "php": ">=5.4.0" 130 | }, 131 | "require-dev": { 132 | "jeremeamia/superclosure": "~1.0.1", 133 | "patchwork/utf8": "~1.1" 134 | }, 135 | "type": "library", 136 | "extra": { 137 | "branch-alias": { 138 | "dev-master": "4.2-dev" 139 | } 140 | }, 141 | "autoload": { 142 | "psr-0": { 143 | "Illuminate\\Support": "" 144 | }, 145 | "files": [ 146 | "Illuminate/Support/helpers.php" 147 | ] 148 | }, 149 | "notification-url": "https://packagist.org/downloads/", 150 | "license": [ 151 | "MIT" 152 | ], 153 | "authors": [ 154 | { 155 | "name": "Taylor Otwell", 156 | "email": "taylorotwell@gmail.com" 157 | } 158 | ], 159 | "time": "2015-01-27 20:51:43" 160 | }, 161 | { 162 | "name": "intervention/image", 163 | "version": "1.6.5", 164 | "source": { 165 | "type": "git", 166 | "url": "https://github.com/Intervention/image.git", 167 | "reference": "432f3b28ea08455146b157c6bfdebf4ef5bc661e" 168 | }, 169 | "dist": { 170 | "type": "zip", 171 | "url": "https://api.github.com/repos/Intervention/image/zipball/432f3b28ea08455146b157c6bfdebf4ef5bc661e", 172 | "reference": "432f3b28ea08455146b157c6bfdebf4ef5bc661e", 173 | "shasum": "" 174 | }, 175 | "require": { 176 | "ext-gd": "*", 177 | "illuminate/support": "4.*", 178 | "php": ">=5.3.0" 179 | }, 180 | "require-dev": { 181 | "phpunit/phpunit": "4.1.*" 182 | }, 183 | "suggest": { 184 | "intervention/imagecache": "Caching extension for the Intervention Image library" 185 | }, 186 | "type": "library", 187 | "autoload": { 188 | "psr-0": { 189 | "Intervention\\Image": "src/" 190 | } 191 | }, 192 | "notification-url": "https://packagist.org/downloads/", 193 | "license": [ 194 | "MIT" 195 | ], 196 | "authors": [ 197 | { 198 | "name": "Oliver Vogel", 199 | "email": "oliver@olivervogel.net", 200 | "homepage": "http://olivervogel.net/" 201 | } 202 | ], 203 | "description": "Image handling and manipulation library with support for Laravel 4 integration", 204 | "homepage": "http://image.intervention.io/", 205 | "keywords": [ 206 | "gd", 207 | "image", 208 | "laravel", 209 | "thumbnail", 210 | "watermark" 211 | ], 212 | "time": "2014-06-07 16:25:27" 213 | }, 214 | { 215 | "name": "knplabs/gaufrette", 216 | "version": "v0.1.9", 217 | "source": { 218 | "type": "git", 219 | "url": "https://github.com/KnpLabs/Gaufrette.git", 220 | "reference": "4c73bb66ff41d7c9beb57372a82047cf5dcc6d1c" 221 | }, 222 | "dist": { 223 | "type": "zip", 224 | "url": "https://api.github.com/repos/KnpLabs/Gaufrette/zipball/4c73bb66ff41d7c9beb57372a82047cf5dcc6d1c", 225 | "reference": "4c73bb66ff41d7c9beb57372a82047cf5dcc6d1c", 226 | "shasum": "" 227 | }, 228 | "require": { 229 | "php": ">=5.3.2" 230 | }, 231 | "require-dev": { 232 | "amazonwebservices/aws-sdk-for-php": "1.5.*", 233 | "aws/aws-sdk-php": "~2", 234 | "doctrine/dbal": ">=2.3", 235 | "dropbox-php/dropbox-php": "*", 236 | "google/apiclient": "~1.1", 237 | "herzult/php-ssh": "*", 238 | "microsoft/windowsazure": "dev-master", 239 | "mikey179/vfsstream": "~1.2.0", 240 | "phpseclib/phpseclib": "dev-master", 241 | "phpspec/phpspec": "2.0.*", 242 | "phpunit/phpunit": "3.7.*", 243 | "rackspace/php-opencloud": "1.9.*" 244 | }, 245 | "suggest": { 246 | "amazonwebservices/aws-sdk-for-php": "to use the legacy Amazon S3 adapters", 247 | "aws/aws-sdk-php": "to use the Amazon S3 adapter", 248 | "doctrine/dbal": "to use the Doctrine DBAL adapter", 249 | "dropbox-php/dropbox-php": "to use the Dropbox adapter", 250 | "ext-apc": "to use the APC adapter", 251 | "ext-curl": "*", 252 | "ext-fileinfo": "This extension is used to automatically detect the content-type of a file in the AwsS3, OpenCloud, AzureBlogStorage and GoogleCloudStorage adapters", 253 | "ext-mbstring": "*", 254 | "ext-mongo": "*", 255 | "ext-zip": "to use the Zip adapter", 256 | "google/apiclient": "to use GoogleCloudStorage adapter", 257 | "herzult/php-ssh": "to use SFtp adapter", 258 | "knplabs/knp-gaufrette-bundle": "to use with Symfony2", 259 | "microsoft/windowsazure": "to use Microsoft Azure Blob Storage adapter", 260 | "phpseclib/phpseclib": "to use PhpseclibSftp adapter", 261 | "rackspace/php-opencloud": "to use Opencloud adapter" 262 | }, 263 | "type": "library", 264 | "extra": { 265 | "branch-alias": { 266 | "dev-master": "0.2.x-dev" 267 | } 268 | }, 269 | "autoload": { 270 | "psr-0": { 271 | "Gaufrette": "src/" 272 | } 273 | }, 274 | "notification-url": "https://packagist.org/downloads/", 275 | "license": [ 276 | "MIT" 277 | ], 278 | "authors": [ 279 | { 280 | "name": "The contributors", 281 | "homepage": "http://github.com/knplabs/Gaufrette/contributors" 282 | }, 283 | { 284 | "name": "KnpLabs Team", 285 | "homepage": "http://knplabs.com" 286 | } 287 | ], 288 | "description": "PHP5 library that provides a filesystem abstraction layer", 289 | "homepage": "http://knplabs.com", 290 | "keywords": [ 291 | "abstraction", 292 | "file", 293 | "filesystem", 294 | "media" 295 | ], 296 | "time": "2015-03-09 08:06:57" 297 | }, 298 | { 299 | "name": "psr/log", 300 | "version": "1.0.0", 301 | "source": { 302 | "type": "git", 303 | "url": "https://github.com/php-fig/log.git", 304 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b" 305 | }, 306 | "dist": { 307 | "type": "zip", 308 | "url": "https://api.github.com/repos/php-fig/log/zipball/fe0936ee26643249e916849d48e3a51d5f5e278b", 309 | "reference": "fe0936ee26643249e916849d48e3a51d5f5e278b", 310 | "shasum": "" 311 | }, 312 | "type": "library", 313 | "autoload": { 314 | "psr-0": { 315 | "Psr\\Log\\": "" 316 | } 317 | }, 318 | "notification-url": "https://packagist.org/downloads/", 319 | "license": [ 320 | "MIT" 321 | ], 322 | "authors": [ 323 | { 324 | "name": "PHP-FIG", 325 | "homepage": "http://www.php-fig.org/" 326 | } 327 | ], 328 | "description": "Common interface for logging libraries", 329 | "keywords": [ 330 | "log", 331 | "psr", 332 | "psr-3" 333 | ], 334 | "time": "2012-12-21 11:40:51" 335 | }, 336 | { 337 | "name": "symfony/filesystem", 338 | "version": "v3.0.0", 339 | "source": { 340 | "type": "git", 341 | "url": "https://github.com/symfony/filesystem.git", 342 | "reference": "692d98d813e4ef314b9c22775c86ddbeb0f44884" 343 | }, 344 | "dist": { 345 | "type": "zip", 346 | "url": "https://api.github.com/repos/symfony/filesystem/zipball/692d98d813e4ef314b9c22775c86ddbeb0f44884", 347 | "reference": "692d98d813e4ef314b9c22775c86ddbeb0f44884", 348 | "shasum": "" 349 | }, 350 | "require": { 351 | "php": ">=5.5.9" 352 | }, 353 | "type": "library", 354 | "extra": { 355 | "branch-alias": { 356 | "dev-master": "3.0-dev" 357 | } 358 | }, 359 | "autoload": { 360 | "psr-4": { 361 | "Symfony\\Component\\Filesystem\\": "" 362 | }, 363 | "exclude-from-classmap": [ 364 | "/Tests/" 365 | ] 366 | }, 367 | "notification-url": "https://packagist.org/downloads/", 368 | "license": [ 369 | "MIT" 370 | ], 371 | "authors": [ 372 | { 373 | "name": "Fabien Potencier", 374 | "email": "fabien@symfony.com" 375 | }, 376 | { 377 | "name": "Symfony Community", 378 | "homepage": "https://symfony.com/contributors" 379 | } 380 | ], 381 | "description": "Symfony Filesystem Component", 382 | "homepage": "https://symfony.com", 383 | "time": "2015-11-23 10:41:47" 384 | } 385 | ], 386 | "packages-dev": [ 387 | { 388 | "name": "doctrine/instantiator", 389 | "version": "1.0.5", 390 | "source": { 391 | "type": "git", 392 | "url": "https://github.com/doctrine/instantiator.git", 393 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d" 394 | }, 395 | "dist": { 396 | "type": "zip", 397 | "url": "https://api.github.com/repos/doctrine/instantiator/zipball/8e884e78f9f0eb1329e445619e04456e64d8051d", 398 | "reference": "8e884e78f9f0eb1329e445619e04456e64d8051d", 399 | "shasum": "" 400 | }, 401 | "require": { 402 | "php": ">=5.3,<8.0-DEV" 403 | }, 404 | "require-dev": { 405 | "athletic/athletic": "~0.1.8", 406 | "ext-pdo": "*", 407 | "ext-phar": "*", 408 | "phpunit/phpunit": "~4.0", 409 | "squizlabs/php_codesniffer": "~2.0" 410 | }, 411 | "type": "library", 412 | "extra": { 413 | "branch-alias": { 414 | "dev-master": "1.0.x-dev" 415 | } 416 | }, 417 | "autoload": { 418 | "psr-4": { 419 | "Doctrine\\Instantiator\\": "src/Doctrine/Instantiator/" 420 | } 421 | }, 422 | "notification-url": "https://packagist.org/downloads/", 423 | "license": [ 424 | "MIT" 425 | ], 426 | "authors": [ 427 | { 428 | "name": "Marco Pivetta", 429 | "email": "ocramius@gmail.com", 430 | "homepage": "http://ocramius.github.com/" 431 | } 432 | ], 433 | "description": "A small, lightweight utility to instantiate objects in PHP without invoking their constructors", 434 | "homepage": "https://github.com/doctrine/instantiator", 435 | "keywords": [ 436 | "constructor", 437 | "instantiate" 438 | ], 439 | "time": "2015-06-14 21:17:01" 440 | }, 441 | { 442 | "name": "myclabs/deep-copy", 443 | "version": "1.5.0", 444 | "source": { 445 | "type": "git", 446 | "url": "https://github.com/myclabs/DeepCopy.git", 447 | "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc" 448 | }, 449 | "dist": { 450 | "type": "zip", 451 | "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/e3abefcd7f106677fd352cd7c187d6c969aa9ddc", 452 | "reference": "e3abefcd7f106677fd352cd7c187d6c969aa9ddc", 453 | "shasum": "" 454 | }, 455 | "require": { 456 | "php": ">=5.4.0" 457 | }, 458 | "require-dev": { 459 | "doctrine/collections": "1.*", 460 | "phpunit/phpunit": "~4.1" 461 | }, 462 | "type": "library", 463 | "autoload": { 464 | "psr-4": { 465 | "DeepCopy\\": "src/DeepCopy/" 466 | } 467 | }, 468 | "notification-url": "https://packagist.org/downloads/", 469 | "license": [ 470 | "MIT" 471 | ], 472 | "description": "Create deep copies (clones) of your objects", 473 | "homepage": "https://github.com/myclabs/DeepCopy", 474 | "keywords": [ 475 | "clone", 476 | "copy", 477 | "duplicate", 478 | "object", 479 | "object graph" 480 | ], 481 | "time": "2015-11-07 22:20:37" 482 | }, 483 | { 484 | "name": "phpdocumentor/reflection-docblock", 485 | "version": "2.0.4", 486 | "source": { 487 | "type": "git", 488 | "url": "https://github.com/phpDocumentor/ReflectionDocBlock.git", 489 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8" 490 | }, 491 | "dist": { 492 | "type": "zip", 493 | "url": "https://api.github.com/repos/phpDocumentor/ReflectionDocBlock/zipball/d68dbdc53dc358a816f00b300704702b2eaff7b8", 494 | "reference": "d68dbdc53dc358a816f00b300704702b2eaff7b8", 495 | "shasum": "" 496 | }, 497 | "require": { 498 | "php": ">=5.3.3" 499 | }, 500 | "require-dev": { 501 | "phpunit/phpunit": "~4.0" 502 | }, 503 | "suggest": { 504 | "dflydev/markdown": "~1.0", 505 | "erusev/parsedown": "~1.0" 506 | }, 507 | "type": "library", 508 | "extra": { 509 | "branch-alias": { 510 | "dev-master": "2.0.x-dev" 511 | } 512 | }, 513 | "autoload": { 514 | "psr-0": { 515 | "phpDocumentor": [ 516 | "src/" 517 | ] 518 | } 519 | }, 520 | "notification-url": "https://packagist.org/downloads/", 521 | "license": [ 522 | "MIT" 523 | ], 524 | "authors": [ 525 | { 526 | "name": "Mike van Riel", 527 | "email": "mike.vanriel@naenius.com" 528 | } 529 | ], 530 | "time": "2015-02-03 12:10:50" 531 | }, 532 | { 533 | "name": "phpspec/prophecy", 534 | "version": "v1.5.0", 535 | "source": { 536 | "type": "git", 537 | "url": "https://github.com/phpspec/prophecy.git", 538 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7" 539 | }, 540 | "dist": { 541 | "type": "zip", 542 | "url": "https://api.github.com/repos/phpspec/prophecy/zipball/4745ded9307786b730d7a60df5cb5a6c43cf95f7", 543 | "reference": "4745ded9307786b730d7a60df5cb5a6c43cf95f7", 544 | "shasum": "" 545 | }, 546 | "require": { 547 | "doctrine/instantiator": "^1.0.2", 548 | "phpdocumentor/reflection-docblock": "~2.0", 549 | "sebastian/comparator": "~1.1" 550 | }, 551 | "require-dev": { 552 | "phpspec/phpspec": "~2.0" 553 | }, 554 | "type": "library", 555 | "extra": { 556 | "branch-alias": { 557 | "dev-master": "1.4.x-dev" 558 | } 559 | }, 560 | "autoload": { 561 | "psr-0": { 562 | "Prophecy\\": "src/" 563 | } 564 | }, 565 | "notification-url": "https://packagist.org/downloads/", 566 | "license": [ 567 | "MIT" 568 | ], 569 | "authors": [ 570 | { 571 | "name": "Konstantin Kudryashov", 572 | "email": "ever.zet@gmail.com", 573 | "homepage": "http://everzet.com" 574 | }, 575 | { 576 | "name": "Marcello Duarte", 577 | "email": "marcello.duarte@gmail.com" 578 | } 579 | ], 580 | "description": "Highly opinionated mocking framework for PHP 5.3+", 581 | "homepage": "https://github.com/phpspec/prophecy", 582 | "keywords": [ 583 | "Double", 584 | "Dummy", 585 | "fake", 586 | "mock", 587 | "spy", 588 | "stub" 589 | ], 590 | "time": "2015-08-13 10:07:40" 591 | }, 592 | { 593 | "name": "phpunit/php-code-coverage", 594 | "version": "3.0.2", 595 | "source": { 596 | "type": "git", 597 | "url": "https://github.com/sebastianbergmann/php-code-coverage.git", 598 | "reference": "f7bb5cddf4ffe113eeb737b05241adb947b43f9d" 599 | }, 600 | "dist": { 601 | "type": "zip", 602 | "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/f7bb5cddf4ffe113eeb737b05241adb947b43f9d", 603 | "reference": "f7bb5cddf4ffe113eeb737b05241adb947b43f9d", 604 | "shasum": "" 605 | }, 606 | "require": { 607 | "php": ">=5.6", 608 | "phpunit/php-file-iterator": "~1.3", 609 | "phpunit/php-text-template": "~1.2", 610 | "phpunit/php-token-stream": "~1.3", 611 | "sebastian/environment": "^1.3.2", 612 | "sebastian/version": "~1.0" 613 | }, 614 | "require-dev": { 615 | "ext-xdebug": ">=2.1.4", 616 | "phpunit/phpunit": "~5" 617 | }, 618 | "suggest": { 619 | "ext-dom": "*", 620 | "ext-xdebug": ">=2.2.1", 621 | "ext-xmlwriter": "*" 622 | }, 623 | "type": "library", 624 | "extra": { 625 | "branch-alias": { 626 | "dev-master": "3.0.x-dev" 627 | } 628 | }, 629 | "autoload": { 630 | "classmap": [ 631 | "src/" 632 | ] 633 | }, 634 | "notification-url": "https://packagist.org/downloads/", 635 | "license": [ 636 | "BSD-3-Clause" 637 | ], 638 | "authors": [ 639 | { 640 | "name": "Sebastian Bergmann", 641 | "email": "sb@sebastian-bergmann.de", 642 | "role": "lead" 643 | } 644 | ], 645 | "description": "Library that provides collection, processing, and rendering functionality for PHP code coverage information.", 646 | "homepage": "https://github.com/sebastianbergmann/php-code-coverage", 647 | "keywords": [ 648 | "coverage", 649 | "testing", 650 | "xunit" 651 | ], 652 | "time": "2015-11-12 21:08:20" 653 | }, 654 | { 655 | "name": "phpunit/php-file-iterator", 656 | "version": "1.4.1", 657 | "source": { 658 | "type": "git", 659 | "url": "https://github.com/sebastianbergmann/php-file-iterator.git", 660 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0" 661 | }, 662 | "dist": { 663 | "type": "zip", 664 | "url": "https://api.github.com/repos/sebastianbergmann/php-file-iterator/zipball/6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 665 | "reference": "6150bf2c35d3fc379e50c7602b75caceaa39dbf0", 666 | "shasum": "" 667 | }, 668 | "require": { 669 | "php": ">=5.3.3" 670 | }, 671 | "type": "library", 672 | "extra": { 673 | "branch-alias": { 674 | "dev-master": "1.4.x-dev" 675 | } 676 | }, 677 | "autoload": { 678 | "classmap": [ 679 | "src/" 680 | ] 681 | }, 682 | "notification-url": "https://packagist.org/downloads/", 683 | "license": [ 684 | "BSD-3-Clause" 685 | ], 686 | "authors": [ 687 | { 688 | "name": "Sebastian Bergmann", 689 | "email": "sb@sebastian-bergmann.de", 690 | "role": "lead" 691 | } 692 | ], 693 | "description": "FilterIterator implementation that filters files based on a list of suffixes.", 694 | "homepage": "https://github.com/sebastianbergmann/php-file-iterator/", 695 | "keywords": [ 696 | "filesystem", 697 | "iterator" 698 | ], 699 | "time": "2015-06-21 13:08:43" 700 | }, 701 | { 702 | "name": "phpunit/php-text-template", 703 | "version": "1.2.1", 704 | "source": { 705 | "type": "git", 706 | "url": "https://github.com/sebastianbergmann/php-text-template.git", 707 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686" 708 | }, 709 | "dist": { 710 | "type": "zip", 711 | "url": "https://api.github.com/repos/sebastianbergmann/php-text-template/zipball/31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 712 | "reference": "31f8b717e51d9a2afca6c9f046f5d69fc27c8686", 713 | "shasum": "" 714 | }, 715 | "require": { 716 | "php": ">=5.3.3" 717 | }, 718 | "type": "library", 719 | "autoload": { 720 | "classmap": [ 721 | "src/" 722 | ] 723 | }, 724 | "notification-url": "https://packagist.org/downloads/", 725 | "license": [ 726 | "BSD-3-Clause" 727 | ], 728 | "authors": [ 729 | { 730 | "name": "Sebastian Bergmann", 731 | "email": "sebastian@phpunit.de", 732 | "role": "lead" 733 | } 734 | ], 735 | "description": "Simple template engine.", 736 | "homepage": "https://github.com/sebastianbergmann/php-text-template/", 737 | "keywords": [ 738 | "template" 739 | ], 740 | "time": "2015-06-21 13:50:34" 741 | }, 742 | { 743 | "name": "phpunit/php-timer", 744 | "version": "1.0.7", 745 | "source": { 746 | "type": "git", 747 | "url": "https://github.com/sebastianbergmann/php-timer.git", 748 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b" 749 | }, 750 | "dist": { 751 | "type": "zip", 752 | "url": "https://api.github.com/repos/sebastianbergmann/php-timer/zipball/3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 753 | "reference": "3e82f4e9fc92665fafd9157568e4dcb01d014e5b", 754 | "shasum": "" 755 | }, 756 | "require": { 757 | "php": ">=5.3.3" 758 | }, 759 | "type": "library", 760 | "autoload": { 761 | "classmap": [ 762 | "src/" 763 | ] 764 | }, 765 | "notification-url": "https://packagist.org/downloads/", 766 | "license": [ 767 | "BSD-3-Clause" 768 | ], 769 | "authors": [ 770 | { 771 | "name": "Sebastian Bergmann", 772 | "email": "sb@sebastian-bergmann.de", 773 | "role": "lead" 774 | } 775 | ], 776 | "description": "Utility class for timing", 777 | "homepage": "https://github.com/sebastianbergmann/php-timer/", 778 | "keywords": [ 779 | "timer" 780 | ], 781 | "time": "2015-06-21 08:01:12" 782 | }, 783 | { 784 | "name": "phpunit/php-token-stream", 785 | "version": "1.4.8", 786 | "source": { 787 | "type": "git", 788 | "url": "https://github.com/sebastianbergmann/php-token-stream.git", 789 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da" 790 | }, 791 | "dist": { 792 | "type": "zip", 793 | "url": "https://api.github.com/repos/sebastianbergmann/php-token-stream/zipball/3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 794 | "reference": "3144ae21711fb6cac0b1ab4cbe63b75ce3d4e8da", 795 | "shasum": "" 796 | }, 797 | "require": { 798 | "ext-tokenizer": "*", 799 | "php": ">=5.3.3" 800 | }, 801 | "require-dev": { 802 | "phpunit/phpunit": "~4.2" 803 | }, 804 | "type": "library", 805 | "extra": { 806 | "branch-alias": { 807 | "dev-master": "1.4-dev" 808 | } 809 | }, 810 | "autoload": { 811 | "classmap": [ 812 | "src/" 813 | ] 814 | }, 815 | "notification-url": "https://packagist.org/downloads/", 816 | "license": [ 817 | "BSD-3-Clause" 818 | ], 819 | "authors": [ 820 | { 821 | "name": "Sebastian Bergmann", 822 | "email": "sebastian@phpunit.de" 823 | } 824 | ], 825 | "description": "Wrapper around PHP's tokenizer extension.", 826 | "homepage": "https://github.com/sebastianbergmann/php-token-stream/", 827 | "keywords": [ 828 | "tokenizer" 829 | ], 830 | "time": "2015-09-15 10:49:45" 831 | }, 832 | { 833 | "name": "phpunit/phpunit", 834 | "version": "5.0.10", 835 | "source": { 836 | "type": "git", 837 | "url": "https://github.com/sebastianbergmann/phpunit.git", 838 | "reference": "9104a4e2f6a3ebdc4eb036624949a1a2849373dd" 839 | }, 840 | "dist": { 841 | "type": "zip", 842 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit/zipball/9104a4e2f6a3ebdc4eb036624949a1a2849373dd", 843 | "reference": "9104a4e2f6a3ebdc4eb036624949a1a2849373dd", 844 | "shasum": "" 845 | }, 846 | "require": { 847 | "ext-dom": "*", 848 | "ext-json": "*", 849 | "ext-pcre": "*", 850 | "ext-reflection": "*", 851 | "ext-spl": "*", 852 | "myclabs/deep-copy": "~1.3", 853 | "php": ">=5.6", 854 | "phpspec/prophecy": "^1.3.1", 855 | "phpunit/php-code-coverage": "~3.0", 856 | "phpunit/php-file-iterator": "~1.4", 857 | "phpunit/php-text-template": "~1.2", 858 | "phpunit/php-timer": ">=1.0.6", 859 | "phpunit/phpunit-mock-objects": ">=3.0", 860 | "sebastian/comparator": "~1.1", 861 | "sebastian/diff": "~1.2", 862 | "sebastian/environment": "~1.3", 863 | "sebastian/exporter": "~1.2", 864 | "sebastian/global-state": "~1.0", 865 | "sebastian/resource-operations": "~1.0", 866 | "sebastian/version": "~1.0", 867 | "symfony/yaml": "~2.1|~3.0" 868 | }, 869 | "suggest": { 870 | "phpunit/php-invoker": "~1.1" 871 | }, 872 | "bin": [ 873 | "phpunit" 874 | ], 875 | "type": "library", 876 | "extra": { 877 | "branch-alias": { 878 | "dev-master": "5.0.x-dev" 879 | } 880 | }, 881 | "autoload": { 882 | "classmap": [ 883 | "src/" 884 | ] 885 | }, 886 | "notification-url": "https://packagist.org/downloads/", 887 | "license": [ 888 | "BSD-3-Clause" 889 | ], 890 | "authors": [ 891 | { 892 | "name": "Sebastian Bergmann", 893 | "email": "sebastian@phpunit.de", 894 | "role": "lead" 895 | } 896 | ], 897 | "description": "The PHP Unit Testing framework.", 898 | "homepage": "https://phpunit.de/", 899 | "keywords": [ 900 | "phpunit", 901 | "testing", 902 | "xunit" 903 | ], 904 | "time": "2015-11-30 08:33:35" 905 | }, 906 | { 907 | "name": "phpunit/phpunit-mock-objects", 908 | "version": "3.0.4", 909 | "source": { 910 | "type": "git", 911 | "url": "https://github.com/sebastianbergmann/phpunit-mock-objects.git", 912 | "reference": "b28b029356e65091dfbf8c2bc4ef106ffece509e" 913 | }, 914 | "dist": { 915 | "type": "zip", 916 | "url": "https://api.github.com/repos/sebastianbergmann/phpunit-mock-objects/zipball/b28b029356e65091dfbf8c2bc4ef106ffece509e", 917 | "reference": "b28b029356e65091dfbf8c2bc4ef106ffece509e", 918 | "shasum": "" 919 | }, 920 | "require": { 921 | "doctrine/instantiator": "^1.0.2", 922 | "php": ">=5.6", 923 | "phpunit/php-text-template": "~1.2", 924 | "sebastian/exporter": "~1.2" 925 | }, 926 | "require-dev": { 927 | "phpunit/phpunit": "~5" 928 | }, 929 | "suggest": { 930 | "ext-soap": "*" 931 | }, 932 | "type": "library", 933 | "extra": { 934 | "branch-alias": { 935 | "dev-master": "3.0.x-dev" 936 | } 937 | }, 938 | "autoload": { 939 | "classmap": [ 940 | "src/" 941 | ] 942 | }, 943 | "notification-url": "https://packagist.org/downloads/", 944 | "license": [ 945 | "BSD-3-Clause" 946 | ], 947 | "authors": [ 948 | { 949 | "name": "Sebastian Bergmann", 950 | "email": "sb@sebastian-bergmann.de", 951 | "role": "lead" 952 | } 953 | ], 954 | "description": "Mock Object library for PHPUnit", 955 | "homepage": "https://github.com/sebastianbergmann/phpunit-mock-objects/", 956 | "keywords": [ 957 | "mock", 958 | "xunit" 959 | ], 960 | "time": "2015-11-09 15:37:17" 961 | }, 962 | { 963 | "name": "sebastian/comparator", 964 | "version": "1.2.0", 965 | "source": { 966 | "type": "git", 967 | "url": "https://github.com/sebastianbergmann/comparator.git", 968 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22" 969 | }, 970 | "dist": { 971 | "type": "zip", 972 | "url": "https://api.github.com/repos/sebastianbergmann/comparator/zipball/937efb279bd37a375bcadf584dec0726f84dbf22", 973 | "reference": "937efb279bd37a375bcadf584dec0726f84dbf22", 974 | "shasum": "" 975 | }, 976 | "require": { 977 | "php": ">=5.3.3", 978 | "sebastian/diff": "~1.2", 979 | "sebastian/exporter": "~1.2" 980 | }, 981 | "require-dev": { 982 | "phpunit/phpunit": "~4.4" 983 | }, 984 | "type": "library", 985 | "extra": { 986 | "branch-alias": { 987 | "dev-master": "1.2.x-dev" 988 | } 989 | }, 990 | "autoload": { 991 | "classmap": [ 992 | "src/" 993 | ] 994 | }, 995 | "notification-url": "https://packagist.org/downloads/", 996 | "license": [ 997 | "BSD-3-Clause" 998 | ], 999 | "authors": [ 1000 | { 1001 | "name": "Jeff Welch", 1002 | "email": "whatthejeff@gmail.com" 1003 | }, 1004 | { 1005 | "name": "Volker Dusch", 1006 | "email": "github@wallbash.com" 1007 | }, 1008 | { 1009 | "name": "Bernhard Schussek", 1010 | "email": "bschussek@2bepublished.at" 1011 | }, 1012 | { 1013 | "name": "Sebastian Bergmann", 1014 | "email": "sebastian@phpunit.de" 1015 | } 1016 | ], 1017 | "description": "Provides the functionality to compare PHP values for equality", 1018 | "homepage": "http://www.github.com/sebastianbergmann/comparator", 1019 | "keywords": [ 1020 | "comparator", 1021 | "compare", 1022 | "equality" 1023 | ], 1024 | "time": "2015-07-26 15:48:44" 1025 | }, 1026 | { 1027 | "name": "sebastian/diff", 1028 | "version": "1.3.0", 1029 | "source": { 1030 | "type": "git", 1031 | "url": "https://github.com/sebastianbergmann/diff.git", 1032 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3" 1033 | }, 1034 | "dist": { 1035 | "type": "zip", 1036 | "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/863df9687835c62aa423a22412d26fa2ebde3fd3", 1037 | "reference": "863df9687835c62aa423a22412d26fa2ebde3fd3", 1038 | "shasum": "" 1039 | }, 1040 | "require": { 1041 | "php": ">=5.3.3" 1042 | }, 1043 | "require-dev": { 1044 | "phpunit/phpunit": "~4.2" 1045 | }, 1046 | "type": "library", 1047 | "extra": { 1048 | "branch-alias": { 1049 | "dev-master": "1.3-dev" 1050 | } 1051 | }, 1052 | "autoload": { 1053 | "classmap": [ 1054 | "src/" 1055 | ] 1056 | }, 1057 | "notification-url": "https://packagist.org/downloads/", 1058 | "license": [ 1059 | "BSD-3-Clause" 1060 | ], 1061 | "authors": [ 1062 | { 1063 | "name": "Kore Nordmann", 1064 | "email": "mail@kore-nordmann.de" 1065 | }, 1066 | { 1067 | "name": "Sebastian Bergmann", 1068 | "email": "sebastian@phpunit.de" 1069 | } 1070 | ], 1071 | "description": "Diff implementation", 1072 | "homepage": "http://www.github.com/sebastianbergmann/diff", 1073 | "keywords": [ 1074 | "diff" 1075 | ], 1076 | "time": "2015-02-22 15:13:53" 1077 | }, 1078 | { 1079 | "name": "sebastian/environment", 1080 | "version": "1.3.2", 1081 | "source": { 1082 | "type": "git", 1083 | "url": "https://github.com/sebastianbergmann/environment.git", 1084 | "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44" 1085 | }, 1086 | "dist": { 1087 | "type": "zip", 1088 | "url": "https://api.github.com/repos/sebastianbergmann/environment/zipball/6324c907ce7a52478eeeaede764f48733ef5ae44", 1089 | "reference": "6324c907ce7a52478eeeaede764f48733ef5ae44", 1090 | "shasum": "" 1091 | }, 1092 | "require": { 1093 | "php": ">=5.3.3" 1094 | }, 1095 | "require-dev": { 1096 | "phpunit/phpunit": "~4.4" 1097 | }, 1098 | "type": "library", 1099 | "extra": { 1100 | "branch-alias": { 1101 | "dev-master": "1.3.x-dev" 1102 | } 1103 | }, 1104 | "autoload": { 1105 | "classmap": [ 1106 | "src/" 1107 | ] 1108 | }, 1109 | "notification-url": "https://packagist.org/downloads/", 1110 | "license": [ 1111 | "BSD-3-Clause" 1112 | ], 1113 | "authors": [ 1114 | { 1115 | "name": "Sebastian Bergmann", 1116 | "email": "sebastian@phpunit.de" 1117 | } 1118 | ], 1119 | "description": "Provides functionality to handle HHVM/PHP environments", 1120 | "homepage": "http://www.github.com/sebastianbergmann/environment", 1121 | "keywords": [ 1122 | "Xdebug", 1123 | "environment", 1124 | "hhvm" 1125 | ], 1126 | "time": "2015-08-03 06:14:51" 1127 | }, 1128 | { 1129 | "name": "sebastian/exporter", 1130 | "version": "1.2.1", 1131 | "source": { 1132 | "type": "git", 1133 | "url": "https://github.com/sebastianbergmann/exporter.git", 1134 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e" 1135 | }, 1136 | "dist": { 1137 | "type": "zip", 1138 | "url": "https://api.github.com/repos/sebastianbergmann/exporter/zipball/7ae5513327cb536431847bcc0c10edba2701064e", 1139 | "reference": "7ae5513327cb536431847bcc0c10edba2701064e", 1140 | "shasum": "" 1141 | }, 1142 | "require": { 1143 | "php": ">=5.3.3", 1144 | "sebastian/recursion-context": "~1.0" 1145 | }, 1146 | "require-dev": { 1147 | "phpunit/phpunit": "~4.4" 1148 | }, 1149 | "type": "library", 1150 | "extra": { 1151 | "branch-alias": { 1152 | "dev-master": "1.2.x-dev" 1153 | } 1154 | }, 1155 | "autoload": { 1156 | "classmap": [ 1157 | "src/" 1158 | ] 1159 | }, 1160 | "notification-url": "https://packagist.org/downloads/", 1161 | "license": [ 1162 | "BSD-3-Clause" 1163 | ], 1164 | "authors": [ 1165 | { 1166 | "name": "Jeff Welch", 1167 | "email": "whatthejeff@gmail.com" 1168 | }, 1169 | { 1170 | "name": "Volker Dusch", 1171 | "email": "github@wallbash.com" 1172 | }, 1173 | { 1174 | "name": "Bernhard Schussek", 1175 | "email": "bschussek@2bepublished.at" 1176 | }, 1177 | { 1178 | "name": "Sebastian Bergmann", 1179 | "email": "sebastian@phpunit.de" 1180 | }, 1181 | { 1182 | "name": "Adam Harvey", 1183 | "email": "aharvey@php.net" 1184 | } 1185 | ], 1186 | "description": "Provides the functionality to export PHP variables for visualization", 1187 | "homepage": "http://www.github.com/sebastianbergmann/exporter", 1188 | "keywords": [ 1189 | "export", 1190 | "exporter" 1191 | ], 1192 | "time": "2015-06-21 07:55:53" 1193 | }, 1194 | { 1195 | "name": "sebastian/global-state", 1196 | "version": "1.1.1", 1197 | "source": { 1198 | "type": "git", 1199 | "url": "https://github.com/sebastianbergmann/global-state.git", 1200 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4" 1201 | }, 1202 | "dist": { 1203 | "type": "zip", 1204 | "url": "https://api.github.com/repos/sebastianbergmann/global-state/zipball/bc37d50fea7d017d3d340f230811c9f1d7280af4", 1205 | "reference": "bc37d50fea7d017d3d340f230811c9f1d7280af4", 1206 | "shasum": "" 1207 | }, 1208 | "require": { 1209 | "php": ">=5.3.3" 1210 | }, 1211 | "require-dev": { 1212 | "phpunit/phpunit": "~4.2" 1213 | }, 1214 | "suggest": { 1215 | "ext-uopz": "*" 1216 | }, 1217 | "type": "library", 1218 | "extra": { 1219 | "branch-alias": { 1220 | "dev-master": "1.0-dev" 1221 | } 1222 | }, 1223 | "autoload": { 1224 | "classmap": [ 1225 | "src/" 1226 | ] 1227 | }, 1228 | "notification-url": "https://packagist.org/downloads/", 1229 | "license": [ 1230 | "BSD-3-Clause" 1231 | ], 1232 | "authors": [ 1233 | { 1234 | "name": "Sebastian Bergmann", 1235 | "email": "sebastian@phpunit.de" 1236 | } 1237 | ], 1238 | "description": "Snapshotting of global state", 1239 | "homepage": "http://www.github.com/sebastianbergmann/global-state", 1240 | "keywords": [ 1241 | "global state" 1242 | ], 1243 | "time": "2015-10-12 03:26:01" 1244 | }, 1245 | { 1246 | "name": "sebastian/recursion-context", 1247 | "version": "1.0.1", 1248 | "source": { 1249 | "type": "git", 1250 | "url": "https://github.com/sebastianbergmann/recursion-context.git", 1251 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba" 1252 | }, 1253 | "dist": { 1254 | "type": "zip", 1255 | "url": "https://api.github.com/repos/sebastianbergmann/recursion-context/zipball/994d4a811bafe801fb06dccbee797863ba2792ba", 1256 | "reference": "994d4a811bafe801fb06dccbee797863ba2792ba", 1257 | "shasum": "" 1258 | }, 1259 | "require": { 1260 | "php": ">=5.3.3" 1261 | }, 1262 | "require-dev": { 1263 | "phpunit/phpunit": "~4.4" 1264 | }, 1265 | "type": "library", 1266 | "extra": { 1267 | "branch-alias": { 1268 | "dev-master": "1.0.x-dev" 1269 | } 1270 | }, 1271 | "autoload": { 1272 | "classmap": [ 1273 | "src/" 1274 | ] 1275 | }, 1276 | "notification-url": "https://packagist.org/downloads/", 1277 | "license": [ 1278 | "BSD-3-Clause" 1279 | ], 1280 | "authors": [ 1281 | { 1282 | "name": "Jeff Welch", 1283 | "email": "whatthejeff@gmail.com" 1284 | }, 1285 | { 1286 | "name": "Sebastian Bergmann", 1287 | "email": "sebastian@phpunit.de" 1288 | }, 1289 | { 1290 | "name": "Adam Harvey", 1291 | "email": "aharvey@php.net" 1292 | } 1293 | ], 1294 | "description": "Provides functionality to recursively process PHP variables", 1295 | "homepage": "http://www.github.com/sebastianbergmann/recursion-context", 1296 | "time": "2015-06-21 08:04:50" 1297 | }, 1298 | { 1299 | "name": "sebastian/resource-operations", 1300 | "version": "1.0.0", 1301 | "source": { 1302 | "type": "git", 1303 | "url": "https://github.com/sebastianbergmann/resource-operations.git", 1304 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52" 1305 | }, 1306 | "dist": { 1307 | "type": "zip", 1308 | "url": "https://api.github.com/repos/sebastianbergmann/resource-operations/zipball/ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1309 | "reference": "ce990bb21759f94aeafd30209e8cfcdfa8bc3f52", 1310 | "shasum": "" 1311 | }, 1312 | "require": { 1313 | "php": ">=5.6.0" 1314 | }, 1315 | "type": "library", 1316 | "extra": { 1317 | "branch-alias": { 1318 | "dev-master": "1.0.x-dev" 1319 | } 1320 | }, 1321 | "autoload": { 1322 | "classmap": [ 1323 | "src/" 1324 | ] 1325 | }, 1326 | "notification-url": "https://packagist.org/downloads/", 1327 | "license": [ 1328 | "BSD-3-Clause" 1329 | ], 1330 | "authors": [ 1331 | { 1332 | "name": "Sebastian Bergmann", 1333 | "email": "sebastian@phpunit.de" 1334 | } 1335 | ], 1336 | "description": "Provides a list of PHP built-in functions that operate on resources", 1337 | "homepage": "https://www.github.com/sebastianbergmann/resource-operations", 1338 | "time": "2015-07-28 20:34:47" 1339 | }, 1340 | { 1341 | "name": "sebastian/version", 1342 | "version": "1.0.6", 1343 | "source": { 1344 | "type": "git", 1345 | "url": "https://github.com/sebastianbergmann/version.git", 1346 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6" 1347 | }, 1348 | "dist": { 1349 | "type": "zip", 1350 | "url": "https://api.github.com/repos/sebastianbergmann/version/zipball/58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1351 | "reference": "58b3a85e7999757d6ad81c787a1fbf5ff6c628c6", 1352 | "shasum": "" 1353 | }, 1354 | "type": "library", 1355 | "autoload": { 1356 | "classmap": [ 1357 | "src/" 1358 | ] 1359 | }, 1360 | "notification-url": "https://packagist.org/downloads/", 1361 | "license": [ 1362 | "BSD-3-Clause" 1363 | ], 1364 | "authors": [ 1365 | { 1366 | "name": "Sebastian Bergmann", 1367 | "email": "sebastian@phpunit.de", 1368 | "role": "lead" 1369 | } 1370 | ], 1371 | "description": "Library that helps with managing the version number of Git-hosted PHP projects", 1372 | "homepage": "https://github.com/sebastianbergmann/version", 1373 | "time": "2015-06-21 13:59:46" 1374 | }, 1375 | { 1376 | "name": "symfony/yaml", 1377 | "version": "v3.0.0", 1378 | "source": { 1379 | "type": "git", 1380 | "url": "https://github.com/symfony/yaml.git", 1381 | "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002" 1382 | }, 1383 | "dist": { 1384 | "type": "zip", 1385 | "url": "https://api.github.com/repos/symfony/yaml/zipball/177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", 1386 | "reference": "177a015cb0e19ff4a49e0e2e2c5fc1c1bee07002", 1387 | "shasum": "" 1388 | }, 1389 | "require": { 1390 | "php": ">=5.5.9" 1391 | }, 1392 | "type": "library", 1393 | "extra": { 1394 | "branch-alias": { 1395 | "dev-master": "3.0-dev" 1396 | } 1397 | }, 1398 | "autoload": { 1399 | "psr-4": { 1400 | "Symfony\\Component\\Yaml\\": "" 1401 | }, 1402 | "exclude-from-classmap": [ 1403 | "/Tests/" 1404 | ] 1405 | }, 1406 | "notification-url": "https://packagist.org/downloads/", 1407 | "license": [ 1408 | "MIT" 1409 | ], 1410 | "authors": [ 1411 | { 1412 | "name": "Fabien Potencier", 1413 | "email": "fabien@symfony.com" 1414 | }, 1415 | { 1416 | "name": "Symfony Community", 1417 | "homepage": "https://symfony.com/contributors" 1418 | } 1419 | ], 1420 | "description": "Symfony Yaml Component", 1421 | "homepage": "https://symfony.com", 1422 | "time": "2015-11-30 12:36:17" 1423 | } 1424 | ], 1425 | "aliases": [], 1426 | "minimum-stability": "beta", 1427 | "stability-flags": [], 1428 | "prefer-stable": false, 1429 | "prefer-lowest": false, 1430 | "platform": { 1431 | "php": ">=5.4.0" 1432 | }, 1433 | "platform-dev": [] 1434 | } 1435 | -------------------------------------------------------------------------------- /phpunit.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | 16 | 17 | 18 | tests 19 | 20 | 21 | 22 | 23 | 24 | src 25 | 26 | src/**/Resources 27 | src/**/Exceptions 28 | 29 | 30 | 31 | 32 | 33 | 34 | integration 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Encoders/AbstractEncoder.php: -------------------------------------------------------------------------------- 1 | data = $data; 19 | } 20 | } -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Encoders/AbstractFilesystemEncoder.php: -------------------------------------------------------------------------------- 1 | filesystem = new Filesystem(); 21 | } 22 | 23 | public function __destruct() 24 | { 25 | $this->cleanup(); 26 | } 27 | 28 | /** 29 | * Get a temporary file and populate its data 30 | * 31 | * Any files created with this function will be destroyed on cleanup/destruction. 32 | * 33 | * @param string $data Data to populate file with 34 | * @param string $prefix Optional file prefix 35 | * @return string 36 | */ 37 | protected function getTempFile($data = null, $prefix = 'img-mngr-') 38 | { 39 | $file = tempnam(sys_get_temp_dir(), $prefix); 40 | if ($data) { 41 | file_put_contents($file, $data); 42 | } else { 43 | $this->filesystem->touch($file); 44 | } 45 | 46 | $this->temp_files[] = $file; 47 | return $file; 48 | } 49 | 50 | /** 51 | * Remove all temp files 52 | */ 53 | protected function cleanup() 54 | { 55 | foreach ($this->temp_files as $file) { 56 | $this->filesystem->remove($file); 57 | } 58 | $this->temp_files = []; 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Encoders/EncoderInterface.php: -------------------------------------------------------------------------------- 1 | filter = $filter; 31 | $this->resolution = $resolution; 32 | } 33 | 34 | /** 35 | * Check if we support this data-type. 36 | * 37 | * @param string $data 38 | * 39 | * @return bool 40 | */ 41 | public function supports(&$data) 42 | { 43 | $inspector = new DataInspector(); 44 | 45 | // Normal image formats supported 46 | if ($inspector->getImageFormat($data) !== null) { 47 | return true; 48 | } 49 | 50 | // PDF supported 51 | if ($inspector->isPdf($data)) { 52 | return true; 53 | } 54 | 55 | return false; 56 | } 57 | 58 | /** 59 | * {@inheritdoc} 60 | */ 61 | public function createVariation(ImageFormat $output_format, $quality, 62 | ImageDimensions $dimensions = null, ImageCropDimensions $crop_dimensions = null) 63 | { 64 | $src = $this->getTempFile($this->data); 65 | $img = new \Imagick(); 66 | $img->setResolution($this->resolution, $this->resolution); 67 | $img->readImage($src); 68 | $img->setIteratorIndex(0); 69 | 70 | // Flatten images here helps the encoder to get rid of the black background 71 | // that appears on encoded image files. 72 | $img = $img->flattenImages(); 73 | $img->setImageFormat((string) $output_format->value()); 74 | $img->setImageCompressionQuality($quality); 75 | 76 | if (null !== $crop_dimensions) { 77 | $img->cropImage( 78 | $crop_dimensions->getWidth(), 79 | $crop_dimensions->getHeight(), 80 | $crop_dimensions->getX(), 81 | $crop_dimensions->getY() 82 | ); 83 | } 84 | 85 | if (null !== $dimensions) { 86 | $img->resizeImage( 87 | $dimensions->getWidth() ?: 0, 88 | $dimensions->getHeight() ?: 0, 89 | $this->filter, 90 | 1, 91 | false 92 | ); 93 | } 94 | 95 | return $img->getImageBlob(); 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Encoders/InterventionEncoder.php: -------------------------------------------------------------------------------- 1 | getImageFormat($data) !== null; 26 | } 27 | 28 | /** 29 | * {@inheritdoc} 30 | */ 31 | public function createVariation(ImageFormat $output_format, $quality, 32 | ImageDimensions $dimensions = null, ImageCropDimensions $crop_dimensions = null) 33 | { 34 | try { 35 | $img = new InterventionImage($this->data); 36 | } catch (\Intervention\Image\Exception\InvalidImageDataStringException $e) { 37 | throw new BadImageException('Bad image data', 0, $e); 38 | } catch (\Intervention\Image\Exception\ImageNotFoundException $e) { 39 | throw new BadImageException('Not an image', 0, $e); 40 | } 41 | 42 | if ($dimensions) { 43 | if ($dimensions->getGrab()) { 44 | $img->grab($dimensions->getWidth(), $dimensions->getHeight()); 45 | } else { 46 | $img->resize( 47 | $dimensions->getWidth(), 48 | $dimensions->getHeight(), 49 | $dimensions->getMaintainRatio(), 50 | $dimensions->canUpscale() 51 | ); 52 | } 53 | } 54 | 55 | return $img->encode($output_format->key(), $quality); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/Image.php: -------------------------------------------------------------------------------- 1 | key = $key; 47 | } 48 | 49 | /** 50 | * Flush image data from memory. 51 | * 52 | * @param bool $collect_garbage 53 | */ 54 | public function flush($collect_garbage = true) 55 | { 56 | $this->data = null; 57 | 58 | if ($collect_garbage) { 59 | gc_collect_cycles(); 60 | } 61 | } 62 | 63 | /** 64 | * Check the data data for the image type. 65 | * 66 | * If unknown or no data data is present, null will be returned 67 | * 68 | * @deprecated since 1.1.0 Use DataInspector::getImageFormat() instead 69 | * 70 | * @return ImageFormat|null 71 | */ 72 | public function getDataFormat() 73 | { 74 | $inspector = new DataInspector(); 75 | 76 | return $inspector->getImageFormat($this->data); 77 | } 78 | 79 | /** 80 | * Set the remote key. 81 | * 82 | * @param string $key 83 | * 84 | * @return $this 85 | */ 86 | public function setKey($key) 87 | { 88 | $this->key = $key; 89 | $this->persistent = false; 90 | 91 | return $this; 92 | } 93 | 94 | /** 95 | * Get the remote key. 96 | * 97 | * @return string 98 | */ 99 | public function getKey() 100 | { 101 | return $this->key; 102 | } 103 | 104 | /** 105 | * Check if the image is known to exist on the remote. 106 | * 107 | * @return bool 108 | */ 109 | public function isPersistent() 110 | { 111 | return $this->persistent; 112 | } 113 | 114 | /** 115 | * Check if the image data has been loaded. 116 | * 117 | * @return bool 118 | */ 119 | public function isHydrated() 120 | { 121 | return !empty($this->data) && !is_null($this->data); 122 | } 123 | 124 | /** 125 | * Set image data. 126 | * 127 | * @param string $data 128 | * 129 | * @return $this 130 | */ 131 | public function setData($data) 132 | { 133 | $this->data = $data; 134 | $this->persistent = false; 135 | 136 | // Guess MIME-type 137 | $inspector = new DataInspector(); 138 | $this->raw_content_type = $inspector->guessMimeType($this->data); 139 | 140 | return $this; 141 | } 142 | 143 | /** 144 | * Return the content-type of data specified. 145 | * 146 | * @return string 147 | */ 148 | public function getMimeType() 149 | { 150 | return $this->raw_content_type; 151 | } 152 | 153 | /** 154 | * Get image data. 155 | * 156 | * @return string 157 | */ 158 | public function getData() 159 | { 160 | return $this->data; 161 | } 162 | } 163 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/ImageCropDimensions.php: -------------------------------------------------------------------------------- 1 | width = $width; 39 | $this->height = $height; 40 | $this->x = $x; 41 | $this->y = $y; 42 | } 43 | 44 | /** 45 | * Creates a signature containing the crop-dimension specification. 46 | * 47 | * @return string 48 | */ 49 | public function __toString() 50 | { 51 | return 'x'.($this->getX() ?: '-'). 52 | 'y'.($this->getY() ?: '-'). 53 | 'w'.($this->getWidth() ?: '-'). 54 | 'h'.($this->getHeight() ?: '-'); 55 | } 56 | 57 | /** 58 | * Set the start pixel in the x-axis for the horizontal crop of the image. 59 | * 60 | * @param int $x 61 | * 62 | * @return $this 63 | */ 64 | public function setX($x) 65 | { 66 | $this->x = $x; 67 | 68 | return $this; 69 | } 70 | 71 | /** 72 | * Set the start pixel in the y-axis for the vertical crop of the image. 73 | * 74 | * @param int $y 75 | * 76 | * @return $this 77 | */ 78 | public function setY($y) 79 | { 80 | $this->y = $y; 81 | 82 | return $this; 83 | } 84 | 85 | /** 86 | * Sets the horizontal boundary of the cropping dimensions taking only an 87 | * integer relative to the left of the image in pixels, starting from a zero-indexed row. 88 | * 89 | * @param int $width 90 | * 91 | * @return $this 92 | */ 93 | public function setWidth($width) 94 | { 95 | $this->width = $width; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Sets the vertical boundary of the cropping dimensions taking only an 102 | * integer relative to the top of the image in pixels, starting from a zero-indexed row. 103 | * 104 | * @param int $height 105 | * 106 | * @return $this 107 | */ 108 | public function setHeight($height) 109 | { 110 | $this->height = $height; 111 | 112 | return $this; 113 | } 114 | 115 | /** 116 | * Get $x crop start pixel in the x-axis of the image. 117 | * 118 | * @return int 119 | */ 120 | public function getX() 121 | { 122 | return $this->x; 123 | } 124 | 125 | /** 126 | * Get $y crop start pixel in the x-axis of the image. 127 | * 128 | * @return int 129 | */ 130 | public function getY() 131 | { 132 | return $this->y; 133 | } 134 | 135 | /** 136 | * Get crop width. 137 | * 138 | * @return int 139 | */ 140 | public function getWidth() 141 | { 142 | return $this->width; 143 | } 144 | 145 | /** 146 | * Get crop height. 147 | * 148 | * @return int 149 | */ 150 | public function getHeight() 151 | { 152 | return $this->height; 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/ImageDimensions.php: -------------------------------------------------------------------------------- 1 | width = $width; 48 | $this->height = $height; 49 | $this->upscale = $upscale; 50 | $this->maintain_ratio = $maintain_ratio; 51 | $this->grab = $grab; 52 | } 53 | 54 | /** 55 | * Creates a signature containing the dimension specification. 56 | * 57 | * @return string 58 | */ 59 | public function __toString() 60 | { 61 | return 'x'.($this->getWidth() ?: '-'). 62 | 'y'.($this->getHeight() ?: '-'). 63 | 'u'.($this->canUpscale() ? '1' : '0'). 64 | 'r'.($this->getMaintainRatio() ? '1' : '0'). 65 | 'g'.($this->getGrab() ? 'g' : '0'); 66 | } 67 | 68 | /** 69 | * Get proposed width. 70 | * 71 | * @return int 72 | */ 73 | public function getWidth() 74 | { 75 | return $this->width; 76 | } 77 | 78 | /** 79 | * Get proposed height. 80 | * 81 | * @return int 82 | */ 83 | public function getHeight() 84 | { 85 | return $this->height; 86 | } 87 | 88 | /** 89 | * Get image aspect ratio based on the width and height 90 | * provided. 91 | * 92 | * Function uses binary calculator division to 3 decimal places. 93 | * 94 | * @return string 95 | */ 96 | public function getAspectRatio() 97 | { 98 | return bcdiv($this->width, $this->height, 3); 99 | } 100 | 101 | /** 102 | * Check if the image can be upscaled. 103 | * 104 | * @return bool 105 | */ 106 | public function canUpscale() 107 | { 108 | return $this->upscale; 109 | } 110 | 111 | /** 112 | * Check if we should maintain the image ratio. 113 | * 114 | * @return bool 115 | */ 116 | public function getMaintainRatio() 117 | { 118 | return $this->maintain_ratio; 119 | } 120 | 121 | /** 122 | * Check if also crop as well as resize. 123 | * 124 | * @return bool 125 | */ 126 | public function getGrab() 127 | { 128 | return $this->grab; 129 | } 130 | 131 | /** 132 | * {@inheritdoc} 133 | */ 134 | public function serialise() 135 | { 136 | return json_encode([ 137 | 'width' => $this->getWidth(), 138 | 'height' => $this->getHeight(), 139 | 'upscale' => (bool) $this->canUpscale(), 140 | 'grab' => (bool) $this->getGrab(), 141 | 'maintain-ratio' => (bool) $this->getMaintainRatio(), 142 | ]); 143 | } 144 | 145 | /** 146 | * {@inheritdoc} 147 | */ 148 | public static function deserialise($json) 149 | { 150 | if (empty($json)) { 151 | throw \InvalidArgumentException('Json string is empty'); 152 | } 153 | 154 | $object_data = json_decode($json, true); 155 | 156 | $instance = new static( 157 | isset($object_data['width']) ? $object_data['width'] : null, 158 | isset($object_data['height']) ? $object_data['height'] : null, 159 | isset($object_data['maintain-ratio']) ? $object_data['maintain-ratio'] : null, 160 | isset($object_data['upscale']) ? $object_data['upscale'] : null, 161 | isset($object_data['grab']) ? $object_data['grab'] : null 162 | ); 163 | 164 | return $instance; 165 | } 166 | } 167 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/ImageMetadata.php: -------------------------------------------------------------------------------- 1 | mimetype; 60 | } 61 | 62 | /** 63 | * Sets the Internet media type. 64 | * 65 | * @param string $mimetype the mimetype 66 | * 67 | * @return self 68 | */ 69 | public function setMimetype($mimetype) 70 | { 71 | $this->mimetype = $mimetype; 72 | 73 | return $this; 74 | } 75 | 76 | /** 77 | * Gets the ImageManager format. 78 | * 79 | * @return ImageFormat 80 | */ 81 | public function getFormat() 82 | { 83 | return $this->format; 84 | } 85 | 86 | /** 87 | * Sets the ImageManager format. 88 | * 89 | * @param ImageFormat $format the format 90 | * 91 | * @return self 92 | */ 93 | public function setFormat(ImageFormat $format) 94 | { 95 | $this->format = $format; 96 | 97 | return $this; 98 | } 99 | 100 | /** 101 | * Gets the Image resolution. 102 | * 103 | * @return ImageDimensions 104 | */ 105 | public function getResolution() 106 | { 107 | return $this->resolution; 108 | } 109 | 110 | /** 111 | * Sets the Image resolution. 112 | * 113 | * @param ImageDimensions 114 | * 115 | * @return self 116 | */ 117 | public function setResolution(ImageDimensions $resolution) 118 | { 119 | $this->resolution = $resolution; 120 | 121 | return $this; 122 | } 123 | 124 | /** 125 | * Gets the Orientation of the image. 126 | * 127 | * @return ImageOrientation 128 | */ 129 | public function getOrientation() 130 | { 131 | return $this->orientation; 132 | } 133 | 134 | /** 135 | * Sets the Orientation of the image. 136 | * 137 | * @param ImageOrientation $orientation the orientation 138 | * 139 | * @return self 140 | */ 141 | public function setOrientation(ImageOrientation $orientation) 142 | { 143 | $this->orientation = $orientation; 144 | 145 | return $this; 146 | } 147 | 148 | /** 149 | * Gets the Source image dimensions. 150 | * 151 | * @return ImageDimensions 152 | */ 153 | public function getDimensions() 154 | { 155 | return $this->dimensions; 156 | } 157 | 158 | /** 159 | * Sets the Source image dimensions. 160 | * 161 | * @param ImageDimensions $dimensions the dimensions 162 | * 163 | * @return self 164 | */ 165 | public function setDimensions(ImageDimensions $dimensions) 166 | { 167 | $this->dimensions = $dimensions; 168 | 169 | return $this; 170 | } 171 | 172 | /** 173 | * {@inheritdoc} 174 | */ 175 | public function serialise() 176 | { 177 | return json_encode([ 178 | 'mimetype' => $this->getMimetype(), 179 | 'format' => $this->getFormat()->value(), 180 | 'resolution' => $this->getResolution()->serialise(), 181 | 'orientation' => $this->getOrientation()->value(), 182 | 'dimensions' => $this->getDimensions()->serialise(), 183 | ]); 184 | } 185 | 186 | /** 187 | * {@inheritdoc} 188 | */ 189 | public static function deserialise($json) 190 | { 191 | if (empty($json)) { 192 | throw new InvalidImageMetadataException(); 193 | } 194 | 195 | $object_data = json_decode($json, true); 196 | 197 | // MIME-type is an minimum requirement for metadata 198 | if (!isset($object_data['mimetype'])) { 199 | throw new InvalidImageMetadataException(); 200 | } 201 | 202 | $instance = new static(); 203 | $instance 204 | ->setMimeType($object_data['mimetype']) 205 | ->setFormat(isset($object_data['format']) ? ImageFormat::memberByValue($object_data['format']) : null) 206 | ->setResolution(isset($object_data['resolution']) ? ImageDimensions::deserialise($object_data['resolution']) : null) 207 | ->setOrientation(isset($object_data['orientation']) ? ImageOrientation::memberByValue($object_data['orientation']) : null) 208 | ->setDimensions(isset($object_data['dimensions']) ? ImageDimensions::deserialise($object_data['dimensions']) : null) 209 | ; 210 | 211 | return $instance; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/ImageVariation.php: -------------------------------------------------------------------------------- 1 | dimensions = $dimensions; 37 | $this->format = $format; 38 | $this->quality = $quality; 39 | $this->crop_dimensions = $crop_dimensions; 40 | } 41 | 42 | /** 43 | * Get variation or image key. 44 | * 45 | * @param bool $parent 46 | * 47 | * @return string 48 | */ 49 | public function getKey($parent = false) 50 | { 51 | if ($parent) { 52 | return parent::getKey(); 53 | } else { 54 | // TODO: add a configurable naming scheme here 55 | return parent::getKey().'~'.$this; 56 | } 57 | } 58 | 59 | /** 60 | * Get Dimensions. 61 | * 62 | * @return ImageDimensions 63 | */ 64 | public function getDimensions() 65 | { 66 | return $this->dimensions; 67 | } 68 | 69 | /** 70 | * Get Crop Dimensions. 71 | * 72 | * @return ImageCropDimensions 73 | */ 74 | public function getCropDimensions() 75 | { 76 | return $this->crop_dimensions; 77 | } 78 | 79 | /** 80 | * Get Format. 81 | * 82 | * @return ImageFormat 83 | */ 84 | public function getFormat() 85 | { 86 | return $this->format; 87 | } 88 | 89 | /** 90 | * Get Quality. 91 | * 92 | * @return int 93 | */ 94 | public function getQuality() 95 | { 96 | return $this->quality; 97 | } 98 | 99 | /** 100 | * Creates a signature based on the variations applied. 101 | * 102 | * @return string 103 | */ 104 | public function __toString() 105 | { 106 | $out = 'q'.($this->getQuality() ?: self::DEFAULT_QUALITY). 107 | ',d'.($this->getDimensions() ?: '--'). 108 | ',c'.($this->getCropDimensions() ?: '--'). 109 | '.'.(string) $this->getFormat()->value(); 110 | 111 | return $out; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Entities/Interfaces/SerialisableInterface.php: -------------------------------------------------------------------------------- 1 | buffer($data); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Services/ImageInspector.php: -------------------------------------------------------------------------------- 1 | isHydrated()) { 31 | throw new ImageManagerException(ImageManager::ERR_NOT_HYDRATED); 32 | } 33 | 34 | $img = new \Imagick(); 35 | $img->readImageBlob($image->getData()); 36 | $width = $img->getImageWidth(); 37 | $height = $img->getImageHeight(); 38 | 39 | if ($width >= $height) { 40 | return ImageOrientation::LANDSCAPE(); 41 | } 42 | 43 | return ImageOrientation::PORTRAIT(); 44 | } 45 | 46 | /** 47 | * Return image dimensions based on the Image object provided 48 | * 49 | * @param Image $image 50 | * @return ImageDimensions 51 | */ 52 | protected function getImageDimensions(Image $image) 53 | { 54 | if (!$image->isHydrated()) { 55 | throw new ImageManagerException(ImageManager::ERR_NOT_HYDRATED); 56 | } 57 | 58 | $img = new \Imagick(); 59 | $img->readImageBlob($image->getData()); 60 | 61 | $dimensions = new ImageDimensions( 62 | $img->getImageWidth(), 63 | $img->getImageHeight() 64 | ); 65 | 66 | return $dimensions; 67 | } 68 | 69 | /** 70 | * Return image resolution based on the Image object provided 71 | * 72 | * NB: This function may return the image density or DPI, not it's output resolution. 73 | * 74 | * @param Image $image 75 | * @return ImageDimensions 76 | * @throws ImageManagerException 77 | */ 78 | protected function getImageResolution(Image $image) 79 | { 80 | if (!$image->isHydrated()) { 81 | throw new ImageManagerException(ImageManager::ERR_NOT_HYDRATED); 82 | } 83 | 84 | $img = new \Imagick(); 85 | $img->readImageBlob($image->getData()); 86 | $d = $img->getImageResolution(); 87 | 88 | $dimensions = new ImageDimensions($d['x'], $d['y']); 89 | 90 | return $dimensions; 91 | } 92 | 93 | /** 94 | * @param Image $image 95 | * @return ImageMetadata 96 | * @throws ImageManagerException 97 | */ 98 | public function getImageMetadata(Image $image) 99 | { 100 | if (!$image->isHydrated()) { 101 | throw new ImageManagerException(ImageManager::ERR_NOT_HYDRATED); 102 | } 103 | 104 | if ($image instanceof ImageVariation) { 105 | throw new ImageManagerException(self::ERR_SOURCE_IMAGE); 106 | } 107 | 108 | $metadata = new ImageMetadata(); 109 | $data_inspector = new DataInspector(); 110 | $data = $image->getData(); 111 | 112 | if ($data_inspector->isPdf($data)) { 113 | $format = ImageFormat::PDF(); 114 | } else { 115 | $format = $data_inspector->getImageFormat($data); 116 | } 117 | 118 | $metadata->setMimetype($data_inspector->guessMimeType($data)) 119 | ->setFormat($format) 120 | ->setResolution($this->getImageResolution($image)) 121 | ->setOrientation($this->getImageOrientation($image)) 122 | ->setDimensions($this->getImageDimensions($image)); 123 | 124 | return $metadata; 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Services/ImageManager.php: -------------------------------------------------------------------------------- 1 | filesystem = $filesystem; 97 | $this->cache_pool = $cache_pool; 98 | $this->encoders = $encoders; 99 | $this->validate_tags = $validate_tags; 100 | 101 | if (!$this->encoders) { 102 | $this->addEncoder(new InterventionEncoder()); 103 | } 104 | } 105 | 106 | /** 107 | * Push a local image/variation to the remote. 108 | * 109 | * If it is not hydrated this function will throw an exception 110 | * 111 | * @param Image $image 112 | * @param bool $overwrite 113 | * 114 | * @return $this 115 | * 116 | * @throws ImageManagerException 117 | * @throws ObjectAlreadyExistsException 118 | * @throws \Exception 119 | */ 120 | public function push(Image $image, $overwrite = true) 121 | { 122 | if (!$image->isHydrated() && ($image instanceof ImageVariation)) { 123 | // A pull on a variation will check if the variation exists, if not create it 124 | $this->pull($image); 125 | } 126 | 127 | if (!$image->isHydrated()) { 128 | throw new ImageManagerException(self::ERR_NOT_HYDRATED); 129 | } 130 | 131 | if (!$overwrite && $this->tagExists($image->getKey()) === true) { 132 | throw new ObjectAlreadyExistsException(self::ERR_ALREADY_EXISTS); 133 | } 134 | 135 | $adapter = $this->filesystem->getAdapter(); 136 | if ($adapter instanceof MetadataSupporter) { 137 | $metadata = []; 138 | if ($image->getMimeType()) { 139 | // Set image ContentType on remote filesystem 140 | $metadata['ContentType'] = $image->getMimeType(); 141 | } 142 | $adapter->setMetadata($image->getKey(), $metadata); 143 | } 144 | 145 | // Retrieve source image metadata 146 | $metadata = null; 147 | if (!($image instanceof ImageVariation)) { 148 | $image_manipulation = new ImageInspector(); 149 | $metadata = $image_manipulation->getImageMetadata($image); 150 | } 151 | 152 | try { 153 | $this->filesystem->write($image->getKey(), $image->getData(), $overwrite); 154 | $image->__friendSet('persistent', true); 155 | $this->tag($image->getKey(), $metadata); 156 | } catch (FileAlreadyExists $e) { 157 | $this->tag($image->getKey(), $metadata); 158 | throw new ObjectAlreadyExistsException(self::ERR_ALREADY_EXISTS); 159 | } 160 | 161 | return $this; 162 | } 163 | 164 | /** 165 | * Get an image/variation from the remote. 166 | * 167 | * If this image is a variation that does not exist, an attempt will be made to retrieve the parent first 168 | * then create the variation. The variation should be optionally pushed if it's Image::isPersistent() function 169 | * returns false. 170 | * 171 | * @param Image $image 172 | * 173 | * @return $this 174 | * 175 | * @throws ImageManagerException 176 | */ 177 | public function pull(Image $image) 178 | { 179 | if ($image instanceof ImageVariation) { 180 | $this->pullVariation($image); 181 | } else { 182 | $this->pullSource($image); 183 | } 184 | 185 | return $this; 186 | } 187 | 188 | /** 189 | * Get meta information about the source image from the cache layer. 190 | * 191 | * @param Image|string $image 192 | * 193 | * @return ImageMetadata 194 | */ 195 | public function getMetadata($image) 196 | { 197 | // If the $image is a variation, refer to its parent 198 | // for the metadata. 199 | if ($image instanceof ImageVariation) { 200 | $img_key = $image->getKey(true); 201 | } elseif ($image instanceof Image) { 202 | $img_key = $image->getKey(); 203 | } else { 204 | $img_key = $image; 205 | } 206 | 207 | // Retrieve from cache array if image metadata exists 208 | if (isset($this->metadata_cache[$img_key])) { 209 | return $this->metadata_cache[$img_key]; 210 | } 211 | 212 | $item = $this->cache_pool->getItem('remote.'.$img_key); 213 | $metadata = ImageMetadata::deserialise($item->get()); 214 | 215 | // Set cache item 216 | $this->metadata_cache[$img_key] = $metadata; 217 | 218 | return $metadata; 219 | } 220 | 221 | /** 222 | * Pull a source (or variation) image. 223 | * 224 | * @param Image $image 225 | * 226 | * @throws NotExistsException 227 | */ 228 | protected function pullSource(Image $image) 229 | { 230 | // Image is a source image 231 | if ($this->tagExists($image->getKey()) === false) { 232 | if (!$this->validate_tags || !$this->validateTag($image)) { 233 | throw new NotExistsException(self::ERR_NOT_EXISTS); 234 | } 235 | } 236 | 237 | try { 238 | // Get source data 239 | $image->setData($this->filesystem->read($image->getKey())); 240 | $image->__friendSet('persistent', true); 241 | } catch (FileNotFoundException $e) { 242 | // Image not found 243 | $this->untag($image->getKey()); 244 | throw new NotExistsException(self::ERR_NOT_EXISTS); 245 | } 246 | } 247 | 248 | /** 249 | * Pull a variation image, if the variation does not exist, try pulling the source and creating the variation. 250 | * 251 | * @param ImageVariation $image 252 | * 253 | * @throws NotExistsException 254 | */ 255 | protected function pullVariation(ImageVariation $image) 256 | { 257 | try { 258 | // First, check if the variation exists on the remote 259 | $this->pullSource($image); 260 | } catch (NotExistsException $e) { 261 | // Variation does not exist, try pulling the parent data and creating the variation 262 | try { 263 | $parent = new Image($image->getKey(true)); 264 | 265 | if ($this->tagExists($image->getKey(true)) === false) { 266 | if (!$this->validate_tags || !$this->validateTag($parent)) { 267 | throw new NotExistsException(self::ERR_PARENT_NOT_EXISTS); 268 | } 269 | } 270 | 271 | $data = $this->filesystem->read($image->getKey(true)); 272 | 273 | // Resample 274 | $parent->setData($data); 275 | $this->hydrateVariation($parent, $image); 276 | $parent->flush(); 277 | } catch (FileNotFoundException $e) { 278 | // No image exists 279 | throw new NotExistsException(self::ERR_PARENT_NOT_EXISTS); 280 | } 281 | } 282 | } 283 | 284 | /** 285 | * Mark an image as existing or not existing on the remote. 286 | * 287 | * This function has no effect if there is no cache pool. 288 | * 289 | * @param Image $image 290 | * @param bool $exists 291 | * 292 | * @return $this 293 | */ 294 | public function setImageExists(Image $image, $exists) 295 | { 296 | if ($exists) { 297 | $this->tag($image->getKey()); 298 | } else { 299 | $this->untag($image->getKey()); 300 | } 301 | 302 | return $this; 303 | } 304 | 305 | /** 306 | * Mark a file as existing on the remote. 307 | * If metadata object is populated, that metadata will be stored 308 | * against the image tag stored in the cache layer. 309 | * 310 | * @param string $key 311 | * @param ImageMetadata|null $metadata 312 | * 313 | * @return $this 314 | */ 315 | protected function tag($key, ImageMetadata $metadata = null) 316 | { 317 | if (!$this->cache_pool) { 318 | return null; 319 | } 320 | 321 | $item = $this->cache_pool->getItem('remote.'.$key); 322 | 323 | if (null !== $metadata) { 324 | $value = $metadata->serialise(); 325 | } else { 326 | $value = 1; 327 | } 328 | 329 | $item->set($value, null); 330 | 331 | return $this; 332 | } 333 | 334 | /** 335 | * Mark a file as absent on the remote. 336 | * 337 | * @param string $key 338 | * 339 | * @return $this 340 | */ 341 | protected function untag($key) 342 | { 343 | if (!$this->cache_pool) { 344 | return null; 345 | } 346 | 347 | $item = $this->cache_pool->getItem('remote.'.$key); 348 | $item->delete(); 349 | 350 | return $this; 351 | } 352 | 353 | /** 354 | * Check if a file exists on the remote. 355 | * 356 | * Returns null if caching isn't available (and unsure), else a boolean value 357 | * 358 | * @param string $key 359 | * 360 | * @return bool|null 361 | */ 362 | protected function tagExists($key) 363 | { 364 | if (!$this->cache_pool) { 365 | return null; 366 | } 367 | 368 | $item = $this->cache_pool->getItem('remote.'.$key); 369 | 370 | return $item->exists(); 371 | } 372 | 373 | /** 374 | * Delete an image from the remote. 375 | * 376 | * @param Image $image 377 | * 378 | * @return $this 379 | */ 380 | public function remove(Image $image) 381 | { 382 | $this->filesystem->delete($image->getKey()); 383 | $this->untag($image->getKey()); 384 | 385 | return $this; 386 | } 387 | 388 | /** 389 | * Save the image to the local filesystem. 390 | * 391 | * The extension of the filename is ignored, either the original format or the variation format will be used. 392 | * If the image is not hydrated a pull will be attempted. 393 | * 394 | * @param Image $image 395 | * @param string $filename Path to save the image 396 | * 397 | * @return $this 398 | */ 399 | public function save(Image $image, $filename) 400 | { 401 | if (!$image->isHydrated()) { 402 | // Auto-pull 403 | $this->pull($image); 404 | } 405 | 406 | file_put_contents($filename, $image->getData()); 407 | 408 | return $this; 409 | } 410 | 411 | /** 412 | * Hydrate and render an image variation with parent data. 413 | * 414 | * You can use this to create a variation with a source image 415 | * 416 | * @param Image $parent 417 | * @param ImageVariation $variation 418 | * 419 | * @return ImageVariation 420 | * 421 | * @throws BadImageException 422 | * @throws ImageManagerException 423 | */ 424 | protected function hydrateVariation(Image $parent, ImageVariation &$variation) 425 | { 426 | if (!$parent->isHydrated()) { 427 | throw new ImageManagerException('Parent: '.self::ERR_NOT_HYDRATED); 428 | } 429 | 430 | $quality = $variation->getQuality() ?: 90; 431 | 432 | if ($quality < 1) { 433 | $quality = 1; 434 | } elseif ($quality > 100) { 435 | $quality = 100; 436 | } 437 | 438 | $variation->setData(null); 439 | $input = $parent->getData(); 440 | 441 | foreach ($this->encoders as $encoder) { 442 | if ($encoder->supports($input)) { 443 | $encoder->setData($input); 444 | $variation->setData( 445 | $encoder->createVariation( 446 | $variation->getFormat(), 447 | $quality, 448 | $variation->getDimensions(), 449 | $variation->getCropDimensions() 450 | ) 451 | ); 452 | $encoder->setData(null); 453 | break; 454 | } 455 | } 456 | 457 | if (!$variation->getData()) { 458 | throw new NoSupportedEncoderException('There is no known encoder for this data type'); 459 | } 460 | 461 | return $variation; 462 | } 463 | 464 | /** 465 | * Create a new image variation from a local source image. 466 | * 467 | * @param Image $source 468 | * @param ImageFormat $format 469 | * @param int $quality 470 | * @param ImageDimensions|null $dimensions 471 | * @param ImageCropDimensions|null $crop_dimensions 472 | * 473 | * @return ImageVariation 474 | * 475 | * @throws ImageManagerException 476 | * @throws NoSupportedEncoderException 477 | */ 478 | public function createVariation( 479 | Image $source, 480 | ImageFormat $format, 481 | $quality = ImageVariation::DEFAULT_QUALITY, 482 | ImageDimensions $dimensions = null, 483 | ImageCropDimensions $crop_dimensions = null 484 | ) { 485 | $var = new ImageVariation($source->getKey(), $format, $quality, $dimensions, $crop_dimensions); 486 | 487 | return $this->hydrateVariation($source, $var); 488 | } 489 | 490 | /** 491 | * Create a new image from a filename and hydrate it. 492 | * 493 | * @param string $filename 494 | * @param string|null $key 495 | * 496 | * @return Image 497 | * 498 | * @throws IoException 499 | */ 500 | public function loadFromFile($filename, $key = null) 501 | { 502 | if (!is_readable($filename)) { 503 | throw new IoException('File not readable: '.$filename); 504 | } 505 | 506 | if (!$key) { 507 | $key = basename($filename); 508 | } 509 | 510 | $image = new Image($key); 511 | $image->setData(file_get_contents($filename)); 512 | 513 | return $image; 514 | } 515 | 516 | /** 517 | * Create a new image from memory and hydrate it. 518 | * 519 | * @param string $data 520 | * @param string $key 521 | * 522 | * @return Image 523 | */ 524 | public function load($data, $key) 525 | { 526 | $image = new Image($key); 527 | $image->setData($data); 528 | 529 | return $image; 530 | } 531 | 532 | /** 533 | * Check if an image exists on the remote. 534 | * 535 | * This will check the cache pool if one exists, else it will talk to the remote filesystem to check if 536 | * the image exists. 537 | * 538 | * @param Image $image 539 | * 540 | * @return bool 541 | */ 542 | public function exists(Image $image) 543 | { 544 | $key = $image->getKey(); 545 | 546 | $tag_exists = $this->tagExists($key); 547 | if ($tag_exists !== null) { 548 | return $tag_exists; 549 | } 550 | 551 | return $this->filesystem->has($key); 552 | } 553 | 554 | /** 555 | * Get all registered encoders. 556 | * 557 | * @return EncoderInterface[] 558 | */ 559 | public function getEncoders() 560 | { 561 | return $this->encoders; 562 | } 563 | 564 | /** 565 | * Set encoders. 566 | * 567 | * @param EncoderInterface[] $encoders 568 | * 569 | * @return $this 570 | */ 571 | public function setEncoders(array $encoders) 572 | { 573 | $this->encoders = $encoders; 574 | 575 | return $this; 576 | } 577 | 578 | /** 579 | * Add an encoder. 580 | * 581 | * @param EncoderInterface $encoder 582 | * @param bool $prepend Prepend to the list instead of appending 583 | * 584 | * @return $this 585 | */ 586 | public function addEncoder(EncoderInterface $encoder, $prepend = false) 587 | { 588 | if ($prepend) { 589 | array_unshift($this->encoders, $encoder); 590 | } else { 591 | $this->encoders[] = $encoder; 592 | } 593 | 594 | return $this; 595 | } 596 | 597 | /** 598 | * Rename a file 599 | * 600 | * TODO: Potentially fix https://github.com/KnpLabs/Gaufrette/issues/374 here. 601 | * 602 | * @param string $source_key 603 | * @param string $target_key 604 | * 605 | * @return $this 606 | */ 607 | public function rename($source_key, $target_key) 608 | { 609 | $this->filesystem->rename($source_key, $target_key); 610 | 611 | $metadata = null; 612 | if ($this->tagExists($source_key)) { 613 | $metadata = $this->getMetadata($source_key); 614 | $this->untag($source_key); 615 | } 616 | 617 | $this->tag($target_key, $metadata); 618 | 619 | return $this; 620 | } 621 | 622 | /** 623 | * Check with the filesystem if the image exists and update the image key cache. 624 | * 625 | * @param Image $image 626 | * 627 | * @return bool 628 | */ 629 | protected function validateTag(Image $image) 630 | { 631 | $exists = $this->filesystem->has($image->getKey()); 632 | $this->setImageExists($image, $exists); 633 | 634 | return $exists; 635 | } 636 | } 637 | -------------------------------------------------------------------------------- /src/Bravo3/ImageManager/Traits/FriendTrait.php: -------------------------------------------------------------------------------- 1 | __friends) ? $this->__friends : []; 18 | 19 | $trace = debug_backtrace(); 20 | if (isset($trace[1]['class']) && in_array($trace[1]['class'], $friends)) { 21 | return $this->$key = $value; 22 | } else { 23 | throw new \Exception("Property is private"); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Entities/ImageTest.php: -------------------------------------------------------------------------------- 1 | setData(file_get_contents($fn.'image.png')); 18 | $this->assertEquals(ImageFormat::PNG, $image->getDataFormat()->value()); 19 | 20 | $image->setData(file_get_contents($fn.'image.jpg')); 21 | $this->assertEquals(ImageFormat::JPEG, $image->getDataFormat()->value()); 22 | 23 | $image->setData(file_get_contents($fn.'animated.gif')); 24 | $this->assertEquals(ImageFormat::GIF, $image->getDataFormat()->value()); 25 | 26 | $image->setData(file_get_contents($fn.'not_an_image.png')); 27 | $this->assertNull($image->getDataFormat()); 28 | 29 | $image->setData(null); 30 | $this->assertNull($image->getDataFormat()); 31 | } 32 | 33 | /** 34 | * @small 35 | * @expectedException \Bravo3\ImageManager\Exceptions\ImageManagerException 36 | */ 37 | public function testBadKey() 38 | { 39 | new Image(''); 40 | } 41 | 42 | 43 | } 44 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/FriendlyClass.php: -------------------------------------------------------------------------------- 1 | x; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/UnfriendlyClass.php: -------------------------------------------------------------------------------- 1 | x; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/actually_a_png.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/actually_a_png.jpg -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/animated.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/animated.gif -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/image.jpg -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/image.png -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/not_an_image.png: -------------------------------------------------------------------------------- 1 | This is clearly not an image. 2 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/sample_pdf.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/sample_pdf.pdf -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/sample_pdf2.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/sample_pdf2.pdf -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Resources/transparent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bravo3/image-manager/149c73c21831e699720b23c38df14510913de265/tests/Bravo3/ImageManager/Tests/Resources/transparent.png -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Services/ImageManagerTest.php: -------------------------------------------------------------------------------- 1 | loadFromFile($fn); 39 | $this->assertTrue($image instanceof Image); 40 | 41 | $hydrated_memory = memory_get_usage(); 42 | 43 | $this->assertGreaterThanOrEqual($start_memory, $hydrated_memory, 'Hydration increased memory consumption'); 44 | 45 | $im->save($image, self::$tmp_dir.'local/'.basename($fn)); 46 | 47 | gc_collect_cycles(); 48 | $saved_memory = memory_get_usage(); 49 | $image->flush(); 50 | $this->assertLessThan($saved_memory, memory_get_usage(), 'Flushing decreased memory consumption'); 51 | } 52 | 53 | /** 54 | * @small 55 | * @expectedException \Bravo3\ImageManager\Exceptions\NoSupportedEncoderException 56 | */ 57 | public function testBadImage() 58 | { 59 | $fn = __DIR__.'/../Resources/not_an_image.png'; 60 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 61 | $img = $im->loadFromFile($fn); 62 | $var = $im->createVariation($img, ImageFormat::JPEG(), 90); 63 | $im->save($var, self::$tmp_dir.'local/invalid.jpg'); 64 | } 65 | 66 | /** 67 | * @small 68 | * @expectedException \Bravo3\ImageManager\Exceptions\IoException 69 | */ 70 | public function testMissingImage() 71 | { 72 | $fn = __DIR__.'/../Resources/does_not_exist.png'; 73 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 74 | $im->loadFromFile($fn); 75 | } 76 | 77 | /** 78 | * @medium 79 | * @dataProvider cacheProvider 80 | * 81 | * @param array $cache 82 | */ 83 | public function testRemote($cache) 84 | { 85 | $fn = __DIR__.'/../Resources/image.png'; 86 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), $cache); 87 | 88 | $image_a = $im->loadFromFile($fn, self::TEST_KEY); 89 | $this->assertTrue($image_a->isHydrated()); 90 | $this->assertFalse($image_a->isPersistent()); 91 | 92 | $this->assertFalse($im->exists($image_a)); 93 | $im->push($image_a); 94 | $this->assertTrue($im->exists($image_a)); 95 | $this->assertTrue($image_a->isPersistent()); 96 | 97 | $image_b = new Image(self::TEST_KEY); 98 | $this->assertFalse($image_b->isHydrated()); 99 | $this->assertFalse($image_b->isPersistent()); 100 | 101 | $im->pull($image_b); 102 | $this->assertTrue($image_b->isHydrated()); 103 | $this->assertTrue($image_b->isPersistent()); 104 | $im->save($image_b, self::$tmp_dir.'local/pushed_and_pulled.png'); 105 | 106 | $im->remove($image_b); 107 | $this->assertFalse($im->exists($image_a)); 108 | } 109 | 110 | public function testRenamedAssetIsPersistent() 111 | { 112 | $fn = __DIR__.'/../Resources/image.png'; 113 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), new EphemeralCachePool()); 114 | 115 | $image = $im->loadFromFile($fn, self::TEST_KEY); 116 | $im->push($image); 117 | 118 | $original_key = $image->getKey(); 119 | $new_key = '/tmp/some-new-key'; 120 | 121 | $im->rename($original_key, $new_key); 122 | 123 | $renamed = new Image($new_key); 124 | 125 | $im->pull($renamed); 126 | 127 | $this->assertTrue($renamed->isHydrated()); 128 | $this->assertTrue($renamed->isPersistent()); 129 | } 130 | 131 | /** 132 | * @expectedException \Bravo3\ImageManager\Exceptions\NotExistsException 133 | * @medium 134 | */ 135 | public function testKeyValidationFail() 136 | { 137 | $fn = __DIR__.'/../Resources/image.png'; 138 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), new EphemeralCachePool()); 139 | 140 | $image = $im->loadFromFile($fn, self::TEST_KEY); 141 | $this->assertTrue($image->isHydrated()); 142 | $this->assertFalse($image->isPersistent()); 143 | $im->push($image); 144 | $this->assertTrue($image->isPersistent()); 145 | 146 | $im->setImageExists($image, false); 147 | 148 | $var = new ImageVariation(self::TEST_KEY, ImageFormat::GIF(), 50); 149 | $im->pull($var); 150 | } 151 | 152 | /** 153 | * @medium 154 | */ 155 | public function testKeyValidationSuccess() 156 | { 157 | $fn = __DIR__.'/../Resources/image.png'; 158 | $im = new ImageManager( 159 | new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), 160 | new EphemeralCachePool(), 161 | [], 162 | true 163 | ); 164 | 165 | $image = $im->loadFromFile($fn, self::TEST_KEY); 166 | $this->assertTrue($image->isHydrated()); 167 | $this->assertFalse($image->isPersistent()); 168 | $im->push($image); 169 | $this->assertTrue($image->isPersistent()); 170 | 171 | $im->setImageExists($image, false); 172 | 173 | $var = new ImageVariation(self::TEST_KEY, ImageFormat::GIF(), 50); 174 | $im->pull($var); 175 | } 176 | 177 | /** 178 | * Data provider returning a real cache and a null cache. 179 | * 180 | * @return array 181 | */ 182 | public function cacheProvider() 183 | { 184 | return [ 185 | [null], 186 | [new EphemeralCachePool()], 187 | ]; 188 | } 189 | 190 | /** 191 | * @medium 192 | */ 193 | public function testVariationLocalCreate() 194 | { 195 | $fn = __DIR__.'/../Resources/image.png'; 196 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 197 | 198 | $source = $im->loadFromFile($fn, self::TEST_KEY); 199 | 200 | // Create and render the variation 201 | $var = $im->createVariation($source, ImageFormat::JPEG(), 50); 202 | 203 | $this->assertFalse($var->isPersistent()); 204 | $this->assertTrue($var->isHydrated()); 205 | 206 | // Test the local save 207 | $im->save($var, self::$tmp_dir.'local/variation.jpg'); 208 | $this->assertFalse($var->isPersistent()); 209 | 210 | $im->push($var); 211 | $this->assertTrue($var->isPersistent()); 212 | 213 | // Test the pull 214 | $var_pull = new ImageVariation(self::TEST_KEY, ImageFormat::JPEG(), 50); 215 | $this->assertFalse($var_pull->isPersistent()); 216 | $this->assertFalse($var_pull->isHydrated()); 217 | 218 | $im->pull($var_pull); 219 | $this->assertTrue($var_pull->isPersistent()); 220 | $this->assertTrue($var_pull->isHydrated()); 221 | $im->save($var_pull, self::$tmp_dir.'local/variation_pulled.jpg'); 222 | 223 | // Test an auto-pull 224 | $var_autopull = new ImageVariation(self::TEST_KEY, ImageFormat::JPEG(), 50); 225 | $this->assertFalse($var_autopull->isPersistent()); 226 | $this->assertFalse($var_autopull->isHydrated()); 227 | $im->save($var_autopull, self::$tmp_dir.'local/variation_autopulled.jpg'); 228 | 229 | // Image data should be identical - no loss 230 | $md5_a = md5_file(self::$tmp_dir.'local/variation.jpg'); 231 | $md5_b = md5_file(self::$tmp_dir.'local/variation_pulled.jpg'); 232 | $md5_c = md5_file(self::$tmp_dir.'local/variation_autopulled.jpg'); 233 | $this->assertEquals($md5_a, $md5_b); 234 | $this->assertEquals($md5_a, $md5_c); 235 | } 236 | 237 | /** 238 | * @medium 239 | */ 240 | public function testEncodePdf() 241 | { 242 | $fn = __DIR__.'/../Resources/sample_pdf.pdf'; 243 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 244 | $im->addEncoder(new ImagickEncoder()); 245 | 246 | $source = $im->loadFromFile($fn, self::TEST_KEY.'_pdf'); 247 | 248 | // Create and render the variation 249 | $var = $im->createVariation($source, ImageFormat::PNG(), 90, new ImageDimensions(1200)); 250 | $im->save($var, self::$tmp_dir.'local/sample_pdf.png'); 251 | } 252 | 253 | /** 254 | * @medium 255 | */ 256 | public function testPdfEncodeColorCorrection() 257 | { 258 | $fn = __DIR__.'/../Resources/sample_pdf2.pdf'; 259 | 260 | // Define a pixel within the image which has colour white when alpha 261 | // channel ignored. Test ignores alpha channel - See below. 262 | $x_px = $y_px = 10; 263 | 264 | // Retrieve PDF background color before encoding 265 | $imagick_pdf = new \Imagick($fn); 266 | $before_bg_color = $imagick_pdf 267 | ->getImagePixelColor($x_px, $y_px) 268 | ->getColor(); 269 | 270 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 271 | $imagick_encoder = new ImagickEncoder(); 272 | $im->addEncoder($imagick_encoder); 273 | 274 | $source = $im->loadFromFile($fn, self::TEST_KEY.'_pdf'); 275 | 276 | // Create and render the variation 277 | $var = $im->createVariation($source, ImageFormat::JPEG(), 90, new ImageDimensions(1200)); 278 | $fn_jpeg = self::$tmp_dir.'local/sample_pdf2.jpg'; 279 | $im->save($var, $fn_jpeg); 280 | 281 | // Check jpeg bg color 282 | $imagick_jpeg = new \Imagick($fn_jpeg); 283 | $after_bg_color = $imagick_jpeg 284 | ->getImagePixelColor($x_px, $y_px) 285 | ->getColor(); 286 | 287 | // Unset alpha channel 288 | unset($before_bg_color['a']); 289 | unset($after_bg_color['a']); 290 | 291 | $this->assertEquals($before_bg_color, $after_bg_color); 292 | } 293 | 294 | /** 295 | * @medium 296 | */ 297 | public function testVariationRemoteCreate() 298 | { 299 | $fn = __DIR__.'/../Resources/image.png'; 300 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 301 | 302 | $source = $im->loadFromFile($fn, self::TEST_KEY_VAR); 303 | $im->push($source); 304 | 305 | $var = new ImageVariation(self::TEST_KEY_VAR, ImageFormat::GIF(), 50); 306 | $im->pull($var); 307 | 308 | $this->assertTrue($var->isHydrated()); 309 | $this->assertFalse($var->isPersistent()); 310 | 311 | $this->assertTrue($im->exists($source)); 312 | $this->assertFalse($im->exists($var)); 313 | 314 | $im->push($var); 315 | 316 | $this->assertTrue($var->isPersistent()); 317 | $this->assertTrue($im->exists($var)); 318 | } 319 | 320 | /** 321 | * @medium 322 | * @expectedException \Bravo3\ImageManager\Exceptions\ImageManagerException 323 | */ 324 | public function testNotHydratedPush() 325 | { 326 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 327 | $var = new Image(self::TEST_KEY); 328 | $im->push($var); 329 | } 330 | 331 | /** 332 | * @medium 333 | * @expectedException \Bravo3\ImageManager\Exceptions\ImageManagerException 334 | */ 335 | public function testNotHydratedSrc() 336 | { 337 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 338 | $img = new Image(self::TEST_KEY); 339 | $im->createVariation($img, ImageFormat::PNG()); 340 | } 341 | 342 | /** 343 | * @medium 344 | */ 345 | public function testBounds() 346 | { 347 | $fn = __DIR__.'/../Resources/image.png'; 348 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 349 | 350 | $source = $im->load(file_get_contents($fn), self::TEST_KEY); 351 | $im->createVariation($source, ImageFormat::PNG(), -1); 352 | $im->createVariation($source, ImageFormat::PNG(), 150); 353 | } 354 | 355 | /** 356 | * @medium 357 | * @dataProvider cacheProvider 358 | * @expectedException \Bravo3\ImageManager\Exceptions\NotExistsException 359 | * 360 | * @param array $cache 361 | */ 362 | public function testNotFoundImage($cache) 363 | { 364 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), $cache); 365 | 366 | $image = new Image('does-not-exist'); 367 | $im->pull($image); 368 | } 369 | 370 | /** 371 | * @medium 372 | * @dataProvider cacheProvider 373 | * @expectedException \Bravo3\ImageManager\Exceptions\NotExistsException 374 | * 375 | * @param array $cache 376 | */ 377 | public function testNotFoundVariation($cache) 378 | { 379 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), $cache); 380 | 381 | $image = new ImageVariation('does-not-exist', ImageFormat::PNG()); 382 | $im->pull($image); 383 | } 384 | 385 | /** 386 | * @medium 387 | */ 388 | public function testRemoteResample() 389 | { 390 | $fn = __DIR__.'/../Resources/image.png'; 391 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 392 | 393 | $source = $im->loadFromFile($fn, self::TEST_KEY_VAR); 394 | $im->push($source); 395 | 396 | $this->assertTrue($source->isHydrated()); 397 | $this->assertTrue($source->isPersistent()); 398 | 399 | $var_x = new ImageVariation( 400 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 401 | new ImageDimensions(20) 402 | ); 403 | $im->push($var_x); 404 | 405 | $var_xy_stretch = new ImageVariation( 406 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 407 | new ImageDimensions(200, null, false) 408 | ); 409 | $im->push($var_xy_stretch); 410 | 411 | $var_y = new ImageVariation( 412 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 413 | new ImageDimensions(null, 200) 414 | ); 415 | $im->push($var_y); 416 | 417 | $var_xy = new ImageVariation( 418 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 419 | new ImageDimensions(100, 200) 420 | ); 421 | $im->push($var_xy); 422 | 423 | $var_xy_scale = new ImageVariation( 424 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 425 | new ImageDimensions(100, 200, false) 426 | ); 427 | $im->push($var_xy_scale); 428 | 429 | $var_xy_stretch = new ImageVariation( 430 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 431 | new ImageDimensions(100, 200, false) 432 | ); 433 | $im->push($var_xy_stretch); 434 | 435 | $var_x_noup = new ImageVariation( 436 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 437 | new ImageDimensions(1000, null, true, false) 438 | ); 439 | $im->push($var_x_noup); 440 | 441 | $var_x_up = new ImageVariation( 442 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 443 | new ImageDimensions(1000) 444 | ); 445 | $im->push($var_x_up); 446 | 447 | $var_g = new ImageVariation( 448 | self::TEST_KEY_VAR, ImageFormat::JPEG(), 90, 449 | new ImageDimensions(100, 200, true, true, true) 450 | ); 451 | $im->push($var_g); 452 | } 453 | 454 | /** 455 | * @medium 456 | */ 457 | public function testLocalResample() 458 | { 459 | $fn = __DIR__.'/../Resources/image.png'; 460 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 461 | 462 | $source = $im->loadFromFile($fn, self::TEST_KEY_VAR); 463 | $resized = $im->createVariation($source, ImageFormat::JPEG(), 90, new ImageDimensions(100)); 464 | 465 | $this->assertTrue($resized->isHydrated()); 466 | $this->assertFalse($resized->isPersistent()); 467 | 468 | $im->save($resized, self::$tmp_dir.'local/resized.jpg'); 469 | } 470 | 471 | /** 472 | * @medium 473 | * @dataProvider cacheProvider 474 | * @expectedException \Bravo3\ImageManager\Exceptions\ObjectAlreadyExistsException 475 | * 476 | * @param array $cache 477 | */ 478 | public function testOverwrite($cache) 479 | { 480 | $fn = __DIR__.'/../Resources/image.png'; 481 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), $cache); 482 | 483 | $source = $im->loadFromFile($fn, self::TEST_KEY); 484 | $im->push($source); 485 | $im->push($source, false); 486 | } 487 | 488 | /** 489 | * @medium 490 | */ 491 | public function testChangeKey() 492 | { 493 | $fn = __DIR__.'/../Resources/image.png'; 494 | $im = new ImageManager(new Filesystem(new LocalAdapter(static::$tmp_dir.'remote'))); 495 | 496 | $source = $im->loadFromFile($fn, self::TEST_KEY); 497 | $im->push($source); 498 | 499 | $this->assertTrue($source->isHydrated()); 500 | $this->assertTrue($source->isPersistent()); 501 | 502 | $source->setKey('change_key'); 503 | 504 | $this->assertTrue($source->isHydrated()); 505 | $this->assertFalse($source->isPersistent()); 506 | 507 | $im->push($source); 508 | $this->assertTrue($source->isPersistent()); 509 | } 510 | 511 | public function testMetadataRetrieval() 512 | { 513 | $inspector = new ImageInspector(); 514 | 515 | $fn = __DIR__.'/../Resources/image.png'; 516 | $im = new ImageManager( 517 | new Filesystem(new LocalAdapter(static::$tmp_dir.'remote')), 518 | new EphemeralCachePool(), 519 | [], 520 | true 521 | ); 522 | 523 | $image = $im->loadFromFile($fn, self::TEST_KEY); 524 | $im->push($image); 525 | 526 | $metadata = $inspector->getImageMetadata($image); 527 | $this->assertEquals(ImageFormat::PNG(), $metadata->getFormat()); 528 | $this->assertEquals(new ImageDimensions(300, 300), $metadata->getDimensions()); 529 | 530 | // This test appears to fail on some systems, assuming it to be an OS-level issue 531 | //$this->assertEquals(new ImageDimensions(72, 72), $metadata->getResolution()); 532 | 533 | $this->assertEquals(ImageOrientation::LANDSCAPE(), $metadata->getOrientation()); 534 | } 535 | 536 | // -- 537 | 538 | /** 539 | * Get a list of images. 540 | * 541 | * @return array 542 | */ 543 | public function imageProvider() 544 | { 545 | $base = __DIR__.'/../Resources/'; 546 | 547 | return [ 548 | [$base.'image.jpg'], 549 | [$base.'image.png'], 550 | [$base.'transparent.png'], 551 | [$base.'animated.gif'], 552 | ]; 553 | } 554 | 555 | /** 556 | * This isn't an actual test, it permits the teardown function to delete the test images 557 | * Exclude this test to keep the test images. 558 | * 559 | * @small 560 | * @group deleteTestImages 561 | */ 562 | public function testDeleteTestImages() 563 | { 564 | self::$allow_delete = true; 565 | } 566 | 567 | // -- 568 | 569 | public static function setUpBeforeClass() 570 | { 571 | $sys_temp = sys_get_temp_dir(); 572 | if (substr($sys_temp, -1) != DIRECTORY_SEPARATOR) { 573 | $sys_temp .= DIRECTORY_SEPARATOR; 574 | } 575 | 576 | self::$tmp_dir = $sys_temp.rand(10000, 99999).DIRECTORY_SEPARATOR; 577 | 578 | mkdir(self::$tmp_dir.'local', 0777, true); 579 | mkdir(self::$tmp_dir.'remote', 0777, true); 580 | } 581 | 582 | public static function tearDownAfterClass() 583 | { 584 | if (self::$allow_delete) { 585 | self::rrmdir(self::$tmp_dir); 586 | } else { 587 | fwrite(STDERR, "\n\nTest images saved to ".self::$tmp_dir."\n"); 588 | } 589 | } 590 | 591 | protected static function rrmdir($dir) 592 | { 593 | foreach (glob($dir.'/*') as $file) { 594 | if (is_dir($file)) { 595 | self::rrmdir($file); 596 | } else { 597 | unlink($file); 598 | } 599 | } 600 | rmdir($dir); 601 | } 602 | } 603 | -------------------------------------------------------------------------------- /tests/Bravo3/ImageManager/Tests/Traits/FriendTraitTest.php: -------------------------------------------------------------------------------- 1 | __friendSet('x', 10); 17 | $this->assertEquals(10, $class->getX()); 18 | } 19 | 20 | /** 21 | * @small 22 | * @expectedException \Exception 23 | */ 24 | public function testEnemy() 25 | { 26 | $class = new UnfriendlyClass(); 27 | $class->__friendSet('x', 10); 28 | $this->assertEquals(10, $class->getX()); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 |