Skip to content

Resource Image Encoding - #63

Merged
tg666 merged 1 commit into
masterfrom
feature/resource-image-encoding
Jun 24, 2026
Merged

Resource Image Encoding#63
tg666 merged 1 commit into
masterfrom
feature/resource-image-encoding

Conversation

@tg666

@tg666 tg666 commented Jun 23, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • Improvements
    • Optimized image encoding with centralized quality configuration and caching for enhanced performance.
    • Streamlined image resource handling with improved separation between resource creation and persistence layers.

- added method `ResourceInterface::getEncodedImage()`
- method `ImagePersister::save()` now uses the `getEncodedImage()` from a resource during saving
- fixed unit tests
@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Encoding responsibility is moved from ImagePersister into ImageResource. ResourceInterface gains getEncodedImage(): string and a non-nullable getEncodeQuality(): int. ResourceFactory receives ConfigInterface to pass encodeQuality at construction. ImagePersister drops ConfigInterface and its encodeImage() helper, calling $resource->getEncodedImage() directly. DI wiring and all tests are updated accordingly.

Changes

Encoding responsibility shift

Layer / File(s) Summary
ResourceInterface contract and ImageResource/TmpFileImageResource implementation
src/Resource/ResourceInterface.php, src/Resource/ImageResource.php, src/Resource/TmpFileImageResource.php
ResourceInterface adds getEncodedImage(): string and changes getEncodeQuality() to non-nullable int. ImageResource gains an encodedImage cache field, resets the cache in modifyImage(), and implements getEncodedImage() (file-read path for unmodified, encode path for modified). TmpFileImageResource adds int $encodeQuality to its constructor and forwards it to the parent.
ResourceFactory config dependency and encodeQuality propagation
src/Resource/ResourceFactory.php
ResourceFactory gains a ConfigInterface constructor parameter and reads Config::ENCODE_QUALITY (defaulting to 90) when constructing ImageResource and TmpFileImageResource.
DI extension wiring adjustment
src/Bridge/Nette/DI/ImageStorageExtension.php
Adds config.<name> to resource_factory.<name> arguments and removes it from image_persister.<name> arguments.
ImagePersister simplification
src/Persistence/ImagePersister.php
Removes the ConfigInterface constructor parameter and the encodeImage() helper; save() now calls $resource->getEncodedImage() directly.
Test suite updates
tests/Persistence/ImagePersisterTest.phpt, tests/Resource/ImageResourceTest.phpt, tests/Resource/ResourceFactoryTest.phpt, tests/Resource/TmpFileImageResourceTest.phpt
ImagePersisterTest removes config mocking and setupImageSaveExpectations(), rewriting save tests to use getEncodedImage(). ImageResourceTest adds tests for the file-read and encode paths of getEncodedImage(). ResourceFactoryTest adds a createConfig() mock helper. TmpFileImageResourceTest updates constructor calls with the new encodeQuality argument.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • 68publishers/image-storage#59: Directly overlaps with this PR's encoding refactor — changes to how encodeFormat/encodeQuality and stripMeta are produced and propagated through ModifierFacade/ModifyResult into the persistence stage are the same pipeline being restructured here.

Suggested labels

enhancement

🐇 Hoppity hop, the encoder's moved,
No more config in the persister's groove!
getEncodedImage() leads the way,
The resource caches bytes each day.
Quality ninety, unless you say! 🎨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 9.52% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Resource Image Encoding' directly reflects the main architectural change: moving image encoding responsibility from ImagePersister to ImageResource, which is the core refactoring across all modified files.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/resource-image-encoding

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/Resource/ImageResource.php (1)

62-73: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Encode-only modifier changes can be skipped.

If modifyImage() updates encodeFormat/encodeQuality but ModifyResult::modified is false, getEncodedImage() still returns local file bytes via the unmodified shortcut. That bypasses requested re-encoding and can persist incorrect output.

💡 Proposed fix
 public function modifyImage(string|array $modifiers, bool $stripMeta = false): self
 {
     $resource = clone $this;
     $modifyResult = $this->modifierFacade->modifyImage($this->image, $this->pathInfo, $modifiers, $stripMeta);
     $resource->image = $modifyResult->image;
+    $needsReencode = $modifyResult->modified;

-    if ($modifyResult->modified) {
-        $resource->modified = $modifyResult->modified;
-        $resource->encodedImage = null;
-    }
-
     if (null !== $modifyResult->encodeFormat) {
         $resource->encodeFormat = $modifyResult->encodeFormat;
+        $needsReencode = true;
     }

     if (null !== $modifyResult->encodeQuality) {
         $resource->encodeQuality = $modifyResult->encodeQuality;
+        $needsReencode = true;
+    }
+
+    if ($needsReencode) {
+        $resource->modified = true;
+        $resource->encodedImage = null;
     }

     return $resource;
 }

Also applies to: 106-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/Resource/ImageResource.php` around lines 62 - 73, The current code in
ImageResource.php updates encodeFormat and encodeQuality properties
independently from the modified flag check. If only the encode settings change
but the image itself is not modified (ModifyResult::modified is false),
getEncodedImage() will bypass re-encoding and return the original file bytes. To
fix this, ensure that when encodeFormat or encodeQuality are updated (in the
conditional blocks checking null !== $modifyResult->encodeFormat and null !==
$modifyResult->encodeQuality), you also set the modified flag to true so that
getEncodedImage() will properly re-encode the image with the new settings. This
ensures encode-only modifications trigger necessary re-encoding instead of being
skipped.
🧹 Nitpick comments (2)
tests/Resource/ImageResourceTest.phpt (1)

83-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Prove cache behavior by mutating the temp file between reads.

These assertions can still pass if getEncodedImage() rereads disk each time, because the file bytes never change. Mutating the file after the first read makes the cache check deterministic.

Proposed test tweak
-            Assert::same('... original bytes ...', $resource->getEncodedImage());
-            # the result is cached
-            Assert::same('... original bytes ...', $resource->getEncodedImage());
+            $first = $resource->getEncodedImage();
+            file_put_contents($localFile, '... mutated bytes ...');
+            # second read should still return cached bytes
+            Assert::same('... original bytes ...', $first);
+            Assert::same('... original bytes ...', $resource->getEncodedImage());
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Resource/ImageResourceTest.phpt` around lines 83 - 85, The current
cache test in ImageResourceTest.phpt does not actually prove caching behavior
because the file contents remain the same between both getEncodedImage() calls.
To make the cache check deterministic, modify the temp file on disk after the
first getEncodedImage() call but before the second call. This way, if caching is
not working, the second call would return different content from the file, and
the assertion would fail. If caching is working correctly, both assertions will
still pass because the second call returns the cached value from the first read,
not the newly mutated file contents.
tests/Resource/ResourceFactoryTest.phpt (1)

328-339: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add one test for non-default encode_quality propagation.

createConfig() only validates the default fallback path. Add a case with Config::ENCODE_QUALITY present and assert the created resource carries that configured quality.

Suggested direction
+    public function testConfiguredEncodeQualityShouldBePropagatedToResource(): void
+    {
+        $imageManager = Mockery::mock(ImageManager::class);
+        $modifierFacade = Mockery::mock(ModifierFacadeInterface::class);
+        $pathInfo = Mockery::mock(FilePathInfoInterface::class);
+        $image = Mockery::mock(Image::class);
+
+        $imageManager->shouldReceive('make')
+            ->once()
+            ->with('filename')
+            ->andReturn($image);
+
+        $resourceFactory = new ResourceFactory(
+            $this->createFilesystem(),
+            $imageManager,
+            $modifierFacade,
+            $this->createConfig(72),
+        );
+
+        $resource = $resourceFactory->createResourceFromFile($pathInfo, 'filename');
+        Assert::same(72, $resource->getEncodeQuality());
+    }
+
-    private function createConfig(): ConfigInterface
+    private function createConfig(?int $encodeQuality = null): ConfigInterface
     {
         $config = Mockery::mock(ConfigInterface::class);
-
-        # the resource reads the encode quality from the config; falling back to the default is enough here
-        $config->shouldReceive('offsetExists')
-            ->with(Config::ENCODE_QUALITY)
-            ->andReturn(false);
+        $config->shouldReceive('offsetExists')
+            ->with(Config::ENCODE_QUALITY)
+            ->andReturn(null !== $encodeQuality);
+        if (null !== $encodeQuality) {
+            $config->shouldReceive('offsetGet')
+                ->with(Config::ENCODE_QUALITY)
+                ->andReturn($encodeQuality);
+        }
 
         return $config;
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/Resource/ResourceFactoryTest.phpt` around lines 328 - 339, The
createConfig() method only tests the default fallback path where ENCODE_QUALITY
is not present in the config. Add a new test case that verifies the non-default
path where ENCODE_QUALITY is configured. This test should mock the offsetExists
method to return true for Config::ENCODE_QUALITY, mock offsetGet to return a
specific non-default quality value, create a resource with this config, and then
assert that the created resource carries the configured quality value instead of
using the default.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/Resource/ImageResource.php`:
- Around line 62-73: The current code in ImageResource.php updates encodeFormat
and encodeQuality properties independently from the modified flag check. If only
the encode settings change but the image itself is not modified
(ModifyResult::modified is false), getEncodedImage() will bypass re-encoding and
return the original file bytes. To fix this, ensure that when encodeFormat or
encodeQuality are updated (in the conditional blocks checking null !==
$modifyResult->encodeFormat and null !== $modifyResult->encodeQuality), you also
set the modified flag to true so that getEncodedImage() will properly re-encode
the image with the new settings. This ensures encode-only modifications trigger
necessary re-encoding instead of being skipped.

---

Nitpick comments:
In `@tests/Resource/ImageResourceTest.phpt`:
- Around line 83-85: The current cache test in ImageResourceTest.phpt does not
actually prove caching behavior because the file contents remain the same
between both getEncodedImage() calls. To make the cache check deterministic,
modify the temp file on disk after the first getEncodedImage() call but before
the second call. This way, if caching is not working, the second call would
return different content from the file, and the assertion would fail. If caching
is working correctly, both assertions will still pass because the second call
returns the cached value from the first read, not the newly mutated file
contents.

In `@tests/Resource/ResourceFactoryTest.phpt`:
- Around line 328-339: The createConfig() method only tests the default fallback
path where ENCODE_QUALITY is not present in the config. Add a new test case that
verifies the non-default path where ENCODE_QUALITY is configured. This test
should mock the offsetExists method to return true for Config::ENCODE_QUALITY,
mock offsetGet to return a specific non-default quality value, create a resource
with this config, and then assert that the created resource carries the
configured quality value instead of using the default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 108f1881-3bfe-42fc-8270-beb2f347b4f6

📥 Commits

Reviewing files that changed from the base of the PR and between 44ecc1c and 5af2325.

📒 Files selected for processing (10)
  • src/Bridge/Nette/DI/ImageStorageExtension.php
  • src/Persistence/ImagePersister.php
  • src/Resource/ImageResource.php
  • src/Resource/ResourceFactory.php
  • src/Resource/ResourceInterface.php
  • src/Resource/TmpFileImageResource.php
  • tests/Persistence/ImagePersisterTest.phpt
  • tests/Resource/ImageResourceTest.phpt
  • tests/Resource/ResourceFactoryTest.phpt
  • tests/Resource/TmpFileImageResourceTest.phpt

@tg666
tg666 merged commit 69465e6 into master Jun 24, 2026
9 checks passed
@tg666
tg666 deleted the feature/resource-image-encoding branch June 24, 2026 07:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant