Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ImageResponse::class and macro #15

Open
wants to merge 12 commits into
base: main
Choose a base branch
from
72 changes: 62 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,29 @@ In your existing Laravel application you can install this package using [Compose
composer require intervention/image-laravel
```

Next, add the configuration files to your application using the `vendor:publish` command:
## Features

Although Intervention Image can be used with Laravel without this extension,
this intergration package includes the following features that make image
interaction with the framework much easier.

### Application-wide configuration

The extension comes with a global configuration file that is recognized by
Laravel. It is therefore possible to store the settings for Intervention Image
once centrally and not have to define them individually each time you call the
image manager.

The configuration file can be copied to the application with the following command.

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

This command will publish the configuration file `image.php` for the image
integration to your `app/config` directory. In this file you can set the
desired driver and its configuration options for Intervention Image. By default
the library is configured to use GD library for image processing.
This command will publish the configuration file `config/image.php`. Here you
can set the desired driver and its configuration options for Intervention
Image. By default the library is configured to use GD library for image
processing.

The configuration files looks like this.

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

## Getting started
### Static Facade Interface

This package also integrates access to Intervention Image's central entry
point, the `ImageManager::class`, via a static [facade](https://laravel.com/docs/11.x/facades). The call provides access to the
centrally configured [image manager](https://image.intervention.io/v3/basics/instantiation) via singleton pattern.

The following code example shows how to read an image from an upload request
the image facade in a Laravel route and save it on disk with a random file
name.

```php
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
use Intervention\Image\Laravel\Facades\Image;

Route::get('/', function (Request $request) {
$upload = $request->file('image');
$image = Image::read($upload)
->resize(300, 200);

Storage::put(
Str::random() . '.' . $upload->getClientOriginalExtension(),
$image->encodeByExtension($upload->getClientOriginalExtension(), quality: 70)
);
});
```

### Image Response Macro

The integration is now complete and it is possible to access the [ImageManager](https://image.intervention.io/v3/basics/instantiation)
via Laravel's facade system.
Furthermore, the package also includes a response macro that can be used to
elegantly convert an image resource into an HTTP response.

The following code example shows how to read an image from disk
apply modifications and use the image response macro to encode it and send the image back to
the user in one call. Only the first parameter is required.

```php
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Storage;
use Intervention\Image\Format;
use Intervention\Image\Laravel\Facades\Image;

Route::get('/', function () {
$image = Image::read('images/example.jpg');
$image = Image::read(Storage::get('example.jpg'))
->scale(300, 200);

return response()->image($image, Format::WEBP, quality: 65);
});
```

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

## Authors

Expand Down
1 change: 1 addition & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"intervention/image": "^3.11"
},
"require-dev": {
"ext-fileinfo": "*",
"phpunit/phpunit": "^10.0 || ^11.0",
"orchestra/testbench": "^8.18"
},
Expand Down
7 changes: 6 additions & 1 deletion src/Facades/Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,13 @@
*/
class Image extends Facade
{
/**
* Binding name of the service container
*/
public const BINDING = 'image';

protected static function getFacadeAccessor()
{
return 'image';
return self::BINDING;
}
}
131 changes: 131 additions & 0 deletions src/ImageResponseFactory.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
<?php

declare(strict_types=1);

namespace Intervention\Image\Laravel;

use Illuminate\Http\Response;
use Illuminate\Support\Facades\Response as ResponseFactory;
use Intervention\Image\EncodedImage;
use Intervention\Image\Exceptions\DriverException;
use Intervention\Image\Exceptions\NotSupportedException;
use Intervention\Image\Exceptions\RuntimeException;
use Intervention\Image\FileExtension;
use Intervention\Image\Format;
use Intervention\Image\Image;
use Intervention\Image\MediaType;

class ImageResponseFactory
{
/**
* Image encoder options
*
* @var array<string, mixed>
*/
protected array $options = [];

/**
* Create new ImageResponseFactory instance
*
* @param Image|EncodedImage $image
* @param null|string|Format|MediaType|FileExtension $format
* @param mixed ...$options
* @return void
*/
public function __construct(
protected Image|EncodedImage $image,
protected null|string|Format|MediaType|FileExtension $format = null,
mixed ...$options
) {
$this->options = $options;
}

/**
* Static factory method to create HTTP response directly
*
* @param Image $image
* @param null|string|Format|MediaType|FileExtension $format
* @param mixed ...$options
* @throws NotSupportedException
* @throws DriverException
* @throws RuntimeException
* @return Response
*/
public static function make(
Image $image,
null|string|Format|MediaType|FileExtension $format = null,
mixed ...$options,
): Response {
return (new self($image, $format, ...$options))->response();
}

/**
* Create HTTP response
*
* @throws NotSupportedException
* @throws DriverException
* @throws RuntimeException
* @return Response
*/
public function response(): Response
{
return ResponseFactory::make(
content: $this->content(),
headers: $this->headers()
);
}

/**
* Read image contents
*
* @throws NotSupportedException
* @throws DriverException
* @throws RuntimeException
* @return string
*/
private function content(): string
{
return (string) $this->image->encodeByMediaType(
$this->format()->mediaType(),
...$this->options
);
}

/**
* Return HTTP response headers to be attached in the image response
*
* @return array
*/
private function headers(): array
{
return [
'Content-Type' => $this->format()->mediaType()->value
];
}

/**
* Determine the target format of the image in the HTTP response
*
* @return Format
*/
private function format(): Format
{
if ($this->format instanceof Format) {
return $this->format;
}

if (($this->format instanceof MediaType)) {
return $this->format->format();
}

if ($this->format instanceof FileExtension) {
return $this->format->format();
}

if (is_string($this->format)) {
return Format::create($this->format);
}

return Format::tryCreate($this->image->origin()->mediaType()) ?? Format::JPEG;
}
}
29 changes: 21 additions & 8 deletions src/ServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,26 +4,39 @@

namespace Intervention\Image\Laravel;

use Illuminate\Support\Facades\Response as ResponseFacade;
use Illuminate\Support\ServiceProvider as BaseServiceProvider;
use Intervention\Image\ImageManager;
use Intervention\Image\Image;
use Illuminate\Http\Response;
use Intervention\Image\FileExtension;
use Intervention\Image\Format;
use Intervention\Image\MediaType;

class ServiceProvider extends BaseServiceProvider
{
/**
* Binding name of the service container
*/
protected const BINDING = 'image';

/**
* Bootstrap application events
*
* @return void
*/
public function boot()
{
// define config files for publishing
$this->publishes([
__DIR__ . '/../config/image.php' => config_path($this::BINDING . '.php')
__DIR__ . '/../config/image.php' => config_path(Facades\Image::BINDING . '.php')
]);

// register response macro "image"
if (!ResponseFacade::hasMacro(Facades\Image::BINDING)) {
Response::macro(Facades\Image::BINDING, function (
Image $image,
null|string|Format|MediaType|FileExtension $format = null,
mixed ...$options,
): Response {
return ImageResponseFactory::make($image, $format, ...$options);
});
}
}

/**
Expand All @@ -35,10 +48,10 @@ public function register()
{
$this->mergeConfigFrom(
__DIR__ . '/../config/image.php',
$this::BINDING
Facades\Image::BINDING
);

$this->app->singleton($this::BINDING, function ($app) {
$this->app->singleton(Facades\Image::BINDING, function () {
return new ImageManager(
driver: config('image.driver'),
autoOrientation: config('image.options.autoOrientation', true),
Expand Down
Loading