Problem/Motivation
If we were to create an image handling library that supports multiple image handling toolkits from scratch, how would we architect it?
- We would look at currently existing toolkits like Gd, Imagick and Imagine and then embed a similar structure into Drupal (using managers, plugins, factories, etc.).
- So, we would define an Image processing plugin.
- So, we would create a manager that can create the image manipulation objects for a given toolkit.
- We would implement 1 plugin per supported toolkit. This plugin is able to do image manipulation using a given toolkit. This would mean it is able to create an in-memory image resource based on an image file, transform that resource and save it to a file in a given image format.
- We would make sure that we would restrict ourselves to image manipulation, not a file handler, only image file opening and saving interacts with files.
How do we currently do it?
- We have a system like the above, its named ImageToolkit (+interface, manager, image processing operations, etc.)
- However, we also have exactly 1 Image class, not meant to be subclassed or replaced.
- We have an image factory that can create exactly 1 kind of implementation of ImageInterface, namely the above Image class.
- This Image class forwards all calls to an image toolkit plugin that is 1-to-1 tied to it. Calls that it does not forward are an erroneous try to do some file handling in an image processing library.
- The outside world (image styles, effects and fields) talks to Image to do image manipulation. The toolkit is almost invisible.
- The outside world (ImageStyle download controller) misuses image to serve files.
- As image is not more than an empty proxy class, our tests contains lots of duplication. Tests at the toolkit level ar repeated at the image level.
Looking at it, we conclude that Image does not serve any explicit purpose and that we can as well merge Image and ImageToolkit into 1 plugin system.
Some discussions already happened in the past between @mondrake, @fietserwin and @claudiu.cristea to 'unify' Image and ImageToolkit, but that was premature at that stage. Now the Image object is much slimmer and the idea becomes more obvious.
Further explanation of the idea
Looking outside Drupal (at other Image libraries):
- If GD would be rewritten in a OOP way, there would be a GdImage class that contains the resource and provides all the operations.
- Imagick has already 1 class containing bot the image and the operations.
- Imagine offers 1 interface (ImageInterface) and various implementations (GdImage, ImagickImage, GMagickImage). ImageInterface defines "all" the operations like load, save, resize, rotate, etc. Thus also here: data and operations in the same object.
This suggests that sticking with 1 Image interface and an implementation of that per underlying toolkit is the normal way to go.
Looking at sound OO principles:
- Keep data and operations in 1 class.
- To much coupling between 2 components is a code smell. The 1-1 relation between Image and ImageToolkit is such a code smell.
- Proxy classes are a code smell: Image is quite empty, it forwards almost all calls to its strongly coupled toolkit brother.
Looking at open issues:
- #2257163: Restrict image system to image processing
- #2063373: Cannot save image created from scratch
- #2109459: Review image test suite
All these issues are addressing problems that (partly) originate due to current artificial separation
But why is it the way it is?
Simply: historical reasons. In D7, the image system was not OOP, there was an image object and toolkit operations were defined based on a naming scheme. OOP'ing this system in D8 was done by simply defining classes around existing concepts, not refactoring or rearchitecting the system.
Proposed resolution
This issue/patch:
- Merges Image and ImageToolkit (and its interfaces)
- Merges the image factory and image toolkit manager. By merging the above 2 classes/interfaces, Image becomes a plugin and thus we should have a manager, no longer a factory.
- [Optional] Rename everything to Image and drop the use of Toolkit in class, namespace and variable names. This also means moving classes to other folders. In documentation use "image plugin".
Remaining tasks
- The patch needs a reroll.
- Agree on naming: Image or ImageToolkit? Image is shorter and already used outside the subsystem,so hardly any changes required there. ImageToolkit is more explicit about the fact that it is an image handling library.
- restore the "value" behavior of Image objects. An image object should not be reused for different files or for creating different blank canvases. So there should be no methods to set the file or create a blank canvas, this should be part of the constructor.
User interface changes
None.
API changes
Image was already the interface that the outside world used. Toolkit was almost invisible to that outside world. So, if we rename ImageToolkit to Image not much is changing at the API level.
Think of things like:
ImageInterface::getToolkit()
will be removed.ImageInterface::getToolkitId()
will be replace by the already in the parent existing getPluginId().- ImageFactory becomes ImagePluginManager.
As usual, change record https://www.drupal.org/node/2084547 (that details the new Image API) will be updated as soon as this patch gets committed.
Comment | File | Size | Author |
---|---|---|---|
#5 | 2336811-5.patch | 171.9 KB | mondrake |
#1 | 2336811-1.patch | 154.77 KB | mondrake |
Comments
Comment #1
mondrakeFirst go.
This patch, built on top of #2331481: Merge Image and ImageToolkitBase, removes the 'toolkit' layer and terminology from core, and converts 'images' to plugins. It introduces an
ImageManager
plugin manager that replacesImageToolkitManager
andImageFactory
. Temporarily, to reduce the size of the patch, theimage.factory
service remains as an alias toimage.manager
. That could be cleaned in a follow up.To summarize current vs. patch
Current status:
a) when an image is needed, code calls
ImageFactory
- this instantiates anImage
object, and anImageToolkit
plugin object viaImageToolkitManager
, associates theImageToolkit
plugin to theImage
object, and returns the Image object to the caller.Image::parseFile()
is executed at construction time to get the image from the file system.b) when an image needs to have an operation applied, code calls
Image::apply()
. In turns,apply()
gets anImageToolkitOperation
plugin object from theImageToolkitOperationManager
, passing theImageToolkit
object to it, and calls the methods from theImageToolkitOperation
that modify the content of theImageToolkit
object.With this patch:
a) when an image is needed, code calls
ImageManager
- this instantiates anImageInterface
plugin object, executesImageInterface::createFromSource()
to get the image from the file system, and returns theImageInterface
object to the caller.b) when an image needs to have an operation applied, code calls
ImageInterface::apply()
. In turns,apply()
gets anImageOperation
plugin object from theImageOperationManager
, passing theImageInterface
object to it, and calls the methods from theImageOperation
that modify the content of theImageInterface
object.Comment #2
fietserwinFirst part, I still have to go through a number of files, but these are my remarks so far.
I don't understand this change to the 7.0 part of the changelog. Perhaps create an addition in 8.0?
@todo and/or already create a follow-up issue?
No longer needed: same namespace
accidentally removed space: restore
> 80 chars.
trait is a left over from a former patch: let's change it to base class here.
why the change to plural. Moreover, I think this text is bogus, it is system that provides a default image plugin and setting, not the image module... not sure though if this thus should go the system module help.
Comment #3
fietserwinShould (and can) the create method be moved to ImageBase? Why should toolkits have to repeat this boilerplate code?
I wouldn't mind to change the parent class to KernelTestBase in this patch as well.
If I look at GDImage, I see that the form methods are named with ConfigurationForm. So, instead of renaming to ImagePluginFormTest, I propose to rename the class to ImagePluginConfigurationFormTest and the method to either testForm (as the class name gives enough context, my preference) or testImagePluginConfigurationForm.
Rename the property imageFactory of this class to imageManager and adapt the type as well, it is now referring to a non-existing type.
If renaming adds too much patch size (because it will also effect child classes, add a @todo and follow-up (or a remark to the review image test suite issue.
This finishes my review. I also updated the issue summary and title.
If this patch gets too large, we could defer many renamings (variables and methods) to a follow-up, we could leave getToolkitId()
Comment #4
mondrake#2.1 - my bad :( reverted
#2.2 - leaving as is at the moment, I'd say we need a cleanup followup to remove instances of 'image.factory' in the codebase, as well as variable names and docs
#2.3 - done
#2.4 - done
#2.5 - done
#2.6 - done
#2.7 - reverted to singular, as well as the menus in the system module 'Image toolkit' => 'Image plugin'; moving the entire text somewhere else I'd leave for some documnetation issue if that needs be
#3.1 - it's a static method that contains plugin specific services needed - I'd leave that to be set explicitly in each plugin
#3.2 - suggest for a followup (there's an issue open already)
#3.3 - will do in a new patch
#3.4 - done, but see also reply to #2.2
#3.5 - one is a PHP unit test, the other a Unit test, would leave any merger (if possible) to a later stage
also rerolled
Comment #5
mondrakeThis patch removes the alias to 'image.factory' and all the remaining references to it in the code base, variable names and documentation, so no need for follow-up. Also renames the image plugin form test as per #3.3.
Comment #7
mondrakeSorry for the misnaming of the interdiff. Changing the issue title to better reflect what this patch is doing, after the parent issue was closed as duplicate.
Comment #8
tim.plunkettIn the parent issue I explained my opposition to this. The new issue title makes it even clearer. Images are NOT plugins! This makes no sense. Toolkits are plugins, images are value objects.
Comment #9
fietserwinI think it will need a reroll after #2215369: Various bugs with PHP 5.5 imagerotate(), including when incorrect color indices are passed in.
RE #8: You are right. the title is misleading as we are actually removing image (moving some yet to be removed methods to toolkit), not toolkit. But then we are renaming toolkit to image as that is the public facing interface name. We could stay with ImageToolkit though and change calls to Image to ImageToolkit. Looking at other image manipulation tools , I see that they tend to name the toolkit class like Image but a bit different, like: Imagick or Imagine. I don't have a real preference.
Comment #10
fietserwinAs the parent never got committed, I merged the issue summaries of the (former) parent and this one and removed the reference to it. i also better explained our ideas (I hope) to avoid confusion and start discussing the real architectural improvements behind this issue.
Comment #11
mondrakeI do not think there is a chance to align on this for D8, also based on the beta criteria for issues. Matter for D9?
Comment #12
fietserwinAPI/Interface changes would be minimal, only effect (toolkit operations)/toolkit contributors would have to rename/move some parts of their work, not even really update their code. So 8.1 seems feasible to me (perhaps with some b.c. hacks like making ImageToolkit derive from Image instead of removing it).
Comment #13
catchYes let's see what doable in minor releases,