Skip to content

Conversation

@nbalkandzhiyski
Copy link

@nbalkandzhiyski nbalkandzhiyski commented Jan 6, 2026

This new feature adds a additional pre-processing step in app/Actions/Photo/Create.php ** $pre_pipes** to detect HEIF file types and convert them to jpeg in order to support working with these file types.

Also during our work we found that the photos.title is varchar(100) but our test images from IPhone 15+ had longer titles, so we had to add a migration to change the title to text column in order to support longer titles.
Ability to add more convertible file types in app/Actions/Photo/Convert/ImageTypeFactory.php

Added fallback image conversion via Go https://github.com/MaestroError/php-heic-to-jpg#php-heic-to-jpg because ImageMagick fails to convert some HEIF images, mostly from IPhone 15+ devices

Key points:
• Pre-processing happens early in the upload/import lifecycle, before AssertSupportedMedia.
• Conversion uses existing ImageMagick support when available (no new hard dependency).
• Converted file transparently replaces the original HEIC/HEIF file for downstream processing.
• Original file remains untouched if conversion fails.
• Fully compatible with S3 / remote storage setups, as conversion happens while the file is still local/temporary.
• Designed as an extensible pre-processing stage, not a HEIC-only hack.

Summary by CodeRabbit

  • New Features

    • Automatic conversion of HEIC/HEIF images to JPEG during upload; unsupported media are normalized before validation.
  • Database Changes

    • Photo title column updated to support longer text.
  • Dependencies

    • Added a HEIC-to-JPEG conversion library.
  • Error Handling

    • Conversion failures now surface consistent, user-facing error responses.
  • Tests

    • Unit tests added to cover conversion success paths and error wrapping.

✏️ Tip: You can customize this high-level summary in your review settings.

- Introduced `HeifToJpeg` class for converting HEIC/HEIF images to JPEG format.
- Added `ImageTypeFactory` to determine conversion classes based on file extensions.
- Implemented `ConvertUnsupportedMedia` pipe to handle unsupported media conversions.
- Created `ConvertMediaFileInterface` for consistent conversion method signatures.
- Added `CannotConvertMediaFileException` for error handling during conversion.
- Updated `Create` action to include the new conversion pipe.
- Introduced `ConvertableImageType` enum to manage image type checks.
- Updated variable names to use snake case  in `HeifToJpeg` and `ConvertMediaFileInterface` for consistency and clarity.
…ecause titles over 100 chars throws error when uploading images
…000) in photos table for improved title length handling
…o TEXT in photos table, updating index to accommodate new column type and ensuring proper handling of long titles.
…existence check before dropping it, ensuring smoother migration process.
…o-jpg

- Updated `composer.json` and `composer.lock` to include the new dependency.
- Enhanced `HeifToJpeg` class to utilize the `HeicToJpg` library for conversion.
- Modified methods to handle both Imagick and HeicToJpg instances for image processing.
@nbalkandzhiyski nbalkandzhiyski requested a review from a team as a code owner January 6, 2026 14:04
@coderabbitai
Copy link

coderabbitai bot commented Jan 6, 2026

📝 Walkthrough

Walkthrough

Adds HEIC/HEIF → JPEG conversion: new conversion interface and HeifToJpeg implementation (Imagick with maestroerror fallback), ImageTypeFactory, pipeline pipe to auto-convert unsupported media, enums and exception, composer dependency, unit tests, and a migration changing photos.title to TEXT.

Changes

Cohort / File(s) Summary
Interface & Exception
app/Contracts/Image/ConvertMediaFileInterface.php, app/Exceptions/CannotConvertMediaFileException.php
New ConvertMediaFileInterface (handle, storeNewImage, deleteOldFile) and new CannotConvertMediaFileException (HTTP 422, default message).
Enums
app/Enum/ConvertableImageType.php, app/Enum/ImageType.php
Adds ConvertableImageType (HEIC, HEIF) with isHeifImageType() and ImageType enum mapping IMAGETYPE_* constants.
Conversion Implementation & Factory
app/Actions/Photo/Convert/HeifToJpeg.php, app/Actions/Photo/Convert/ImageTypeFactory.php
New HeifToJpeg converter: converts HEIC/HEIF to JPEG (Imagick multi-frame, quality, orientation), falls back to maestroerror/php-heic-to-jpg; ImageTypeFactory selects/instantiates converters by extension and validates the interface.
Pipeline Integration
app/Actions/Photo/Create.php, app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
Inserts ConvertUnsupportedMedia into pre_pipes before Init\AssertSupportedMedia; pipe uses ImageTypeFactory, runs conversion when needed, updates state->source_file, and wraps errors into CannotConvertMediaFileException.
Tests
tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
Unit tests for pass-through (JPEG/PNG), HEIF/HEIC conversion triggering, and exception-wrapping; uses mocks and multiple scenarios.
Dependency
composer.json
Adds runtime dependency maestroerror/php-heic-to-jpg ^1.0.5.
Database Migration
database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
Migration changes photos.title to nullable TEXT and adjusts/drops/recreates indexes with driver-aware handling for MySQL/Postgres/SQLite.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐇
I hop through folders, teeth so bright,
nibbling HEICs till JPEGs take flight.
Imagick hums; fallback lends a paw,
pipes update paths — new pixels I saw.
A joyful thump, conversion done right.

🚥 Pre-merge checks | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.

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


📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c2d5a1f and 3213960.

📒 Files selected for processing (1)
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.php: Any new PHP file should contain the license header and have a single blank line after the opening PHP tag
Variable names should be in snake_case in PHP
Apply the PSR-4 coding standard in PHP
Use in_array() with true as the third parameter in PHP
Only use booleans in if statements, not integers or strings
Use strict comparison (===) instead of loose comparison (==)
Avoid code duplication in both if and else statements
Do not use empty() in PHP
Use the moneyphp/money library for handling monetary values in PHP
Never use floats or doubles to represent monetary values; use integers representing the smallest currency unit (e.g., cents for USD)

**/*.php: Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.
For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).
Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.
Spotless now uses Palantir Java Format 2.78.0 with a 120-character wrap; configure IDE formatters to match before pushing code changes.
Keep each increment's control flow flat by delegating validation/normalisation into tiny pure helpers that return simple enums or result records, then compose them instead of introducing inline branching that inflates the branch count per change.
When introducing new helpers/utilities or editing files prone to style violations (records, DTOs, generated adapters), run the narrowest applicable lint target (for example phpstan) before the full pipeline. Note the command in the related plan/task.
For PHP changes, ru...

Files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧠 Learnings (11)
📚 Learning: 2025-08-18T10:19:04.946Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3626
File: database/migrations/2025_06_07_144157_photo_tags_to_table.php:87-105
Timestamp: 2025-08-18T10:19:04.946Z
Learning: In the Lychee photo management system, the migration `2025_06_07_144157_photo_tags_to_table.php` runs on data that only contains comma-separated tag names in tag_albums.show_tags - OR/AND expressions do not exist at this migration time, so handling them is unnecessary.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-08-20T20:32:03.676Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3637
File: database/migrations/2025_08_19_160144_create_renamer_rules_table.php:106-108
Timestamp: 2025-08-20T20:32:03.676Z
Learning: In the Lychee codebase, ildyria intentionally uses destructive migration patterns where up() calls down() to ensure clean state, as indicated by "Working as intended" and "No mercy" comments in migrations like database/migrations/2025_08_19_160144_create_renamer_rules_table.php.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-08-24T14:33:37.747Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3644
File: database/migrations/2025_08_24_122020_fix_timeline_config.php:25-29
Timestamp: 2025-08-24T14:33:37.747Z
Learning: In the Lychee codebase, ildyria prefers not to add null checks in migrations when they can guarantee that database keys exist, as indicated by their "Key exists, don't worry about it" response to null-safety suggestions in database/migrations/2025_08_24_122020_fix_timeline_config.php.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-09-21T20:09:31.762Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3702
File: database/migrations/2025_08_27_203030_create_purchasable.php:35-39
Timestamp: 2025-09-21T20:09:31.762Z
Learning: In the Lychee codebase, ildyria has decided to ignore the unique constraint issue with UNIQUE(album_id, photo_id) in the purchasables table where NULL values in MySQL/MariaDB don't enforce uniqueness as expected. This is acceptable for their multi-database support strategy (sqlite, pgsql, mariadb) and likely handled at the application level.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:11:17.217Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: .github/copilot-instructions.md:0-0
Timestamp: 2025-12-22T14:11:17.217Z
Learning: Applies to **/*.php : Any new PHP file should contain the license header and have a single blank line after the opening PHP tag

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP changes, run php artisan test to ensure all tests pass before committing.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP changes, run vendor/bin/php-cs-fixer fix to apply PHP code style fixes before committing.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-19T21:01:40.134Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3838
File: lang/pl/webshop.php:1-2
Timestamp: 2025-12-19T21:01:40.134Z
Learning: In the LycheeOrg/Lychee repository, files in the lang/* directory do not require the license header that is normally required for PHP files.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP changes, run make phpstan to verify PHPStan level 6 minimum; fix all errors before committing.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-28T18:12:55.752Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3901
File: app/Providers/AppServiceProvider.php:0-0
Timestamp: 2025-12-28T18:12:55.752Z
Learning: When using Laravel Octane's tick API, Octane::tick(...) returns an InvokeTickCallable that only has ->seconds(int) and ->immediate() methods. There is no ->every(N) method. Use the correct usage: Octane::tick('name', fn() => ...)->seconds(N) or Octane::tick('name', fn() => ..., N). Apply this guideline to PHP files across the project (not just AppServiceProvider.php).

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🔇 Additional comments (4)
database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php (4)

1-12: LGTM! Header and imports are correct.

All previously flagged issues have been addressed:

  • SPDX license header is present
  • Single blank line after opening PHP tag
  • DB facade is properly imported

18-39: Excellent driver-specific index handling for TEXT columns.

The migration correctly implements driver-aware index creation:

  • MySQL uses prefix index title(100) to comply with TEXT column index limitations
  • PostgreSQL uses LEFT(title, 100) function for equivalent behavior
  • SQLite indexes the full TEXT column

The use of old_album_id is correct, as it references the column name after the 2025_05_28 migration. All previously flagged issues (nullable modifier, column naming) have been properly addressed.


44-57: LGTM! Rollback logic is correct.

The down() method properly reverses the migration:

  • Reverts TEXT column back to VARCHAR(100) nullable
  • Recreates index with explicit name (previously flagged issue now resolved)
  • Uses old_album_id correctly without typos (previous "odl_album_id" typo now fixed)
  • No driver-specific logic needed since VARCHAR(100) can be indexed directly on all supported databases

14-58: Well-implemented migration with proper reversibility.

The migration successfully handles the title column change from VARCHAR(100) to TEXT with appropriate driver-specific index strategies. All issues from previous review rounds have been thoroughly addressed. The code is clean, follows Laravel conventions, and properly maintains data integrity throughout the migration lifecycle.

As per coding guidelines, remember to run before committing:

  • vendor/bin/php-cs-fixer fix
  • php artisan test
  • make phpstan

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 12

🧹 Nitpick comments (2)
app/Actions/Photo/Convert/ImageTypeFactory.php (1)

15-24: Consider making the property readonly and using FQCN.

The public mutable property $convertionClass (after fixing the typo to $conversionClass) could be made readonly since it's only set in the constructor. Additionally, storing the fully-qualified class name (FQCN) instead of just the class name would eliminate fragile string concatenation in the make() method.

🔎 Suggested refactor
-	public ?string $convertionClass = null;
+	public readonly ?string $converterClass;
 
 	public function __construct(string $extension)
 	{
-		$this->convertionClass = match (true) {
-			ConvertableImageType::isHeifImageType($extension) => 'HeifToJpeg',
+		$this->converterClass = match (true) {
+			ConvertableImageType::isHeifImageType($extension) => HeifToJpeg::class,
 			// TODO: Add more convertion types/classes
 			default => null,
 		};
 	}

Then simplify the make() method:

 	public function make(): ConvertMediaFileInterface
 	{
-		$class = 'App\Actions\Photo\Convert\\' . $this->convertionClass;
+		if ($this->converterClass === null) {
+			throw new \RuntimeException('No conversion class available for this file type');
+		}
+
+		$instance = new ($this->converterClass)();
 
-		return new $class();
+		if (!$instance instanceof ConvertMediaFileInterface) {
+			throw new \RuntimeException("Converter must implement ConvertMediaFileInterface");
+		}
+
+		return $instance;
 	}
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)

27-31: Typo: "convertionClass" should be "conversionClass".

The property name in ImageTypeFactory contains a spelling error. While consistent across files, correcting it would improve code readability.

This would require updating ImageTypeFactory.php as well:

-		if ($factory->convertionClass === null) {
+		if ($factory->conversionClass === null) {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5f7b1 and 25cfca0.

📒 Files selected for processing (10)
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Create.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Contracts/Image/ConvertMediaFileInterface.php
  • app/Enum/ConvertableImageType.php
  • app/Enum/ImageType.php
  • app/Exceptions/CannotConvertMediaFileException.php
  • composer.json
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.php: Any new PHP file should contain the license header and have a single blank line after the opening PHP tag
Variable names should be in snake_case in PHP
Apply the PSR-4 coding standard in PHP
Use in_array() with true as the third parameter in PHP
Only use booleans in if statements, not integers or strings
Use strict comparison (===) instead of loose comparison (==)
Avoid code duplication in both if and else statements
Do not use empty() in PHP
Use the moneyphp/money library for handling monetary values in PHP
Never use floats or doubles to represent monetary values; use integers representing the smallest currency unit (e.g., cents for USD)

**/*.php: Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.
For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).
Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.
Spotless now uses Palantir Java Format 2.78.0 with a 120-character wrap; configure IDE formatters to match before pushing code changes.
Keep each increment's control flow flat by delegating validation/normalisation into tiny pure helpers that return simple enums or result records, then compose them instead of introducing inline branching that inflates the branch count per change.
When introducing new helpers/utilities or editing files prone to style violations (records, DTOs, generated adapters), run the narrowest applicable lint target (for example phpstan) before the full pipeline. Note the command in the related plan/task.
For PHP changes, ru...

Files:

  • app/Exceptions/CannotConvertMediaFileException.php
  • app/Enum/ConvertableImageType.php
  • app/Enum/ImageType.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Contracts/Image/ConvertMediaFileInterface.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
  • app/Actions/Photo/Create.php
🧠 Learnings (5)
📓 Common learnings
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3626
File: database/migrations/2025_06_07_144157_photo_tags_to_table.php:87-105
Timestamp: 2025-08-18T10:19:04.946Z
Learning: In the Lychee photo management system, the migration `2025_06_07_144157_photo_tags_to_table.php` runs on data that only contains comma-separated tag names in tag_albums.show_tags - OR/AND expressions do not exist at this migration time, so handling them is unnecessary.
📚 Learning: 2025-08-25T08:48:12.321Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 2562
File: app/Exceptions/Internal/ZipExtractionException.php:9-17
Timestamp: 2025-08-25T08:48:12.321Z
Learning: In the Lychee codebase, LycheeDomainException exists in the App\Exceptions\Internal namespace. Exception classes within the same App\Exceptions\Internal namespace can extend LycheeDomainException without requiring explicit use statements, following standard PHP namespace resolution rules.

Applied to files:

  • app/Exceptions/CannotConvertMediaFileException.php
📚 Learning: 2025-08-25T19:07:01.275Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 2562
File: tests/Feature_v2/Photo/PhotoZipUploadTest.php:81-95
Timestamp: 2025-08-25T19:07:01.275Z
Learning: In the Lychee codebase, when testing ZIP file uploads in PhotoZipUploadTest, Laravel middleware does not trigger for ZipInvalidException, so the exception is thrown directly to PHPUnit rather than being converted to an HTTP response. This means using $this->expectException(ZipInvalidException::class) is the correct approach rather than asserting HTTP status codes.

Applied to files:

  • app/Exceptions/CannotConvertMediaFileException.php
📚 Learning: 2025-12-28T18:12:55.752Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3901
File: app/Providers/AppServiceProvider.php:0-0
Timestamp: 2025-12-28T18:12:55.752Z
Learning: When using Laravel Octane's tick API, Octane::tick(...) returns an InvokeTickCallable that only has ->seconds(int) and ->immediate() methods. There is no ->every(N) method. Use the correct usage: Octane::tick('name', fn() => ...)->seconds(N) or Octane::tick('name', fn() => ..., N). Apply this guideline to PHP files across the project (not just AppServiceProvider.php).

Applied to files:

  • app/Exceptions/CannotConvertMediaFileException.php
  • app/Enum/ConvertableImageType.php
  • app/Enum/ImageType.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Contracts/Image/ConvertMediaFileInterface.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
  • app/Actions/Photo/Create.php
📚 Learning: 2025-08-18T10:19:04.946Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3626
File: database/migrations/2025_06_07_144157_photo_tags_to_table.php:87-105
Timestamp: 2025-08-18T10:19:04.946Z
Learning: In the Lychee photo management system, the migration `2025_06_07_144157_photo_tags_to_table.php` runs on data that only contains comma-separated tag names in tag_albums.show_tags - OR/AND expressions do not exist at this migration time, so handling them is unnecessary.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧬 Code graph analysis (4)
app/Enum/ConvertableImageType.php (1)
app/DTO/ImportEventReport.php (1)
  • str (93-96)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (5)
app/Actions/Photo/Convert/ImageTypeFactory.php (2)
  • ImageTypeFactory (13-32)
  • make (26-31)
app/DTO/PhotoCreate/InitDTO.php (1)
  • InitDTO (18-61)
app/Exceptions/CannotConvertMediaFileException.php (1)
  • CannotConvertMediaFileException (18-26)
app/Contracts/Image/ConvertMediaFileInterface.php (1)
  • handle (17-17)
app/Actions/Photo/Convert/HeifToJpeg.php (1)
  • handle (24-45)
app/Actions/Photo/Convert/ImageTypeFactory.php (1)
app/Enum/ConvertableImageType.php (1)
  • isHeifImageType (16-24)
app/Actions/Photo/Create.php (1)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)
  • ConvertUnsupportedMedia (16-41)
🔇 Additional comments (5)
app/Actions/Photo/Create.php (1)

82-88: LGTM! Proper placement in the pipeline.

The new ConvertUnsupportedMedia pipe is correctly positioned before AssertSupportedMedia, allowing unsupported formats to be converted before validation. The error handling in the pipe properly throws CannotConvertMediaFileException for conversion failures.

app/Enum/ImageType.php (1)

1-19: LGTM! Well-structured enum.

The enum correctly maps to PHP's native IMAGETYPE_* constants, providing type-safe image type representation. The implementation follows all coding guidelines including proper license headers and formatting.

app/Enum/ConvertableImageType.php (1)

1-25: LGTM!

The enum implementation is clean and follows best practices:

  • License header present with correct format
  • in_array() correctly uses true as the third parameter for strict comparison
  • The str()->lower()->toString() approach properly handles case-insensitive extension matching
app/Exceptions/CannotConvertMediaFileException.php (1)

1-26: LGTM!

The exception class is well-structured:

  • Appropriate HTTP 422 status for media conversion failures
  • Follows the existing BaseLycheeException pattern in the codebase
  • Clean constructor with optional message and previous exception chaining
app/Actions/Photo/Convert/HeifToJpeg.php (1)

80-103: Solid fallback strategy for HEIC/HEIF conversion.

The implementation correctly handles the known limitation where ImageMagick may fail on newer iPhone images by falling back to the php-heic-to-jpg library. The multi-frame handling and auto-orientation are good practices for preserving image quality.

The maestroerror/php-heic-to-jpg package is compatible with PHP 7.4 and above. Ensure the installed version is 1.0.5 or later to avoid the RCE vulnerability (CVE-2024-48514) present in earlier versions.

{
public function handle(NativeLocalFile $tmp_file): TemporaryJobFile;

public function storeNewImage(\Imagick|HeicToJpg $image_instance, string $store_to_path);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Add explicit return type and reconsider the tight coupling.

The storeNewImage method is missing a return type declaration. Additionally, the union type \Imagick|HeicToJpg tightly couples this interface to specific implementation classes. Consider accepting a more generic type or creating an abstraction to reduce coupling.

🔎 Suggested refactor
-	public function storeNewImage(\Imagick|HeicToJpg $image_instance, string $store_to_path);
+	public function storeNewImage(\Imagick|HeicToJpg $image_instance, string $store_to_path): void;

For better design, consider creating an adapter interface:

interface ImageResourceInterface {
    public function save(string $path): void;
}

Then change the signature to:

public function storeNewImage(ImageResourceInterface $image_instance, string $store_to_path): void;

As per coding guidelines, all methods should have explicit return types.

- Bump `maestroerror/php-heic-to-jpg` dependency version in `composer.json`.
- Refactor `HeifToJpeg` class to use filename instead of basename for output path.
- Enhance `ImageTypeFactory` to ensure conversion class validity and implement interface checks.
- Improve error handling in `ConvertUnsupportedMedia` by including the original exception in the thrown `CannotConvertMediaFileException`.
- Update migration for 'title' column in photos table to allow null values and adjust indexing accordingly.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🧹 Nitpick comments (1)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)

27-31: Prefer encapsulated check over direct property access.

Accessing $factory->conversionClass directly exposes internal factory state and couples the pipe to implementation details. Consider adding a method to ImageTypeFactory such as canConvert(): bool or hasConverter(): bool for better encapsulation.

🔎 Example refactor in ImageTypeFactory

Add a method to ImageTypeFactory:

public function canConvert(): bool
{
    return $this->conversionClass !== null;
}

Then update this pipe:

 $factory = new ImageTypeFactory($ext);
 
-if ($factory->conversionClass === null) {
+if (!$factory->canConvert()) {
     return $next($state);
 }
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 25cfca0 and 584deef.

📒 Files selected for processing (5)
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • composer.json
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Actions/Photo/Convert/ImageTypeFactory.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.php: Any new PHP file should contain the license header and have a single blank line after the opening PHP tag
Variable names should be in snake_case in PHP
Apply the PSR-4 coding standard in PHP
Use in_array() with true as the third parameter in PHP
Only use booleans in if statements, not integers or strings
Use strict comparison (===) instead of loose comparison (==)
Avoid code duplication in both if and else statements
Do not use empty() in PHP
Use the moneyphp/money library for handling monetary values in PHP
Never use floats or doubles to represent monetary values; use integers representing the smallest currency unit (e.g., cents for USD)

**/*.php: Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.
For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).
Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.
Spotless now uses Palantir Java Format 2.78.0 with a 120-character wrap; configure IDE formatters to match before pushing code changes.
Keep each increment's control flow flat by delegating validation/normalisation into tiny pure helpers that return simple enums or result records, then compose them instead of introducing inline branching that inflates the branch count per change.
When introducing new helpers/utilities or editing files prone to style violations (records, DTOs, generated adapters), run the narrowest applicable lint target (for example phpstan) before the full pipeline. Note the command in the related plan/task.
For PHP changes, ru...

Files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧠 Learnings (4)
📚 Learning: 2025-12-28T18:12:55.752Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3901
File: app/Providers/AppServiceProvider.php:0-0
Timestamp: 2025-12-28T18:12:55.752Z
Learning: When using Laravel Octane's tick API, Octane::tick(...) returns an InvokeTickCallable that only has ->seconds(int) and ->immediate() methods. There is no ->every(N) method. Use the correct usage: Octane::tick('name', fn() => ...)->seconds(N) or Octane::tick('name', fn() => ..., N). Apply this guideline to PHP files across the project (not just AppServiceProvider.php).

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-08-18T10:19:04.946Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3626
File: database/migrations/2025_06_07_144157_photo_tags_to_table.php:87-105
Timestamp: 2025-08-18T10:19:04.946Z
Learning: In the Lychee photo management system, the migration `2025_06_07_144157_photo_tags_to_table.php` runs on data that only contains comma-separated tag names in tag_albums.show_tags - OR/AND expressions do not exist at this migration time, so handling them is unnecessary.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
📚 Learning: 2025-08-20T20:32:03.676Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3637
File: database/migrations/2025_08_19_160144_create_renamer_rules_table.php:106-108
Timestamp: 2025-08-20T20:32:03.676Z
Learning: In the Lychee codebase, ildyria intentionally uses destructive migration patterns where up() calls down() to ensure clean state, as indicated by "Working as intended" and "No mercy" comments in migrations like database/migrations/2025_08_19_160144_create_renamer_rules_table.php.

Applied to files:

  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🧬 Code graph analysis (1)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (5)
app/Actions/Photo/Convert/ImageTypeFactory.php (2)
  • ImageTypeFactory (14-47)
  • make (27-46)
app/DTO/PhotoCreate/InitDTO.php (1)
  • InitDTO (18-61)
app/Exceptions/CannotConvertMediaFileException.php (1)
  • CannotConvertMediaFileException (18-26)
app/Actions/Photo/Convert/HeifToJpeg.php (1)
  • handle (24-45)
app/Contracts/Image/ConvertMediaFileInterface.php (1)
  • handle (17-17)
🔇 Additional comments (4)
composer.json (1)

71-71: LGTM! Dependency constraint addresses critical vulnerability.

The version constraint ^1.0.5 correctly excludes versions affected by CVE-2024-48514 (critical RCE vulnerability). Latest version v1.0.8 is available and will be installed.

app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)

33-37: LGTM! Exception chain properly preserved.

The conversion logic correctly wraps exceptions in CannotConvertMediaFileException while preserving the original exception chain for debugging.

app/Actions/Photo/Convert/HeifToJpeg.php (2)

24-45: LGTM! Conversion workflow is well-structured.

The handle method implements a clean workflow: convert → store → delete → return. Exception handling correctly preserves the original error message for debugging.


80-103: LGTM! Robust conversion with appropriate fallback.

The conversion method correctly:

  • Handles multi-frame images by selecting the first frame
  • Applies appropriate JPEG settings (quality 92, auto-orient)
  • Falls back to HeicToJpg library when Imagick fails
  • Preserves error messages from both conversion attempts (line 100)

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)

18-22: Update docblock to reflect actual exception type.

The docblock specifies @throws \Exception but the method actually throws CannotConvertMediaFileException (line 36), which is more specific and informative.

🔎 Proposed fix
 	/**
 	 * Tries to convert the file to a supported format.
 	 *
-	 * @throws \Exception
+	 * @throws CannotConvertMediaFileException
 	 */
app/Actions/Photo/Convert/ImageTypeFactory.php (1)

16-16: Consider making the property readonly.

Since $conversionClass is set in the constructor and never modified afterward, declaring it as readonly would better express its immutability.

🔎 Proposed change
-	public ?string $conversionClass = null;
+	public readonly ?string $conversionClass;

Note: You'll also need to adjust the constructor to set it via parameter or assignment, as readonly properties must be initialized during construction.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 584deef and f8f91e4.

⛔ Files ignored due to path filters (1)
  • composer.lock is excluded by !**/*.lock
📒 Files selected for processing (8)
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Contracts/Image/ConvertMediaFileInterface.php
  • app/Enum/ConvertableImageType.php
  • app/Enum/ImageType.php
  • app/Exceptions/CannotConvertMediaFileException.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
🚧 Files skipped from review as they are similar to previous changes (5)
  • app/Contracts/Image/ConvertMediaFileInterface.php
  • app/Exceptions/CannotConvertMediaFileException.php
  • app/Actions/Photo/Convert/HeifToJpeg.php
  • database/migrations/2025_12_22_105523_change_title_to_text_on_photos_table.php
  • app/Enum/ConvertableImageType.php
🧰 Additional context used
📓 Path-based instructions (1)
**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.php: Any new PHP file should contain the license header and have a single blank line after the opening PHP tag
Variable names should be in snake_case in PHP
Apply the PSR-4 coding standard in PHP
Use in_array() with true as the third parameter in PHP
Only use booleans in if statements, not integers or strings
Use strict comparison (===) instead of loose comparison (==)
Avoid code duplication in both if and else statements
Do not use empty() in PHP
Use the moneyphp/money library for handling monetary values in PHP
Never use floats or doubles to represent monetary values; use integers representing the smallest currency unit (e.g., cents for USD)

**/*.php: Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.
For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).
Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.
Spotless now uses Palantir Java Format 2.78.0 with a 120-character wrap; configure IDE formatters to match before pushing code changes.
Keep each increment's control flow flat by delegating validation/normalisation into tiny pure helpers that return simple enums or result records, then compose them instead of introducing inline branching that inflates the branch count per change.
When introducing new helpers/utilities or editing files prone to style violations (records, DTOs, generated adapters), run the narrowest applicable lint target (for example phpstan) before the full pipeline. Note the command in the related plan/task.
For PHP changes, ru...

Files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Enum/ImageType.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
🧠 Learnings (1)
📚 Learning: 2025-12-28T18:12:55.752Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3901
File: app/Providers/AppServiceProvider.php:0-0
Timestamp: 2025-12-28T18:12:55.752Z
Learning: When using Laravel Octane's tick API, Octane::tick(...) returns an InvokeTickCallable that only has ->seconds(int) and ->immediate() methods. There is no ->every(N) method. Use the correct usage: Octane::tick('name', fn() => ...)->seconds(N) or Octane::tick('name', fn() => ..., N). Apply this guideline to PHP files across the project (not just AppServiceProvider.php).

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • app/Enum/ImageType.php
  • app/Actions/Photo/Convert/ImageTypeFactory.php
🧬 Code graph analysis (2)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (5)
app/Actions/Photo/Convert/ImageTypeFactory.php (2)
  • ImageTypeFactory (14-47)
  • make (27-46)
app/DTO/PhotoCreate/InitDTO.php (1)
  • InitDTO (18-61)
app/Exceptions/CannotConvertMediaFileException.php (1)
  • CannotConvertMediaFileException (18-26)
app/Actions/Photo/Convert/HeifToJpeg.php (1)
  • handle (24-45)
app/Contracts/Image/ConvertMediaFileInterface.php (1)
  • handle (17-17)
app/Actions/Photo/Convert/ImageTypeFactory.php (1)
app/Enum/ConvertableImageType.php (1)
  • isHeifImageType (16-24)
🔇 Additional comments (2)
app/Enum/ImageType.php (1)

11-18: LGTM! Clean enum implementation.

The enum correctly maps to PHP's built-in IMAGETYPE_* constants and provides a type-safe way to represent supported image types.

app/Actions/Photo/Convert/ImageTypeFactory.php (1)

27-46: Write tests for ImageTypeFactory following test-first development practices.

No tests currently exist for ImageTypeFactory. Per coding guidelines requiring test-driven development, add unit tests covering:

  • HEIF/HEIC extension identification and converter instantiation
  • Exception handling for null conversion class (unsupported extensions)
  • Exception handling for missing converter class
  • Exception handling for invalid ConvertMediaFileInterface implementation

…ror handling and testing

- Change `conversionClass` property in `ImageTypeFactory` to be readonly.
- Update exception type in `ConvertUnsupportedMedia` to `CannotConvertMediaFileException`.
- Add unit tests for `ConvertUnsupportedMedia` to ensure proper handling of various image formats and conversion scenarios.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (3)
tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php (2)

46-116: Consider using a data provider to reduce duplication in passthrough tests.

The three passthrough tests (testHandleWithJpegExtensionPassesThrough, testHandleWithPngExtensionPassesThrough, testHandleWithExtensionWithoutDotPassesThrough) have nearly identical logic with only the extension value differing.

🔎 Proposed refactor using data provider
+	/**
+	 * @dataProvider passthroughExtensionsProvider
+	 */
+	public function testHandleWithNonConvertibleExtensionPassesThrough(string $extension): void
+	{
+		$originalFile = \Mockery::mock(NativeLocalFile::class);
+
+		$originalFile->shouldReceive('getOriginalExtension')
+			->once()
+			->andReturn($extension);
+
+		$state = $this->createInitDTO($originalFile);
+
+		$nextCalled = false;
+		$next = function (InitDTO $state) use (&$nextCalled, $originalFile): InitDTO {
+			$nextCalled = true;
+			self::assertSame($originalFile, $state->source_file);
+
+			return $state;
+		};
+
+		$result = $this->pipe->handle($state, $next);
+
+		$this->assertTrue($nextCalled);
+		$this->assertSame($originalFile, $result->source_file);
+	}
+
+	public static function passthroughExtensionsProvider(): array
+	{
+		return [
+			'jpeg with dot' => ['.jpg'],
+			'png with dot' => ['.png'],
+			'jpeg without dot' => ['jpg'],
+		];
+	}

118-149: Unused mock variable on line 121.

The $convertedFile mock is created but never used in the test. This appears to be leftover code from a different test approach.

🔎 Proposed fix
 	public function testHandleWithHeifExtensionTriggersConversion(): void
 	{
 		$originalFile = \Mockery::mock(NativeLocalFile::class);
-		$convertedFile = \Mockery::mock(TemporaryJobFile::class);
 
 		$originalFile->shouldReceive('getOriginalExtension')
app/Actions/Photo/Convert/ImageTypeFactory.php (1)

22-22: Fix typo in comment: "convertion" → "conversion".

🔎 Proposed fix
-			// TODO: Add more convertion types/classes
+			// TODO: Add more conversion types/classes
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f8f91e4 and c2d5a1f.

📒 Files selected for processing (3)
  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

**/*.php: Any new PHP file should contain the license header and have a single blank line after the opening PHP tag
Variable names should be in snake_case in PHP
Apply the PSR-4 coding standard in PHP
Use in_array() with true as the third parameter in PHP
Only use booleans in if statements, not integers or strings
Use strict comparison (===) instead of loose comparison (==)
Avoid code duplication in both if and else statements
Do not use empty() in PHP
Use the moneyphp/money library for handling monetary values in PHP
Never use floats or doubles to represent monetary values; use integers representing the smallest currency unit (e.g., cents for USD)

**/*.php: Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.
For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).
Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.
Spotless now uses Palantir Java Format 2.78.0 with a 120-character wrap; configure IDE formatters to match before pushing code changes.
Keep each increment's control flow flat by delegating validation/normalisation into tiny pure helpers that return simple enums or result records, then compose them instead of introducing inline branching that inflates the branch count per change.
When introducing new helpers/utilities or editing files prone to style violations (records, DTOs, generated adapters), run the narrowest applicable lint target (for example phpstan) before the full pipeline. Note the command in the related plan/task.
For PHP changes, ru...

Files:

  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
tests/Unit/**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

Tests in the tests/Unit directory should extend from AbstractTestCase

Files:

  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
tests/**/*.php

📄 CodeRabbit inference engine (.github/copilot-instructions.md)

No need to mock the database in tests; use the in-memory SQLite database instead

Files:

  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
🧠 Learnings (6)
📚 Learning: 2025-12-28T18:12:55.752Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 3901
File: app/Providers/AppServiceProvider.php:0-0
Timestamp: 2025-12-28T18:12:55.752Z
Learning: When using Laravel Octane's tick API, Octane::tick(...) returns an InvokeTickCallable that only has ->seconds(int) and ->immediate() methods. There is no ->every(N) method. Use the correct usage: Octane::tick('name', fn() => ...)->seconds(N) or Octane::tick('name', fn() => ..., N). Apply this guideline to PHP files across the project (not just AppServiceProvider.php).

Applied to files:

  • app/Actions/Photo/Convert/ImageTypeFactory.php
  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : Write or extend executable specifications (unit, behaviour, or scenario tests) ahead of implementation, confirm they fail, and then drive code to green before refactoring. List the expected success, validation, and failure branches and add thin failing tests for each path.

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : Always run phpunit tests. If a test remains red, disable it with a TODO, note the reason, and capture the follow-up in the relevant plan.

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
📚 Learning: 2025-08-25T19:07:01.275Z
Learnt from: ildyria
Repo: LycheeOrg/Lychee PR: 2562
File: tests/Feature_v2/Photo/PhotoZipUploadTest.php:81-95
Timestamp: 2025-08-25T19:07:01.275Z
Learning: In the Lychee codebase, when testing ZIP file uploads in PhotoZipUploadTest, Laravel middleware does not trigger for ZipInvalidException, so the exception is thrown directly to PHPUnit rather than being converted to an HTTP response. This means using $this->expectException(ZipInvalidException::class) is the correct approach rather than asserting HTTP status codes.

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
  • tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP code, adhere to conventions: license headers in new files, strict comparison (===), no empty(), in_array() with third parameter true, snake_case variables, PSR-4 standard, test base classes (AbstractTestCase for Unit, BaseApiWithDataTest for Feature_v2).

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
📚 Learning: 2025-12-22T14:12:18.082Z
Learnt from: CR
Repo: LycheeOrg/Lychee PR: 0
File: AGENTS.md:0-0
Timestamp: 2025-12-22T14:12:18.082Z
Learning: Applies to **/*.php : For PHP changes, run php artisan test to ensure all tests pass before committing.

Applied to files:

  • app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php
🧬 Code graph analysis (2)
app/Actions/Photo/Convert/ImageTypeFactory.php (1)
app/Enum/ConvertableImageType.php (1)
  • isHeifImageType (16-24)
tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php (6)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (2)
  • ConvertUnsupportedMedia (16-41)
  • handle (23-40)
app/DTO/ImportMode.php (1)
  • ImportMode (14-41)
app/DTO/ImportParam.php (1)
  • ImportParam (14-33)
app/DTO/PhotoCreate/InitDTO.php (1)
  • InitDTO (18-61)
app/Exceptions/CannotConvertMediaFileException.php (1)
  • CannotConvertMediaFileException (18-26)
app/Image/Files/TemporaryJobFile.php (1)
  • TemporaryJobFile (20-76)
🔇 Additional comments (4)
app/Actions/Photo/Pipes/Init/ConvertUnsupportedMedia.php (1)

1-41: LGTM! Clean implementation of the conversion pipe.

The implementation correctly:

  • Preserves the exception chain by passing the original exception as the second parameter to CannotConvertMediaFileException
  • Uses strict comparison (===) for null check
  • Handles extensions with or without leading dots via ltrim()
  • Follows the single responsibility principle by delegating conversion to the factory
tests/Unit/Actions/Photo/Pipes/Init/ConvertUnsupportedMediaTest.php (2)

183-243: Good coverage of exception wrapping behavior.

The tests properly verify that:

  1. The original exception is preserved via getPrevious()
  2. The wrapper message includes context about the conversion failure
  3. Both base Exception and RuntimeException are handled consistently

245-257: Clean helper method for test setup.

The createInitDTO helper properly encapsulates the DTO construction, making tests more readable and maintainable.

app/Actions/Photo/Convert/ImageTypeFactory.php (1)

27-46: Well-structured factory method with proper guards.

The make() method includes all necessary validations:

  • Null check before attempting instantiation
  • Class existence verification
  • Interface implementation check

This defensive approach prevents runtime errors from bubbling up unexpectedly.

@ildyria
Copy link
Member

ildyria commented Jan 7, 2026

Hi,

Thank you for your pull request. I really agree that we need to have a pre-processing step that can be further extended.

However, I have a few issues that needs to be resolved:

  • the vulnerabilities in the golang binaries see the findings from trivy. Those are simply not acceptable.
  • Update go.mod MaestroError/php-heic-to-jpg#37 < this update does not inspire me trust in the maintaining dev.
    This has been dragging for more than 1.5 year, we are now on go 1.25, this should be kept up to date.

Looking more into details:

So converting via Imagick, no issue, totally approved.
Converting with this tool is going to be a big nope.

With regard to Imagick failing:
ImageMagick/ImageMagick#7426 (comment)
It seems that dependencies needs to be updated mostly.

@nbalkandzhiyski
Copy link
Author

@ildyria
Hi,
Thank you for your response.
I suspected we'll have issues with the fallback package.

I'll explore the possible solutions for libhief.
Currently the issue seems indeed to be related with libheif and not ImageMagick itself.

libheif version greater than 1.15 is not able to get installed on Debian versions <= 12 or Ubuntu <= 25.04 out of the box and as from your comment the possible solution will be to update libheif to >= 1.18.2

I'll update the PR once I have a resolution regarding this and update the docs if needed

Regards,

@ildyria
Copy link
Member

ildyria commented Jan 8, 2026

libheif version greater than 1.15 is not able to get installed on Debian versions <= 12 or Ubuntu <= 25.04 out of the box and as from your comment the possible solution will be to update libheif to >= 1.18.2

Yeah, but Debian versions <= 12 are no longer considered. 😃
We consider only Debian stable which is Trixie - aka version 13! 🥳

https://tracker.debian.org/pkg/libheif
stable: 1.19.8-1

Ubuntu is not in the scope of Lychee as our base images are on alpine or debian stable.

What I would do instead is to see if we can have a check in diagnostics for the version of libheif, and display a warning if it is too old. That way we do not have to deal with the fallback package. 👍

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.

3 participants