Skip to content

Commit 002b36a

Browse files
authored
Implement ImageResponse::class and macro (#15)
1 parent 141ded4 commit 002b36a

File tree

6 files changed

+387
-19
lines changed

6 files changed

+387
-19
lines changed

README.md

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -23,16 +23,29 @@ In your existing Laravel application you can install this package using [Compose
2323
composer require intervention/image-laravel
2424
```
2525

26-
Next, add the configuration files to your application using the `vendor:publish` command:
26+
## Features
27+
28+
Although Intervention Image can be used with Laravel without this extension,
29+
this intergration package includes the following features that make image
30+
interaction with the framework much easier.
31+
32+
### Application-wide configuration
33+
34+
The extension comes with a global configuration file that is recognized by
35+
Laravel. It is therefore possible to store the settings for Intervention Image
36+
once centrally and not have to define them individually each time you call the
37+
image manager.
38+
39+
The configuration file can be copied to the application with the following command.
2740

2841
```bash
2942
php artisan vendor:publish --provider="Intervention\Image\Laravel\ServiceProvider"
3043
```
3144

32-
This command will publish the configuration file `image.php` for the image
33-
integration to your `app/config` directory. In this file you can set the
34-
desired driver and its configuration options for Intervention Image. By default
35-
the library is configured to use GD library for image processing.
45+
This command will publish the configuration file `config/image.php`. Here you
46+
can set the desired driver and its configuration options for Intervention
47+
Image. By default the library is configured to use GD library for image
48+
processing.
3649

3750
The configuration files looks like this.
3851

@@ -89,20 +102,60 @@ You can read more about the different options for
89102
[decoding animations](https://image.intervention.io/v3/modifying/animations) and
90103
[blending color](https://image.intervention.io/v3/basics/colors#transparency).
91104

92-
## Getting started
105+
### Static Facade Interface
106+
107+
This package also integrates access to Intervention Image's central entry
108+
point, the `ImageManager::class`, via a static [facade](https://laravel.com/docs/11.x/facades). The call provides access to the
109+
centrally configured [image manager](https://image.intervention.io/v3/basics/instantiation) via singleton pattern.
110+
111+
The following code example shows how to read an image from an upload request
112+
the image facade in a Laravel route and save it on disk with a random file
113+
name.
114+
115+
```php
116+
use Illuminate\Http\Request;
117+
use Illuminate\Support\Facades\Route;
118+
use Illuminate\Support\Facades\Storage;
119+
use Illuminate\Support\Str;
120+
use Intervention\Image\Laravel\Facades\Image;
121+
122+
Route::get('/', function (Request $request) {
123+
$upload = $request->file('image');
124+
$image = Image::read($upload)
125+
->resize(300, 200);
126+
127+
Storage::put(
128+
Str::random() . '.' . $upload->getClientOriginalExtension(),
129+
$image->encodeByExtension($upload->getClientOriginalExtension(), quality: 70)
130+
);
131+
});
132+
```
133+
134+
### Image Response Macro
93135

94-
The integration is now complete and it is possible to access the [ImageManager](https://image.intervention.io/v3/basics/instantiation)
95-
via Laravel's facade system.
136+
Furthermore, the package includes a response macro that can be used to
137+
elegantly encode an image resource and convert it to an HTTP response in a
138+
single step.
139+
140+
The following code example shows how to read an image from disk apply
141+
modifications and use the image response macro to encode it and send the image
142+
back to the user in one call. Only the first parameter is required.
96143

97144
```php
145+
use Illuminate\Support\Facades\Route;
146+
use Illuminate\Support\Facades\Storage;
147+
use Intervention\Image\Format;
98148
use Intervention\Image\Laravel\Facades\Image;
99149

100150
Route::get('/', function () {
101-
$image = Image::read('images/example.jpg');
151+
$image = Image::read(Storage::get('example.jpg'))
152+
->scale(300, 200);
153+
154+
return response()->image($image, Format::WEBP, quality: 65);
102155
});
103156
```
104157

105-
Read the [official documentation of Intervention Image](https://image.intervention.io) for more information.
158+
You can read more about intervention image in general in the [official documentation of Intervention Image](https://image.intervention.io).
106159

107160
## Authors
108161

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"intervention/image": "^3.11"
2626
},
2727
"require-dev": {
28+
"ext-fileinfo": "*",
2829
"phpunit/phpunit": "^10.0 || ^11.0",
2930
"orchestra/testbench": "^8.18"
3031
},

src/Facades/Image.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,13 @@
1313
*/
1414
class Image extends Facade
1515
{
16+
/**
17+
* Binding name of the service container
18+
*/
19+
public const BINDING = 'image';
20+
1621
protected static function getFacadeAccessor()
1722
{
18-
return 'image';
23+
return self::BINDING;
1924
}
2025
}

src/ImageResponseFactory.php

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Intervention\Image\Laravel;
6+
7+
use Illuminate\Http\Response;
8+
use Illuminate\Support\Facades\Response as ResponseFactory;
9+
use Intervention\Image\Exceptions\DriverException;
10+
use Intervention\Image\Exceptions\NotSupportedException;
11+
use Intervention\Image\Exceptions\RuntimeException;
12+
use Intervention\Image\FileExtension;
13+
use Intervention\Image\Format;
14+
use Intervention\Image\Image;
15+
use Intervention\Image\MediaType;
16+
17+
class ImageResponseFactory
18+
{
19+
/**
20+
* Image encoder options
21+
*
22+
* @var array<string, mixed>
23+
*/
24+
protected array $options = [];
25+
26+
/**
27+
* Create new ImageResponseFactory instance
28+
*
29+
* @param Image $image
30+
* @param null|string|Format|MediaType|FileExtension $format
31+
* @param mixed ...$options
32+
* @return void
33+
*/
34+
public function __construct(
35+
protected Image $image,
36+
protected null|string|Format|MediaType|FileExtension $format = null,
37+
mixed ...$options
38+
) {
39+
$this->options = $options;
40+
}
41+
42+
/**
43+
* Static factory method to create HTTP response directly
44+
*
45+
* @param Image $image
46+
* @param null|string|Format|MediaType|FileExtension $format
47+
* @param mixed ...$options
48+
* @throws NotSupportedException
49+
* @throws DriverException
50+
* @throws RuntimeException
51+
* @return Response
52+
*/
53+
public static function make(
54+
Image $image,
55+
null|string|Format|MediaType|FileExtension $format = null,
56+
mixed ...$options,
57+
): Response {
58+
return (new self($image, $format, ...$options))->response();
59+
}
60+
61+
/**
62+
* Create HTTP response
63+
*
64+
* @throws NotSupportedException
65+
* @throws DriverException
66+
* @throws RuntimeException
67+
* @return Response
68+
*/
69+
public function response(): Response
70+
{
71+
return ResponseFactory::make(
72+
content: $this->content(),
73+
headers: $this->headers()
74+
);
75+
}
76+
77+
/**
78+
* Read image contents
79+
*
80+
* @throws NotSupportedException
81+
* @throws DriverException
82+
* @throws RuntimeException
83+
* @return string
84+
*/
85+
private function content(): string
86+
{
87+
return (string) $this->image->encodeByMediaType(
88+
$this->format()->mediaType(),
89+
...$this->options
90+
);
91+
}
92+
93+
/**
94+
* Return HTTP response headers to be attached in the image response
95+
*
96+
* @return array
97+
*/
98+
private function headers(): array
99+
{
100+
return [
101+
'Content-Type' => $this->format()->mediaType()->value
102+
];
103+
}
104+
105+
/**
106+
* Determine the target format of the image in the HTTP response
107+
*
108+
* @return Format
109+
*/
110+
private function format(): Format
111+
{
112+
if ($this->format instanceof Format) {
113+
return $this->format;
114+
}
115+
116+
if (($this->format instanceof MediaType)) {
117+
return $this->format->format();
118+
}
119+
120+
if ($this->format instanceof FileExtension) {
121+
return $this->format->format();
122+
}
123+
124+
if (is_string($this->format)) {
125+
return Format::create($this->format);
126+
}
127+
128+
// target format is undefined (null) at this point:
129+
// try to extract the original image format or use jpeg by default.
130+
return Format::tryCreate($this->image->origin()->mediaType()) ?? Format::JPEG;
131+
}
132+
}

src/ServiceProvider.php

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,26 +4,39 @@
44

55
namespace Intervention\Image\Laravel;
66

7+
use Illuminate\Support\Facades\Response as ResponseFacade;
78
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
89
use Intervention\Image\ImageManager;
10+
use Intervention\Image\Image;
11+
use Illuminate\Http\Response;
12+
use Intervention\Image\FileExtension;
13+
use Intervention\Image\Format;
14+
use Intervention\Image\MediaType;
915

1016
class ServiceProvider extends BaseServiceProvider
1117
{
12-
/**
13-
* Binding name of the service container
14-
*/
15-
protected const BINDING = 'image';
16-
1718
/**
1819
* Bootstrap application events
1920
*
2021
* @return void
2122
*/
2223
public function boot()
2324
{
25+
// define config files for publishing
2426
$this->publishes([
25-
__DIR__ . '/../config/image.php' => config_path($this::BINDING . '.php')
27+
__DIR__ . '/../config/image.php' => config_path(Facades\Image::BINDING . '.php')
2628
]);
29+
30+
// register response macro "image"
31+
if (!ResponseFacade::hasMacro(Facades\Image::BINDING)) {
32+
Response::macro(Facades\Image::BINDING, function (
33+
Image $image,
34+
null|string|Format|MediaType|FileExtension $format = null,
35+
mixed ...$options,
36+
): Response {
37+
return ImageResponseFactory::make($image, $format, ...$options);
38+
});
39+
}
2740
}
2841

2942
/**
@@ -35,10 +48,10 @@ public function register()
3548
{
3649
$this->mergeConfigFrom(
3750
__DIR__ . '/../config/image.php',
38-
$this::BINDING
51+
Facades\Image::BINDING
3952
);
4053

41-
$this->app->singleton($this::BINDING, function ($app) {
54+
$this->app->singleton(Facades\Image::BINDING, function () {
4255
return new ImageManager(
4356
driver: config('image.driver'),
4457
autoOrientation: config('image.options.autoOrientation', true),

0 commit comments

Comments
 (0)