-
Notifications
You must be signed in to change notification settings - Fork 7
Add support for themes service data #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR extends the DiagramGenerator to support theme URLs from a new themes service while maintaining backward compatibility with legacy hardcoded theme names and paths. The main addition is a themeUrls field in the Config class that allows consuming theme assets from dynamic URLs rather than static file paths.
- Adds
StorageNewclass to handle theme URL-based image fetching and caching - Extends
Configclass withthemeUrlsproperty and related getter/setter/checker methods - Updates
Boardclass to conditionally useStorageNewwhen theme URLs are configured - Adds comprehensive test coverage for the new theme URL functionality
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| src/DiagramGenerator/Config.php | Adds themeUrls property with getter, setter, and hasThemeUrls() method to support dynamic theme URLs |
| src/DiagramGenerator/Board.php | Conditionally instantiates StorageNew when theme URLs are configured, otherwise uses legacy Storage |
| src/DiagramGenerator/Image/Image.php | Updates type hints to accept both Storage and StorageNew, adds logic to detect board textures from theme URLs |
| src/DiagramGenerator/Image/StorageNew.php | New class implementing theme URL-based image fetching with URL-based caching strategy using MD5 hashes |
| tests/DiagramGenerator/Tests/ConfigTest.php | Adds tests for theme URLs getter, setter, and validation methods |
| tests/DiagramGenerator/Tests/Image/StorageTest.php | Comprehensive test suite for StorageNew including cache path generation and error handling |
| tests/DiagramGenerator/Tests/Image/ImageTest.php | Tests verifying detection of board textures from theme URLs vs legacy mode |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| } | ||
|
|
||
| /** | ||
| * Fetches remove file, and stores it locally |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Typo in comment: "remove" should be "remote".
| * Fetches remove file, and stores it locally | |
| * Fetches remote file, and stores it locally |
| protected function removeDirectory($dir) | ||
| { | ||
| if (!is_dir($dir)) { | ||
| return; | ||
| } | ||
|
|
||
| $files = array_diff(scandir($dir), array('.', '..')); | ||
| foreach ($files as $file) { | ||
| $path = $dir . '/' . $file; | ||
| if (is_dir($path)) { | ||
| $this->removeDirectory($path); | ||
| } else { | ||
| unlink($path); | ||
| } | ||
| } | ||
|
|
||
| rmdir($dir); | ||
| } |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removeDirectory() helper method is duplicated across multiple test files (StorageTest.php and ImageTest.php). Consider extracting this to a shared test trait or base test class to reduce code duplication and improve maintainability.
| protected function removeDirectory($dir) | ||
| { | ||
| if (!is_dir($dir)) { | ||
| return; | ||
| } | ||
|
|
||
| $files = array_diff(scandir($dir), array('.', '..')); | ||
| foreach ($files as $file) { | ||
| $path = $dir . '/' . $file; | ||
| if (is_dir($path)) { | ||
| $this->removeDirectory($path); | ||
| } else { | ||
| unlink($path); | ||
| } | ||
| } | ||
|
|
||
| rmdir($dir); | ||
| } |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The removeDirectory() helper method is duplicated across multiple test files (StorageTest.php and ImageTest.php). Consider extracting this to a shared test trait or base test class to reduce code duplication and improve maintainability.
| protected function createConfigWithThemeUrls() | ||
| { | ||
| $config = new Config(); | ||
| $config->setFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'); | ||
| $config->setSizeIndex('100px'); | ||
|
|
||
| $size = new Size(); | ||
| $size->setCell(100); | ||
| $config->setSize($size); | ||
|
|
||
| $theme = new Theme(); | ||
| $theme->setName('test'); | ||
| $config->setTheme($theme); | ||
|
|
||
| $config->setThemeUrls([ | ||
| 'board' => 'https://example.com/board.png', | ||
| 'wp' => 'https://example.com/wp.png', | ||
| ]); | ||
|
|
||
| return $config; | ||
| } | ||
|
|
||
| protected function createConfigWithoutThemeUrls() | ||
| { | ||
| $config = new Config(); | ||
| $config->setFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'); | ||
| $config->setSizeIndex('100px'); | ||
|
|
||
| $size = new Size(); | ||
| $size->setCell(100); | ||
| $config->setSize($size); | ||
|
|
||
| return $config; | ||
| } |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The config creation helper methods (createConfigWithThemeUrls(), createConfigWithoutThemeUrls()) are duplicated between StorageTest.php and ImageTest.php. Consider extracting these to a shared test trait or base test class to improve maintainability and ensure consistency across tests.
| ]; | ||
| } | ||
|
|
||
| foreach($handles as $key => $handle) { |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing space after foreach keyword. This should be foreach ($handles (with a space) to maintain consistency with the coding style used elsewhere in the codebase.
| foreach($handles as $key => $handle) { | |
| foreach ($handles as $key => $handle) { |
| rename($fileHandle['tmpPath'], $fileHandle['realPath']); | ||
| } | ||
| } | ||
|
|
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File handles opened with fopen() at line 180 are never closed. After the curl operations complete and files are renamed, the file handles should be closed with fclose() to prevent resource leaks. Add a loop after line 202 to close all file handles:
foreach ($fileHandles as $fileHandle) {
if (is_resource($fileHandle['handle'])) {
fclose($fileHandle['handle']);
}
}| // Close all file handles to prevent resource leaks | |
| foreach ($fileHandles as $fileHandle) { | |
| if (is_resource($fileHandle['handle'])) { | |
| fclose($fileHandle['handle']); | |
| } | |
| } |
| rename($fileHandle['tmpPath'], $fileHandle['realPath']); | ||
| } | ||
| } | ||
|
|
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The curl_init() handles created at line 178 are never individually closed with curl_close() before calling curl_multi_close(). While curl_multi_close() may clean up multi handle resources, the individual curl handles should be explicitly removed and closed. Add cleanup after line 196:
foreach ($handles as $handle) {
curl_multi_remove_handle($multiHandle, $handle);
curl_close($handle);
}| // Cleanup: remove and close each cURL handle | |
| foreach ($handles as $handle) { | |
| curl_multi_remove_handle($multiHandle, $handle); | |
| curl_close($handle); | |
| } |
| $fileHandles[$pieceShortName] = [ | ||
| 'handle' => fopen($filePath . $uniqid, 'wb'), |
Copilot
AI
Nov 24, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If fopen() fails and returns false, it should be handled before attempting to use the file handle. Add error handling similar to the pattern in cacheImage() method (lines 261-263):
$handle = fopen($filePath . $uniqid, 'wb');
if (!$handle) {
throw new RuntimeException(sprintf('Could not open temporary file: %s', $filePath . $uniqid));
}
$fileHandles[$pieceShortName] = [
'handle' => $handle,
// ...
];| $fileHandles[$pieceShortName] = [ | |
| 'handle' => fopen($filePath . $uniqid, 'wb'), | |
| $handle = fopen($filePath . $uniqid, 'wb'); | |
| if (!$handle) { | |
| throw new RuntimeException(sprintf('Could not open temporary file: %s', $filePath . $uniqid)); | |
| } | |
| $fileHandles[$pieceShortName] = [ | |
| 'handle' => $handle, |
58ae5c9 to
dbdd4b9
Compare
982db02 to
be356d2
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 9 out of 9 changed files in this pull request and generated 13 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| public function testDrawBoardWithFiguresUsesLegacyTextureWhenThemeUrlsNotSet() | ||
| { | ||
| $config = $this->createConfigWithoutThemeUrls(); | ||
| $storage = new Storage($this->cacheDirectory, $this->pieceThemeUrl, $this->boardTextureUrl); | ||
| $image = new Image($storage, $config); | ||
|
|
||
| // Verify that hasThemeUrls returns false | ||
| $this->assertFalse($config->hasThemeUrls()); | ||
|
|
||
| // The actual drawing will fail due to missing images/colors, but we've verified | ||
| // the config setup is correct for legacy mode | ||
| $this->assertTrue(true); | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the previous test, this only verifies config setup without testing actual behavior. Consider adding assertions that test the actual drawing logic or exception handling.
|
|
||
| /** | ||
| * Gets background texture image from theme URL. | ||
| * |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing @param Config $config parameter documentation in the PHPDoc comment.
| * | |
| * | |
| * @param Config $config |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Useless, it's already in the code.
| /** | ||
| * Finds max height of piece image | ||
| * | ||
| * |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing parameter documentation. Should include @param Fen $fen and @param Config $config in the PHPDoc comment.
| * | |
| * @param Fen $fen | |
| * @param Config $config |
|
|
||
| /** | ||
| * Fetches piece image from theme URL. | ||
| * |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing parameter documentation. Should include @param Piece $piece and @param Config $config in the PHPDoc comment.
| * | |
| * | |
| * @param Piece $piece | |
| * @param Config $config |
| } | ||
|
|
||
| /** | ||
| * Downloads all piece images from theme URLs. |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing parameter documentation. Should include @param Config $config in the PHPDoc comment.
| * Downloads all piece images from theme URLs. | |
| * Downloads all piece images from theme URLs. | |
| * | |
| * @param Config $config |
| protected function createConfigWithThemeUrls() | ||
| { | ||
| $config = new Config(); | ||
| $config->setFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'); | ||
| $config->setSizeIndex('100px'); | ||
|
|
||
| $size = new Size(); | ||
| $size->setCell(100); | ||
| $config->setSize($size); | ||
|
|
||
| $theme = new Theme(); | ||
| $theme->setName('test'); | ||
| $config->setTheme($theme); | ||
|
|
||
| $config->setThemeUrls([ | ||
| 'board' => 'https://example.com/board.png', | ||
| 'wp' => 'https://example.com/wp.png', | ||
| ]); | ||
|
|
||
| return $config; | ||
| } | ||
|
|
||
| protected function createConfigWithoutThemeUrls() | ||
| { | ||
| $config = new Config(); | ||
| $config->setFen('rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR'); | ||
| $config->setSizeIndex('100px'); | ||
|
|
||
| $size = new Size(); | ||
| $size->setCell(100); | ||
| $config->setSize($size); | ||
|
|
||
| return $config; | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The createConfigWithThemeUrls and createConfigWithoutThemeUrls helper methods have duplicated setup code. Consider extracting the common config setup into a base method to reduce duplication, similar to the issue in StorageTest.php.
|
|
||
| /** | ||
| * Gets piece image from theme URLs. | ||
| * |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The method documentation is incomplete. It should include @param Piece $piece and @param Config $config to document the parameters, following the PHPDoc standard used in the codebase.
| * | |
| * | |
| * @param Piece $piece | |
| * @param Config $config |
| foreach($handles as $key => $handle) { | ||
| curl_setopt($handle, CURLOPT_FILE, $fileHandles[$key]['handle']); | ||
| curl_setopt($handle, CURLOPT_HEADER, 0); | ||
|
|
||
| curl_multi_add_handle($multiHandle, $handle); | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The cURL handle operations in the loop don't check for errors. Consider checking if curl_init() returns false and handling that case, or at minimum checking for errors after curl_multi_exec() to ensure downloads completed successfully.
| foreach ($fileHandles as $fileHandle) { | ||
| if (isset($fileHandle['tmpPath']) && file_exists($fileHandle['tmpPath'])) { | ||
| rename($fileHandle['tmpPath'], $fileHandle['realPath']); | ||
| } | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
File handles should be closed before attempting to rename the files. On Windows systems, attempting to rename an open file may fail. The file handles in $fileHandles should be closed in this loop before the rename() call, not later in the cleanup section.
| public function testDrawBoardWithFiguresDetectsBoardTextureFromThemeUrls() | ||
| { | ||
| $config = $this->createConfigWithThemeUrls(); | ||
| $storage = new StorageNew($this->cacheDirectory); | ||
| $image = new Image($storage, $config); | ||
|
|
||
| // Verify that hasThemeUrls works correctly | ||
| $this->assertTrue($config->hasThemeUrls()); | ||
| $this->assertTrue(isset($config->getThemeUrls()['board'])); | ||
|
|
||
| // The actual drawing will fail due to missing images/colors, but we've verified | ||
| // the config setup is correct for theme URLs | ||
| $this->assertTrue(true); | ||
| } |
Copilot
AI
Nov 27, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn't actually verify the behavior of drawBoardWithFigures. It only checks the config setup and then asserts true. Consider either calling the method and checking for expected behavior/exceptions, or making this an integration test that verifies the full drawing workflow.
src/DiagramGenerator/Board.php
Outdated
| $storage = new StorageNew($this->cacheDir); | ||
| } else { | ||
| $storage = new Storage($this->cacheDir, $this->pieceThemeUrl, $this->boardTextureUrl); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What I usually prefer is to rename old class to StorageLegacy, because ultimately we would won't to delete the old class and new one should have the default (Storage) name. One less step during FF cleanup.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I will change it. I used New suffix to make review easier, as it is obvious that original file was not changed.
| use Intervention\Image\ImageManagerStatic; | ||
| use RuntimeException; | ||
|
|
||
| class StorageNew implements StorageInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just curious, is this implementation AI-generated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes it is. As a Frontend dev I wouldn't do this PR by normal means. I started with Cursor implementation, but then I checked the code and tried to steer it to whatever looked as a good code to me. I tested code manually afterwards, but deep review by PHP senior is more then welcome.
|
@pavoljurov Do you know who are original Diagram generator authors? I think we should consult with them about these changes, add as reviewers for sure. |
It is very old codebase, but I see @umpirsky and @ikonic89 did some work 10 years ago. You guys have definitely good overview about this repository, would you mind to see this PR and review it? 🙇♂️ |
Co-authored-by: Nikola Poša <[email protected]>
Co-authored-by: Nikola Poša <[email protected]>
Co-authored-by: Nikola Poša <[email protected]>
Co-authored-by: Nikola Poša <[email protected]>
Co-authored-by: Nikola Poša <[email protected]>
|
What project are these changes being made for? Are there any BE developers on that project, is it a top 10 project? Some changes being made here seems fishy, e.g. the use of Also using I think these are quite serious, impactful changes which need to be thought out much better. I kinda doubt that Cursor you used for this has all these things in mind. |
This library is used by Symfony (main chess repo). I am not sure about other projects which use it. There are no BE devs, no top 10 project, no planning - just this PR. I was teased in Slack thread here and here, so I decided to
Cursor used exactly same tech which is used in already existing |
|
I defer to the original author @ikonic89. |
ikonic89
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| $destHeight, | ||
| $srcWidth, | ||
| $srcHeight | ||
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if backgroundTexture image doesn't have perfect dimensions, this could result in asymetric image generated, right?
Of course, if background is always perfect, then nothing to worry about.
umpirsky
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I reviewed the code, and the most concerning part is StorageNew. Name aside, which should be changed, the class looks like it needs some polishing. As @nikolaposa mentioned, it uses file system and network extensively, and does not follow our project patterns.
Maybe the way forward is exposing Storage and let clients implement their own storage. So the monolith can do $board->setStorage(), what do you think @nikolaposa ?
Then in main repo we can use project config, guzzle, caching etc. Not sure what is the future of this repo?
src/DiagramGenerator/Image/Image.php
Outdated
|
|
||
| public function __construct(Storage $storage, Config $config) | ||
| /** | ||
| * @param StorageInterface $storage |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's remove this dockblock and add type hints.
src/DiagramGenerator/Image/Image.php
Outdated
| * @param StorageInterface $storage | ||
| * @param Config $config | ||
| */ | ||
| public function __construct($storage, Config $config) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any reason type hint is removed from storage?
| public function __construct($storage, Config $config) | |
| public function __construct(StorageInterface $storage, Config $config) |
It is very old repository and I would like to avoid massive improvements there. I mimicked the code which existed already. Goal is to enable new themes easily. If it is not aligned with more recent project patterns and those patterns are must, I don't think this PR should be continued (at least not by me). |
|
As I said, maybe the way forward is exposing Storage and let clients implement their own storage. |
You mean like keep the |
Originally, DiagramGenerator accepted legacy theme and piece name (e.g. 'green'/'neo'), and composed asset URL from provided keys.
This PR extends DiagramGenerator config to consume theme URLs directly, so it can be used with new themes service.
Legacy themes with hardcoded names and paths are still supported.