From ec28130d302de25131a30b183bc37ea1174ad3d7 Mon Sep 17 00:00:00 2001 From: AchillesKal Date: Wed, 10 Apr 2024 23:18:52 +0300 Subject: [PATCH] Fix image load performance --- composer.json | 3 + composer.lock | 414 +++++++++++++++++++++++++++- config/services.yaml | 6 + src/DataFixtures/AppFixtures.php | 2 +- src/Factory/BlogPostFactory.php | 18 +- src/Service/UploaderHelper.php | 49 +++- src/Twig/AppExtension.php | 19 +- templates/blog_post/index.html.twig | 8 +- 8 files changed, 487 insertions(+), 32 deletions(-) diff --git a/composer.json b/composer.json index 0086f75..b69602e 100644 --- a/composer.json +++ b/composer.json @@ -16,6 +16,8 @@ "doctrine/doctrine-migrations-bundle": "^3.3", "doctrine/orm": "^3.1", "exercise/htmlpurifier-bundle": "^5.0", + "intervention/image": "2.7.2", + "kornrunner/blurhash": "^1.2", "liip/imagine-bundle": "^2.12", "pagerfanta/doctrine-orm-adapter": "^4.3", "pagerfanta/twig": "^4.3", @@ -26,6 +28,7 @@ "symfony/clock": "7.0.*", "symfony/console": "7.0.*", "symfony/dotenv": "7.0.*", + "symfony/filesystem": "7.0.*", "symfony/flex": "^2", "symfony/form": "7.0.*", "symfony/framework-bundle": "7.0.*", diff --git a/composer.lock b/composer.lock index 1dca517..6646f0f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "2aa32fddf030f75bd7ec389267937b4a", + "content-hash": "d2f53de02af91dbc55dfd01780c92d1d", "packages": [ { "name": "babdev/pagerfanta-bundle", @@ -2091,6 +2091,122 @@ ], "time": "2023-12-03T09:10:34+00:00" }, + { + "name": "guzzlehttp/psr7", + "version": "2.6.2", + "source": { + "type": "git", + "url": "https://github.com/guzzle/psr7.git", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/guzzle/psr7/zipball/45b30f99ac27b5ca93cb4831afe16285f57b8221", + "reference": "45b30f99ac27b5ca93cb4831afe16285f57b8221", + "shasum": "" + }, + "require": { + "php": "^7.2.5 || ^8.0", + "psr/http-factory": "^1.0", + "psr/http-message": "^1.1 || ^2.0", + "ralouphie/getallheaders": "^3.0" + }, + "provide": { + "psr/http-factory-implementation": "1.0", + "psr/http-message-implementation": "1.0" + }, + "require-dev": { + "bamarni/composer-bin-plugin": "^1.8.2", + "http-interop/http-factory-tests": "^0.9", + "phpunit/phpunit": "^8.5.36 || ^9.6.15" + }, + "suggest": { + "laminas/laminas-httphandlerrunner": "Emit PSR-7 responses" + }, + "type": "library", + "extra": { + "bamarni-bin": { + "bin-links": true, + "forward-command": false + } + }, + "autoload": { + "psr-4": { + "GuzzleHttp\\Psr7\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Graham Campbell", + "email": "hello@gjcampbell.co.uk", + "homepage": "https://github.com/GrahamCampbell" + }, + { + "name": "Michael Dowling", + "email": "mtdowling@gmail.com", + "homepage": "https://github.com/mtdowling" + }, + { + "name": "George Mponos", + "email": "gmponos@gmail.com", + "homepage": "https://github.com/gmponos" + }, + { + "name": "Tobias Nyholm", + "email": "tobias.nyholm@gmail.com", + "homepage": "https://github.com/Nyholm" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://github.com/sagikazarmark" + }, + { + "name": "Tobias Schultze", + "email": "webmaster@tubo-world.de", + "homepage": "https://github.com/Tobion" + }, + { + "name": "Márk Sági-Kazár", + "email": "mark.sagikazar@gmail.com", + "homepage": "https://sagikazarmark.hu" + } + ], + "description": "PSR-7 message implementation that also provides common utility methods", + "keywords": [ + "http", + "message", + "psr-7", + "request", + "response", + "stream", + "uri", + "url" + ], + "support": { + "issues": "https://github.com/guzzle/psr7/issues", + "source": "https://github.com/guzzle/psr7/tree/2.6.2" + }, + "funding": [ + { + "url": "https://github.com/GrahamCampbell", + "type": "github" + }, + { + "url": "https://github.com/Nyholm", + "type": "github" + }, + { + "url": "https://tidelift.com/funding/github/packagist/guzzlehttp/psr7", + "type": "tidelift" + } + ], + "time": "2023-12-03T20:05:35+00:00" + }, { "name": "imagine/imagine", "version": "1.3.5", @@ -2153,6 +2269,138 @@ }, "time": "2023-06-07T14:49:52+00:00" }, + { + "name": "intervention/image", + "version": "2.7.2", + "source": { + "type": "git", + "url": "https://github.com/Intervention/image.git", + "reference": "04be355f8d6734c826045d02a1079ad658322dad" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Intervention/image/zipball/04be355f8d6734c826045d02a1079ad658322dad", + "reference": "04be355f8d6734c826045d02a1079ad658322dad", + "shasum": "" + }, + "require": { + "ext-fileinfo": "*", + "guzzlehttp/psr7": "~1.1 || ^2.0", + "php": ">=5.4.0" + }, + "require-dev": { + "mockery/mockery": "~0.9.2", + "phpunit/phpunit": "^4.8 || ^5.7 || ^7.5.15" + }, + "suggest": { + "ext-gd": "to use GD library based image processing.", + "ext-imagick": "to use Imagick based image processing.", + "intervention/imagecache": "Caching extension for the Intervention Image library" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.4-dev" + }, + "laravel": { + "providers": [ + "Intervention\\Image\\ImageServiceProvider" + ], + "aliases": { + "Image": "Intervention\\Image\\Facades\\Image" + } + } + }, + "autoload": { + "psr-4": { + "Intervention\\Image\\": "src/Intervention/Image" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Oliver Vogel", + "email": "oliver@intervention.io", + "homepage": "https://intervention.io/" + } + ], + "description": "Image handling and manipulation library with support for Laravel integration", + "homepage": "http://image.intervention.io/", + "keywords": [ + "gd", + "image", + "imagick", + "laravel", + "thumbnail", + "watermark" + ], + "support": { + "issues": "https://github.com/Intervention/image/issues", + "source": "https://github.com/Intervention/image/tree/2.7.2" + }, + "funding": [ + { + "url": "https://paypal.me/interventionio", + "type": "custom" + }, + { + "url": "https://github.com/Intervention", + "type": "github" + } + ], + "time": "2022-05-21T17:30:32+00:00" + }, + { + "name": "kornrunner/blurhash", + "version": "v1.2.2", + "source": { + "type": "git", + "url": "https://github.com/kornrunner/php-blurhash.git", + "reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/kornrunner/php-blurhash/zipball/bc8a4596cb0a49874f0158696a382ab3933fefe4", + "reference": "bc8a4596cb0a49874f0158696a382ab3933fefe4", + "shasum": "" + }, + "require": { + "php": "^7.3|^8.0" + }, + "require-dev": { + "ext-gd": "*", + "ocramius/package-versions": "^1.4|^2.0", + "phpstan/phpstan": "^0.12", + "phpunit/phpunit": "^9", + "vimeo/psalm": "^4.3" + }, + "type": "library", + "autoload": { + "psr-4": { + "kornrunner\\Blurhash\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Boris Momčilović", + "email": "boris.momcilovic@gmail.com" + } + ], + "description": "Pure PHP implementation of Blurhash", + "homepage": "https://github.com/kornrunner/php-blurhash", + "support": { + "issues": "https://github.com/kornrunner/php-blurhash/issues", + "source": "https://github.com/kornrunner/php-blurhash.git" + }, + "time": "2022-07-13T19:38:39+00:00" + }, { "name": "liip/imagine-bundle", "version": "2.12.2", @@ -2749,6 +2997,114 @@ }, "time": "2019-01-08T18:20:26+00:00" }, + { + "name": "psr/http-factory", + "version": "1.0.2", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-factory.git", + "reference": "e616d01114759c4c489f93b099585439f795fe35" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-factory/zipball/e616d01114759c4c489f93b099585439f795fe35", + "reference": "e616d01114759c4c489f93b099585439f795fe35", + "shasum": "" + }, + "require": { + "php": ">=7.0.0", + "psr/http-message": "^1.0 || ^2.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "1.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interfaces for PSR-7 HTTP message factories", + "keywords": [ + "factory", + "http", + "message", + "psr", + "psr-17", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-factory/tree/1.0.2" + }, + "time": "2023-04-10T20:10:41+00:00" + }, + { + "name": "psr/http-message", + "version": "2.0", + "source": { + "type": "git", + "url": "https://github.com/php-fig/http-message.git", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/php-fig/http-message/zipball/402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "reference": "402d35bcb92c70c026d1a6a9883f06b2ead23d71", + "shasum": "" + }, + "require": { + "php": "^7.2 || ^8.0" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "2.0.x-dev" + } + }, + "autoload": { + "psr-4": { + "Psr\\Http\\Message\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "PHP-FIG", + "homepage": "https://www.php-fig.org/" + } + ], + "description": "Common interface for HTTP messages", + "homepage": "https://github.com/php-fig/http-message", + "keywords": [ + "http", + "http-message", + "psr", + "psr-7", + "request", + "response" + ], + "support": { + "source": "https://github.com/php-fig/http-message/tree/2.0" + }, + "time": "2023-04-04T09:54:51+00:00" + }, { "name": "psr/log", "version": "3.0.0", @@ -2799,6 +3155,50 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "ralouphie/getallheaders", + "version": "3.0.3", + "source": { + "type": "git", + "url": "https://github.com/ralouphie/getallheaders.git", + "reference": "120b605dfeb996808c31b6477290a714d356e822" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/ralouphie/getallheaders/zipball/120b605dfeb996808c31b6477290a714d356e822", + "reference": "120b605dfeb996808c31b6477290a714d356e822", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "php-coveralls/php-coveralls": "^2.1", + "phpunit/phpunit": "^5 || ^6.5" + }, + "type": "library", + "autoload": { + "files": [ + "src/getallheaders.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Ralph Khattar", + "email": "ralph.khattar@gmail.com" + } + ], + "description": "A polyfill for getallheaders.", + "support": { + "issues": "https://github.com/ralouphie/getallheaders/issues", + "source": "https://github.com/ralouphie/getallheaders/tree/develop" + }, + "time": "2019-03-08T08:55:37+00:00" + }, { "name": "runtime/frankenphp-symfony", "version": "0.2.0", @@ -4047,16 +4447,16 @@ }, { "name": "symfony/filesystem", - "version": "v7.0.3", + "version": "v7.0.6", "source": { "type": "git", "url": "https://github.com/symfony/filesystem.git", - "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12" + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/filesystem/zipball/2890e3a825bc0c0558526c04499c13f83e1b6b12", - "reference": "2890e3a825bc0c0558526c04499c13f83e1b6b12", + "url": "https://api.github.com/repos/symfony/filesystem/zipball/408105dff4c104454100730bdfd1a9cdd993f04d", + "reference": "408105dff4c104454100730bdfd1a9cdd993f04d", "shasum": "" }, "require": { @@ -4090,7 +4490,7 @@ "description": "Provides basic utilities for the filesystem", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/filesystem/tree/v7.0.3" + "source": "https://github.com/symfony/filesystem/tree/v7.0.6" }, "funding": [ { @@ -4106,7 +4506,7 @@ "type": "tidelift" } ], - "time": "2024-01-23T15:02:46+00:00" + "time": "2024-03-21T19:37:36+00:00" }, { "name": "symfony/finder", diff --git a/config/services.yaml b/config/services.yaml index cb60ac3..20a1018 100644 --- a/config/services.yaml +++ b/config/services.yaml @@ -25,3 +25,9 @@ services: # add more service definitions when explicit configuration is needed # please note that last definitions always *replace* previous ones + + Intervention\Image\ImageManager: + arguments: + - { driver: 'gd' } + + Liip\ImagineBundle\Service\FilterService: '@liip_imagine.service.filter' diff --git a/src/DataFixtures/AppFixtures.php b/src/DataFixtures/AppFixtures.php index 7e408a8..bb2c093 100644 --- a/src/DataFixtures/AppFixtures.php +++ b/src/DataFixtures/AppFixtures.php @@ -24,7 +24,7 @@ public function load(ObjectManager $manager): void } TagFactory::createMany(15); - BlogPostFactory::createMany(100, function() { + BlogPostFactory::createMany(50, function() { return [ 'tags' => TagFactory::randomRange(1, 5), ]; diff --git a/src/Factory/BlogPostFactory.php b/src/Factory/BlogPostFactory.php index 26b86c1..2e87eab 100644 --- a/src/Factory/BlogPostFactory.php +++ b/src/Factory/BlogPostFactory.php @@ -4,6 +4,8 @@ use App\Entity\BlogPost; use App\Repository\BlogPostRepository; +use App\Service\UploaderHelper; +use Symfony\Component\DependencyInjection\Attribute\Autowire; use Symfony\Component\HttpFoundation\File\UploadedFile; use Zenstruck\Foundry\ModelFactory; use Zenstruck\Foundry\Proxy; @@ -35,8 +37,10 @@ final class BlogPostFactory extends ModelFactory * * @todo inject services if required */ - public function __construct() - { + public function __construct( + private UploaderHelper $uploaderHelper, + #[Autowire(param: 'banner_directory')] private string $bannerDirectory, + ) { parent::__construct(); } @@ -47,19 +51,15 @@ public function __construct() */ protected function getDefaults(): array { - $imagePath = __DIR__ . '/../../assets/images/test.webp'; - $filename = uniqid('test') . '.webp'; - $temporaryImagePath = __DIR__ . '/../../public/uploads/banners/' . $filename; + $file = new UploadedFile( __DIR__ . '/../../assets/images/test.webp', 'test.webp', test: true); - // Copy the dummy image to a temporary file to simulate an upload - copy($imagePath, $temporaryImagePath); - $file = new UploadedFile($temporaryImagePath, $filename); + $filename = $this->uploaderHelper->uploadFile($file, $this->bannerDirectory, true); return [ 'title' => ucfirst(self::faker()->words(5, true)), 'summary' => self::faker()->text, 'content' => self::faker()->randomHtml(3, 6), - 'banner' => $file->getFilename(), + 'banner' => $filename, 'publishedAt' => self::faker()->dateTimeBetween('-3 month'), ]; } diff --git a/src/Service/UploaderHelper.php b/src/Service/UploaderHelper.php index 528b937..8d290bc 100644 --- a/src/Service/UploaderHelper.php +++ b/src/Service/UploaderHelper.php @@ -2,34 +2,65 @@ namespace App\Service; +use Liip\ImagineBundle\Imagine\Cache\CacheManager; +use Liip\ImagineBundle\Imagine\Data\DataManager; +use Liip\ImagineBundle\Imagine\Filter\FilterManager; +use Liip\ImagineBundle\Service\FilterService; use Symfony\Component\HttpFoundation\File\Exception\FileException; use Symfony\Component\HttpFoundation\File\File; use Symfony\Component\String\Slugger\SluggerInterface; +use Symfony\Component\Filesystem\Filesystem; class UploaderHelper { - public function __construct(private readonly SluggerInterface $slugger) + private Filesystem $filesystem; + + public function __construct( + private readonly SluggerInterface $slugger, + private CacheManager $cacheManager, + private FilterManager $filterManager, + private DataManager $dataManager, + private FilterService $filterService + ) { + $this->filesystem = new Filesystem(); } - public function uploadFile(File $file, string $fileDirectory): string + public function uploadFile(File $file, string $fileDirectory, $test = false): string { $originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME); + // this is needed to safely include the file name as part of the URL $safeFilename = $this->slugger->slug($originalFilename); $newFilename = $safeFilename.'-'.uniqid().'.'.$file->guessExtension(); - // Move the file to the directory where brochures are stored + try { - $file->move( - $fileDirectory, - $newFilename - ); - } catch (FileException $e) { + if (true === $test) { + $this->filesystem->copy( + $file->getPathname(), + $fileDirectory.'/'.$newFilename + ); + } else { + $file->move( + $fileDirectory, + $newFilename + ); + } + if (!$this->filesystem->exists($filePath = $fileDirectory.'/'.$newFilename)) { + throw new \InvalidArgumentException("Image does not exist."); + } + + $search = '/app/public'; + $filePath = str_replace($search, '', $filePath); + + $this->filterService->getUrlOfFilteredImage($filePath, 'blog_list'); + $this->filterService->getUrlOfFilteredImage($filePath, 'blog_list_low'); + } catch (FileException $e) { + dd($e->getMessage()); } return $newFilename; } - } diff --git a/src/Twig/AppExtension.php b/src/Twig/AppExtension.php index e749c6d..87e6a44 100644 --- a/src/Twig/AppExtension.php +++ b/src/Twig/AppExtension.php @@ -3,20 +3,25 @@ namespace App\Twig; use App\Repository\TagRepository; +use Liip\ImagineBundle\Imagine\Cache\CacheManager; use Twig\Extension\AbstractExtension; use Twig\TwigFilter; use Twig\TwigFunction; class AppExtension extends AbstractExtension { - public function __construct(private TagRepository $tagRepository) - { + public function __construct( + private TagRepository $tagRepository, + private CacheManager $cache + ) { } public function getFunctions(): array { return [ new TwigFunction('uploaded_asset', [$this, 'getUploadedAssetPath']), + new TwigFunction('uploaded_banner', [$this, 'getUploadedBannerPath']), + new TwigFunction('uploaded_banner_low', [$this, 'getUploadedBannerLowPath']), new TwigFunction('get_menu_tags', [$this, 'getMenuTags']), ]; } @@ -33,6 +38,16 @@ public function getUploadedAssetPath(string $path): string return 'uploads/banners/' . $path; } + public function getUploadedBannerPath(string $path): string + { + return '/media/cache/blog_list/uploads/banners/' . $path; + } + + public function getUploadedBannerLowPath(string $path): string + { + return 'app/public/media/cache/blog_list_low/uploads/banners/' . $path; + } + public function getMenuTags() { // find all tags that have is menu true diff --git a/templates/blog_post/index.html.twig b/templates/blog_post/index.html.twig index ddf6098..6f277b8 100644 --- a/templates/blog_post/index.html.twig +++ b/templates/blog_post/index.html.twig @@ -20,13 +20,13 @@
- {# Check if the blog post has a banner image #} {% if blog_post.banner %} {{ blog_post.title }} {% else %}