thephpleague / glide

Wonderfully easy on-demand image manipulation library with an HTTP based API.
http://glide.thephpleague.com
MIT License
2.55k stars 200 forks source link

Glide 2.0 or future development #288

Open rreynier opened 4 years ago

rreynier commented 4 years ago

Are there plans to continue development on Glide 2.0? It seems like the project is essentially in maintenance mode (which is fine). Just curious what the long term plans are for the project moving forward.

reinink commented 4 years ago

Great question @rreynier!

I am excited to announce that, as of today, @tgalopin has officially taken over as the core maintainer of this project. 🙌

As I announced on Twitter, as much as I love it, I simply don't have the capacity for this project any longer. Passing this project off to Titouan felt like the right thing to do for Glide. 👍

Titouan actually reached out to me months ago, showing interest in this project, and sharing some vision he has for it, which is really cool!

That all said, I'll probably be around a little still during the transition phase...helping Titouan as needed. I think Titouan is planning to get into the project in the next month or two.

tgalopin commented 3 years ago

I profit from this issue and the fact that I (finally) have some time to dedicate to Glide to explain what my ideas are.

Warning: long answer :D

Note that these ideas are what I propose but I'm fully open to suggestions too, I see open-source as community-driven: if you have a need and open a PR, my default opinion will be to accept it unless there is a good reason not to.

In the short term, there are two things I'm working on:

This roadmap will 1/ help people using 1.0 to keep using it (with necessary features) for a while, but at the same time 2/ provide an easy migration path to Flysystem 2.0 as Glide 2.0 will only focus on this upgrade, thus limiting the struggle to upgrade, and finally 3/ in 3.0 we will be able to go further in terms of BC breaks.

Glide 3.0

For Glide 3.0, I proposed several ideas to Jonathan a while ago that still applies today IMO. I would like to rethink the library developer experience to ease its usage, simplify its code and improve its capabilities.

Here is how it could look like in terms of usage:

$glide = Glide::create([
    // Flysystem, source for the images data
    // Required
    'source' => $source,

    // For the cache, I would like to use Symfony Cache instead, for reasons detailed below
    // Required
    'cache' => $cache,

    // The response factory to use (default to HttpFoundation, as it's the most common one)
    'response_factory' => $responseFactory,

    // Equivalent of the "API" in Glide 1.0, but I think this naming is clearer
    // This object will dispatch the manipulation to an array of manipulators
    // If not provided, creates a default one
    'image_manipulator' => $imageManipulator,

    // Secret used to sign image requests, if null signing is disabled
    'secret' => 'af3dc18fc6bfb2afb521e587c348b904',

    // Maximum image size that Glide will accept to render, null to have no limit
    'max_image_size' => 2000*2000,

    // Default parameters for image storage handling
    'default_storage_parameters' => [
        'w' => 2000,
        'h' => 2000,
        'fit' => 'contain',
        'fm' => 'pjpg',
        'q' => 80,
    ],

    // Presets usable in parameters (?p=user-picture-small)
    'presets' => [
        'user-picture-small' => [
            'w' => 200,
            'h' => 200,
            'fit' => 'crop',
        ],
    ],
]);

// Apply initial manipulations to an image and store it in the source at a given path
// Here, the image is resized and re-encoded at storage to decrease the stored file size
// Easier to understand/manipulate than the source Filesystem directly (still possible of course)
$glide->store('users/'.$username.'.jpeg', file_get_contents($uploadedFile), [
    'w' => 2000,
    'h' => 2000,
    'fit' => 'contain',
    'fm' => 'pjpg',
    'q' => 80,
]);

// Check whether the given path exists in the source filesystem
$glide->has('users/'.$username.'.jpeg');

// Remove an image from the source filesystem
$glide->remove('users/'.$username.'.jpeg');

// Clear the cache for a given path
$glide->clearCache('users/'.$username.'.jpeg');

// Clear the cache for all paths
$glide->clearAllCache();

// Sign a given request path and parameters and return the signature
// Returns an empty string if no secret was provided as configuration
$glide->sign('users/'.$username.'.jpeg', [
    'w' => 200,
    'h' => 200,
    'fit' => 'crop',
]);

// Handles a given request with parameters and return a Response using the ResponseFactory
// Throws a NotFoundException if the image doesn't exist or the signature is not valid
// Throws a BadRequestException if the image size is too large or the parameters are not supported
// Exceptions are thrown by the ResponseFactory to profit from framework exceptions
$glide->handle('users/'.$username.'.jpeg', [
    'w' => 200,
    'h' => 200,
    'fit' => 'crop',
    's' => '9978a40f1fc75fa64ac92ea9baf16ff3',
]);

// Directly display an image by sending appropriate headers and content through native PHP calls
// Throws a NotFoundException if the image doesn't exist or the signature is not valid
// Throws a BadRequestException if the image size is too large of the parameters are not supported
// Exceptions are thrown by the ResponseFactory to profit from framework exceptions
$glide->display('users/'.$username.'.jpeg', [
    'w' => 200,
    'h' => 200,
    'fit' => 'crop',
    's' => '9978a40f1fc75fa64ac92ea9baf16ff3',
]);

// Create the base 64 representation of an image
// Throws a NotFoundException if the image doesn't exist or the signature is not valid
// Throws a BadRequestException if the image size is too large of the parameters are not supported
// Exceptions are thrown by the ResponseFactory to profit from framework exceptions
$glide->createBase64('users/'.$username.'.jpeg', [
    'w' => 200,
    'h' => 200,
    'fit' => 'crop',
    's' => '9978a40f1fc75fa64ac92ea9baf16ff3',
]);

These changes would make Glide even more straightforward IMO. Besides the API changes, there are 2 main updates:

Using Symfony Cache has many advantages, the main one being the way it's designed internally. The CacheInterface defines only two methods: get and remove. remove is easy to understand but get is a bit different than the usual cache interfaces.

get receives 3 arguments:

With this structure, a typical cache fetching would look like the following:

$value = $cache->get('my-key', function(ItemInterface $item) {
    $item->expiresAfter(3600);

    // compute the value ...

    return $computedValue;
});

There are two execution paths possible here:

This interface, in addition to being elegant in terms of DX, provides by default a huge advantage over other cache interfaces: it's extremely easy to add locking features around cache generation, avoiding multiple frontends to compute the same value at the same time. That's what the Symfony Cache component does by default: it uses the caching storage to store a lock of currently computed values and avoid multiple frontends taking the CPU/memory hit of computing the cache.

In our context, this is a huge advantage: computing multiple images is CPU-intensive and having multiple PHP-FPM workers computing the same cache on the same time is wasting resources.

The extra parameter, $beta, is referencing an additional feature of Symfony Cache: probabilistic expiration.

Locking is a great way to avoid multiple frontends to compute the same values but these frontends will still be blocked, waiting for the cache to be ready. Having multiple frontends blocked by a cache generation can be a big problem as it uses a network connection and a PHP-FPM worker.

Probabilistic expiration solves this by calculating an increasing probability to expire before the actual expiration date. This means that the cache will randomly be computed earlier than the configured expiration date, this time by a single frontend, and with a probability of doing so that increases as we approach the configured expiration date. This avoids to get all frontends to compute the cache at the same time.

By using Symfony Cache, Glide would profit from these powerful features without having to do anything, thus my proposal.

Big answer, but I thought it was interesting to expose my ideas :) . Feel free to send feedbacks!

ADmad commented 3 years ago

Linking the previous Glide 2.0 brainstorming issue #170 here for better visibility. "Serve cached images via web server" is one feature I would like to be realized.

tgalopin commented 3 years ago

Absolutely, very good idea IMO. It's going to be interesting to see how we can do that.

barryvdh commented 3 years ago

I think you can already do something like this:

Path like: /media/something/jumbotron.jpg/glide/w/600/h/300/jumbotron3.jpg

<?php

$sourceFolder = '/var/www/public/media';
$cacheFolder = __DIR__ .'/../storage/cache';

require __DIR__ . '/../vendor/autoload.php';

// Split on the /glide/
list($path, $params) = explode('/glide/', $_SERVER["REQUEST_URI"]);

$params = explode('/', $params);
$filename = array_pop($params);

// Move the url into key/value pairs
$result = [];
while (count($params) >= 2) {
    list($key, $value) = array_splice($params, 0, 2);
    $result[$key] = $value;
}

// Setup Glide server
$server = League\Glide\ServerFactory::create([
    'source' => $sourceFolder,
    'cache' => $cacheFolder,
]);

// Store for direct access later
$source = $cacheFolder .'/'. $server->makeImage($path, $result);
$target = __DIR__ .'/../public/' . $_SERVER["REQUEST_URI"];
mkdir(dirname($target), 0777, true);
copy($source, $target);

// Serve for now
$server->outputImage($path, $result);

(Use the directories as prefix) Downside is that your cache is doubled. So preferably you would use the cache directly. And you need to make sure you use the same ordering otherwise you end up with the same images over and over again.. (And perhaps some sanitizing like check the query params etc)

Downside is that you can't do auto content-negotiation (WebP)

vitalijalbu commented 9 months ago

Hi guys, I want to use glide with raw php and no frameworks, basically I have a empty subdomain and want to access all iamges from a folder, anyone has a ready-to-clone code? Basically I tried with some code but it doesn't work... Thanks 🙏

celsojr-websters commented 9 months ago

Hi @vitalijalbu, this procedure is working for me:

P.S.: But this is a simple Laravel project boilerplate, not raw PHP.

I'm on WSL2:

$ php --version

PHP 7.4.3-4ubuntu2.17 (cli) (built: Jan 10 2023 15:37:44) ( NTS )
Copyright (c) The PHP Group
Zend Engine v3.4.0, Copyright (c) Zend Technologies
    with Zend OPcache v7.4.3-4ubuntu2.17, Copyright (c), by Zend Technologies
    with Xdebug v2.9.2, Copyright (c) 2002-2020, by Derick Rethans
  1. Create a new Laravel project and open it with VS Code:

    composer create-project --prefer-dist laravel/laravel glide-test
    code glide-test
  2. Install the Glide package using Composer:

    composer require league/glide --prefer-source
  3. Create a route in your routes/web.php file to handle image requests:

    
    <?php

use Illuminate\Support\Facades\Route; use League\Glide\ServerFactory;

...

Route::get('images/{path}', function ($path) { $server = ServerFactory::create([ 'source' => storage_path('app/public'), // The directory where your images are stored 'cache' => storage_path('app/glide-cache'), // The directory where Glide will store its cached images ]);

$server->outputImage($path, $_GET);

})->where('path', '.*');


4. Make sure to create the directories `storage/app/public` and `storage/app/glide-cache` and make them writable:

mkdir -p storage/app/public storage/app/glide-cache chmod -R 775 storage


5. Put an image file inside the nearly created folder.
`e.g.: 01.jpg`

6. Now we can try to run and process the image:

php artisan serve

http://127.0.0.1:8000/images/01.jpg?{params here}



This is a lightweight **Laravel** project that is gonna prevent you of doing extra work to make things work. Of course, if you have a very good reason to not use this and you know what you're doing, you can try to simulate what the framework is doing in terms of request/responses and everything else that could be necessary.

For more information on the basic installation/configuration, please refer to the official documentation:
[Simple example](https://glide.thephpleague.com/2.0/simple-example/)