Updated: Comment #22
Problem
This issue is a spin off of issue #2073759: Convert toolkit operations to plugins, more specifically comment #71 (#2073759-71: Convert toolkit operations to plugins).
In that issue we discovered that it is difficult/impossible to cleanly add toolkit specific code for new image effects. On working towards a solution we found that defining a new toolkit (e.g. Imagemagick toolkit) is also going to be hard with the current design.
This issue focuses on making it more easy/logical to implement an ImageToolkit in contrib.
Problems:
- Image/ImageInterface: define a GD resource handle and methods getResource(), setResource(), hasResource(). These are in fact GD specific and will only be ballast for other toolkits. (What should an imagemagick toolkit return in hasResource()?)
- Contrib toolkits will not use a GD resource, but will have their own data to store. Where to store this:
- In image: iin PHP it is technically possible to add your own properties, but this is not considered a good practice.
- In toolkit instance: this will tie a toolkit instance to a specific Image instance but allows contrib toolkits to add properties as needed.
- In their own newly defined MyImage class: the image factory from core is not made up to this task, so this will be difficult to implement.
Proposed resolution
We chose to go for the 2nd option above: tie toolkit instances to specific Image instances, so a toolkit can store all data it needs in its own object.
Concrete changes this patch implements:
- move the resource handle from the image class to the GDToolkit plugin.
- move Image::getResource() and Image::setResource() to the GDToolkit as well.
- drop Image::hasResource() and replace with an Image::isExisting() method that will just return if an image is existing and 'manageable' for the toolkit.
- the ImageFactory creates a new instance of the toolkit when creating a new image. This is because in this approach each Image instance will get a separate toolkit instance, so that image specific information managed by the toolkit can be stored at toolkit level. ImageFactory is receiving the toolkit manager in the constructor then, no longer the toolkit itself, and we no longer need 'image.toolkit' as a service.
- the
setToolkit()
method in the ImageFactory is replaced by asetToolkitId()
method, as the ImageFactory no longer needs to store an instance of the toolkit. Every Image will get its own toolkit instance. Also, the ImageToolkitManager has a newgetDefaultToolkitId()
method to respond to ImageFactory request for the ID of the default toolkit plugin. - Image has also an Image::getToolkit() method to retrieve the toolkit object stored in the Image instance. That is needed to enable tests and to allow access to the toolkit object by contrib code.
- the load() method in the GDtoolkit becomes an internal protected method to the GDtoolkit.
Remaining tasks
- Review patch.
- Write change notice. (Update current change notice?)
User interface changes
None, this is pure DX.
API changes
Image system will be changed. As this is already completely different from D7, and image related contrib maintainers actively participate in these issues, that should not be a problem.
Related Issues
- #2073759: Convert toolkit operations to plugins.
- #2103635: Remove effects from ImageInterface.
Comment | File | Size | Author |
---|---|---|---|
#30 | interdiff_26-30.txt | 6.67 KB | mondrake |
#30 | gd_move-2103621-30.patch | 30.68 KB | mondrake |
#26 | interdiff_24-26.txt | 6.21 KB | mondrake |
#26 | gd_move-2103621-26.patch | 29.31 KB | mondrake |
Comments
Comment #1
claudiu.cristeaI agree 100% and I stand for this. We need to make
ImageInteface
toolkit-agnostic.It's not so clear to me why we would do that. While Image will became an abstraction having no toolkit logic it seems to me useless. Can you provide more details?
I admit that I cannot see a reason for that, because Image factory is a system service. Being a service, you can decide to swap the class to be used by that service. For particular site you may use you own image class. And, again, I don't think we want Images that are toolkit specific. We want the default implementation of
ImageInterface
which isImage
to work with any toolkit is provided. I'm against having different Image classes for different toolkits.I would keep this issue only to remove the GD logic from image. But let's discuss this and if you are right I will add other tickets for that.
Changed the title and created a meta-issue for all image work here #2105863: [meta] Images, toolkits and operations.
Comment #2
fietserwin- When working with the GDToolkit, an image object has a resource.
- When working with the Imagemagick toolkit, an image has a set of parameters to be provided to the convert(.exe) binary?
So where do you want to store this information?
In other words:
- What do you want to do with the (logic in the) methods getResource(), setResource(), hasResource()?
I see an image as a dataholder, thus IMO, resource belongs to Image and effects (operations) should stay out of Image.
Comment #3
claudiu.cristeaChanging priority because of #2105863-13: [meta] Images, toolkits and operations.
Comment #4
fietserwinWhere do toolkits store their image related data (resource for GD, ops for ImageMagick)? We should be be careful to make toolkit instance <- >image instance a 1-1 relationship. There is no need for it and the current way of instantiating does not assume this. I could instantiate a new instance (at this moment I even have to, as I cannot get the toolkit from an image instance) of toolkit and do some operations on an existing image. When ,subsequently, the original toolkit instance does the save operation, it will go wrong if the toolkit stores image related data in its own instance.
- I oppose to adding properties to objects without them knowing it (becasue in PHP, I can do this on any object: $object->myFunkyProperty = 3;)
- I prefer adding a type property (resource, array ops) above defining some unnamed and untyped bag/collection of data that anyone can add its data to. D8 prefers clean defined interfaces above passing around arrays.
Assuming that a toolkit defines the image type, and I still stand by that, it should not be the case that implementing a toolkit means also defining a new image factory that swaps out the system provided one. Especially not because toolkits can live alongside each other (to the extend that they can operate on different images, not the same).
So tying image type to a toolkit means that the toolkit should create the instance, and thus the image factory should either call that toolkit method, or the toolkit is also seen as being the image factory, thereby combining the 2 classes into 1.
Comment #4.0
fietserwinAdded related issue.
Comment #5
mondrakeSo, I know we are still in outer space here, nevertheless I wanted to give a first try.
This patch is on top of
#2122605: Remove isAvailable() from ImageToolkitInterface
which itself is on top of
#1069140: Requirements should be provided by image toolkit, and
#2111263: Toolkit setup form displays settings for multiple toolkits.
the do-not-test patch is the topping one, the other is just for testing.
The idea:
Comment #7
mondrakeFixed around some test failures.
Comment #8
mondrakeTestbot failed to update #5 to NW
Comment #9
mondrakeBack to NR for bot to process #7
Comment #10
mondrakeOops
Comment #11
mondrakeRe-roll of #7 after #2103635: Remove effects from ImageInterface went in. No changes, no interdiff.
Comment #12
mondrakeA new patch, this time against head and doing the bare minimum (it's no longer on top of #2122605: Remove isAvailable() from ImageToolkitInterface). The patch size is halved and I hope this can get more reviews now.
This still does:
Comment #13
fietserwinTying Image and Toolkit on a 1-1 relation is not what I prefer, because this simply means that they are actually one and the same, so it would be better to merge the 2 interfaces.
However, to get some progress, I am willing to accept this for now. But would like to see the following changes as well, mostly aimed at further hiding the artificial (or if you like historically grown) separation between image and toolkit:
Regarding the list of your changes:
Comment #15
mondrakea) I missed to revert one change to the ToolkitGdTest from patch in #11, corrected here.
b) Removed load() from ImageToolkitInterface and changed to a protected method in GDToolkit and TestToolkit.
c) In ToolkitTestBase, this change was not making sense
so changed to an assert
@fietserwin re #13 please open follow-ups for those, let keep this as small as possible
Comment #16
fietserwinFollow up for width/height problem is OK. But if we are going to tie the toolkit 1-1 to an image, there should no longer be a need to instantiate a toolkit separately in other code:
If the image say it is not supported then ask the image what is does support. We can easily do so by adding that method to Image as well.
I will try to review the patch later on.
Comment #17
mondrakeFound a problem.
Since in current head we have in Image a __call magic method that will pass any method not defined in Image to the underlying toolkit, setResource() and getResource() can still be invoked in a call from Image, as they are public methods in the toolkit (and need to be so, for testing purposes and to allow temp (contrib) code to access the GD resource if it needs).
This causes problems if there's temp code still making such calls through $image->getResource() and not through $image->getToolkit()->getResource(). (I had such a case in my wip conversion of Textimage).
This will go away when we drop the __call method from Image, but for the time being and to avoid mess, I introduced a check to fail hard with an exception in case of a $image->setResource (or getResource, or hasResource).
Comment #18
fietserwinI guess that the get/setResource() indeed must stay public. Not only for tests, but also to give the (yet to be committed) toolkit operations access to the resource and even allow them to replace it with another resource, which is typically done by canvas operations, but also by the core crop operation (IIRC).
However, the only calls that we expect via __call are the methods that were removed in #2103635: Remove effects from ImageInterface. So I would propose to use a white list containing those methods instead of the black list you added in #17.
Comment #19
mondrakeWell no, because if someone wants to swap the GDToolkit class with her/his own in the plugin, having additional operations in there (that would be the only alternative right now to extend the toolkit), s/he needs to access them through the current model $image->operation(...). The blacklist is really only for safety around the xxxResource() calls.
Comment #20
fietserwinRemove the if around the statement: processInfo() already checks on processed.
I would really like to see the white list here. Your argument about swapping in a new toolkit with new methods does not hold. That was not possible before #2103635: Remove effects from ImageInterface and was also not the idea of that issue. (It only shows how necessary it is to continue working on the image system.) Moreover, your change from getToolkitId() to getToolkit() in this very patch makes it possible to call new methods on toolkits and thus should not be done by __call().
To assure that none of the patches that go in until __call() is replaced by apply() abuses this hole, it is better to explicitly check for what is allowed/expected. In fact it should have been part of the patch of the other issue.
So we need (or create) a toolkit instance to create a new one (of the same type) that will be tied to the Image that we are creating. Ugly. This is either a follow-up issue (that should be created before RTBC'ing this issue, although a patch can be postponed until after this patch gets in) or should be solved here and now.
To solve I think that the toolkit manager should work with toolkitId's instead of toolkit instances.
Looking at the code, the property processed does not mean processed but existing or valid. So I propose to rename the property to isValid. This is a small change that should arguably be part of this patch. This patch is not only about cleaning up the interface with GD specific methods, but also about replacing some "abused" or incorrectly named methods with a correctly named counterpart. So renaming this internal property should go with it.
Shall we just remove this method (and the call to it from getInfo). This is a test toolkit and does not have to resemble the GdToolkit. This doesn't seem to be appropriate here (anymore).
TRUE if the image exists and is a valid image, FALSE otherwise.
BTW: I'm fine with leaving getToolkitId() in, thus not replacing it with getToolkit() but just adding the latter.
Add a supportedTypes() to ImageInterface, see comment #13 and #16.
All of the following seem to better express what this test wants to test: assertFileExists, assertNotEmpty or (IMO the best) assertTrue(is_readable(...))
Some background info on my remarks:
My remarks may be seen as scope creep for this patch. But they are not. By moving the resource property from Image to the GdToolkit (and thus in contrib the ops array of IM to the IM toolkit itself), this patch chooses to tie Image and ImageToolkit instances into a 1-1 relation. Doing so has some very important consequences that should be handled by this patch itself, not by a follow up.
Comment #21
mondrakeReroll of #17 because of #2053153: Allow contrib modules to provide plugins on behalf of optional modules; no changes to the patch code.
Comment #22
mondrakeUpdated issue summary with proposed resolution in #12.
Comment #23
mondrakeComment #24
fietserwinFurther updated the issue summary, to reflect the currently chosen solution.
Comment #25
mondrake@fietserwin re #20
I agree but doing that here would imply lot of changes to the ImageFactory (the factory has an instance of the toolkit as a protected property, we would need to change it to the toolkit ID but also change the setToolkit method to setToolkitId method, adjust test etc. - will double the size of this patch, so I suggest to open a followupEDIT - reflected changes to point 3 as implemented in #26
Comment #26
mondrakeSo... second thinking... point #20.3 addressed here. We are removing the setToolkit() method in ImageFactory and replacing with a setToolkitId() method. The concept is to avoid having a dummy, useless toolkit instantiated in the factory, as in any case the factory will instantiate a new one for every new Image object. This requires a mention in the issue summary.
Comment #27
mondrakeUpdated issue summary for the changes in #26
Comment #28
fietserwinThanks for reworking the patch. I will review later, but I can already say that I will agree on not doing;
- 20.2 white list: OK we leave it as is, it's temporary anyway.
- 20.4/5: OK: I will create follow up issues.
However, I stick with the idea behind:
- 20.8: the toolkit becomes an (internal) implementation detail that should no longer be called directly by external code (that's why the getToolkitId() may (should) remain aside of getToolkit()). We do everything via the image: load, isSupported, all operations, save, etc, but not this 1 method? Doesn't make sense: so we should get the toolkit out of all other code that is not part of the image system.
There is something strange as well here: To find out if the image is supported, there is call to getType, which in turn calls processInfo(), which in turn calls the toolkit's getInfo(). If this latter method would really be toolkit dependent, I may expect an error/exception on non-supported types, but there is nowhere any check on return values. processInfo() returns true on all files: supported images, non-supported images, and even on non images).: time for a follow-up issue after all? (should be combined in a follow up with the processed->isValid rename)
Comment #29
fietserwinIt is not obvious why that call to processInfo() is here. It is because methods in the toolkit (load/save) expect the processInfo(), or actually getInfo() in the toolkit, has already run. So adding a comment would be nice.
or will be replaced through issue 2110499 ...
If the image type is not supported, this method (or actually the implementation of it in Image) still returns TRUE. Is that what we want? Or are we going to sort that out in the follow-up?
See above remark: && $image->isSupported(). Or are we going to sort that out in the follow-up?
A GD image resource? See also remark below.
- I would prefer consequent use of 'GD image resource' in the doc blocks (thus not image file handle, image file resource, as the resource has nothing to do with the file)
- Returns this (GD)Toolkit
1 line?
@param array $details
Why don't we use the getter and setter internally as well? Using getters and setters internally is less error prone when overriding by inheritance or future change. (Multiple places)
Same here, see remark above
So we do load not supported image types. Part of the follow-up or solve here?
@param int $type
@param array $details
Unlike this list might suggest, we are getting close. I think that the exact definition of isExisting() versus isSupported() is better sorted out in a follow-up. It is not this patch that introduces this. n general, I notices that when we introduced the isSupported()/supportedTypes() we only focused on external use of that (by the image/file field) not on using this ourselves to do some sanity checks.
Comment #30
mondrake#29:
1. I also do not like how processInfo() is used in each getter method in the Image class, but this is not introduced in this patch and the implementation of getToolkit() here is just following that pattern. I agree we can find a better way, but not here pls.
2. OK
3, 4, 11. Added a check if the type is supported in toolkit's getInfo() method.
5, 6, 7, 8, 9, 10, 12, 13. OK thanks
Comment #31
mondrakeComment #32
mondrakeChanges to issue summary in #27 were inadvertently reverted in #28 (xpost)
Comment #33
fietserwin#30 re#29.1: This processInfo() is only called in getters that depend on the info being available/initialized/processed. Therefore we have these getters (we can do some initialization in it). However, getToolkit() is not such a getter as it does not in itself depend on the source image being processed. Therefore, I was a bit surprised to see it being called anyway, but then discovered that it probably is needed to prevent errors further on, when methods on the returned toolkit are called. Therefore I wanted the comment, but this is so minor that I'm going to RTBC it anyway
I created follow-up issue #2211227: Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType.
Comment #34
alexpottCommitted f437807 and pushed to 8.x. Thanks!
We need a change notice to cover all the change being implementing by #2105863: [meta] Images, toolkits and operations
Comment #35
mondrakeEdited preexisting change record, see https://drupal.org/node/2084547/revisions/view/2833681/7012959