Problem
This is a follow-up from #1069140: Requirements should be provided by image toolkit, where comment #1069140-35: Requirements should be provided by image toolkit noted that
There's an interesting crossover here between the requirements method and isAvailable method
.
To decouple the requirements checking from that "crossover", it was decided to create a follow-up issue, this issue.
It was noted that isAvailable():
- Is not cached, so called over and over again for each instantiation of the selected toolkit for ALL toolkits.
- Becomes a bit superfluous when requirements checking per toolkit has landed.
- Current way of returning false when no available toolkit exists, may lead to WSOD (though itmight also be a "recoverable fatal".
Proposed resolution
Remove isAvailable() all together.
This might lead to errors in a later stage, but only if requirements checking is ignored and the toolkit is indeed not available. Moreover, these errors probably occur on image derivative generation and thus won't result in a WSOD, "just" a missing image on a page. Image uploading and subsequent resizing due to field settings, is probably the exception here, as the image processing is done during page generation.
Remaining tasks
- Agree on proposed resolution.
- Write patch after the originating issue has landed.
User interface changes
API changes
ImageToolkitInterface will be changed, this should be documented in the same change notification as the originating issue.
| Comment | File | Size | Author |
|---|---|---|---|
| #35 | is-available_2122605_35.patch | 16.52 KB | mondrake |
Comments
Comment #1
claudiu.cristeaFew comments. This method is used only once, in
ImageToolkitManager::getAvailableToolkits(), to filter out broken toolkits:The interesting part here is, IMO, how the system is picking up the active toolkit. Right now Drupal is:
system.image.toolkitand use it if it's valid.This behavior was inherited from the legacy Drupal 7 functions: image_get_toolkit() and image_get_available_toolkits().
Here we have a problem. I don't think that the system should assign an arbitrary toolkit. This would hide the failure. I think the site admin should be warned that the configured toolkit is either uninstalled (if the provider module is no more enabled), either broken (something occurred on the platform — e.g. GD2 extension was uninstalled). Course this check should be performed also when the admin submits the toolkit form in
admin/config/media/image-toolkit.What I think now is that this issue must be more an issue about
getDefaultToolkit()thanisAvailable(). The functionality ofisAvailable()should be triggered only once, against the config stored toolkit and not all toolkits. I really don't know if this should be cached or not. At least for GD toolkit it doesn't seems an expensive operation (checking a function existence) but, in terms of performance, we already gained by not checking each toolkit. Probably we'll need to check each toolkit availability inadmin/config/media/image-toolkitbut still display all toolkits together with a warning message attached to broken ones — so that site admin is able to see the "whole picture". So, the active (configured) toolkit validity check may occur:A last note: While per-toolkit requirements is returning a classical
hook_requirements()array and not other availability information, how we'll removeisAvailable()? Regardless of the solution we follow, we still need to check the availability (e.g. toolkit form validation, when setting the "is available" cache, inimage_requirements(), etc).Comment #2
fietserwin- Correct: fail early, fail clear.
- We must show all toolkits, as, e.g. IM needs a path to the convert binary before it can be considered to be available. And this path can only be entered, on the admin/config/media/image-toolkit screen, once that toolkit has been chosen and its configuration form fields become visible.
- What is the problem at runtime? A comparison with other sub systems: E.g., there is no check either if the configured database is available and of the correct type. Drupal just fails if it is incorrectly configured. There is no check if the stream wrappers (temporary://, private://, and public://) are correctly configured. So why should we check if a toolkit is correctly configured time after time (for IM it needs an is_executable() call, thus file system access).
- Why: consider each registered toolkit as being available. Form validation means we can directly present messages to the admin, so a toolkit could either implement its own form validation or "abuse" the requirements() return values for that. I'm not sure if the toolkit needs to hook into the form or that the form needs a hook into the toolkit (the latter would probably indeed require a method on the interface, but for now I prefer the former).
- Note: because the algorithm to get the default toolkit is questionable, I don't want the patch of the originating issue to follow that algorithm... Especially as the algorithm is swappable and will probably be changed in this issue, it is not good to base requirements() on having to have knowledge of that algorithm.
Comment #3
mondrakeMy opinion: let's wait till #2111263: Toolkit setup form displays settings for multiple toolkits gets committed, then go with a single patch to address both this issue and #1069140: Requirements should be provided by image toolkit, built on top of patch in #1069140-50: Requirements should be provided by image toolkit. We can also adjust the Imagemagick conversion in the sandbox at the same time so to have two toolkits to play with.
Based on all the discussion, it seems to me that the key player in terms of checking the requirements would be the toolkit setup form, and a specific toolkit being set as default only on its requirements pass. Then the status report would be more or less 'subsidiary' i.e. a reference to check if something suddenly stops working - and that would happen only if something changed in the underlying setup (GD extension removed, path to convert changed, etc.).
Then I would go with
from #1: effects already fail with a watchdog entry if the toolkit call fails.
Comment #4
claudiu.cristeaWhy everybody want to mix #1069140: Requirements should be provided by image toolkit with other issues, like this one? #1069140: Requirements should be provided by image toolkit is a D7 issue but moved to D8 according to the rule that an issue must be fixed first in the development release and after that back-ported. That one has a specific scope: Stop hardcoding requirements from GD, let the active toolkit scream! If, hypothetically, this issue will never be committed, #1069140: Requirements should be provided by image toolkit is still a valid, standalone issue, just good to get in D8 and back-ported to D7. Because is doing only that simple thing: passes the requirements generation from image to toolkit. The only "sin" of #1069140: Requirements should be provided by image toolkit is that is using that damn
getDefaultToolkit()to get the active toolkit. But that's how we are selecting now the toolkit. So that is ready to go as it is because D7 is waiting for it too.Here, in this issue, is the place were we need to discuss and fix
getDefaultToolkit(), along with others. And if #1069140: Requirements should be provided by image toolkit is committed first we just need to touch againimage_requirements()and use maybe the config value instead that method. But that change is not related to how requirements are built but is related with this issue. That's why, even touching againimage.install, that change belong to this issue.Comment #4.0
claudiu.cristeaadded that we should agree on solution
Comment #5
mondrakeHere's a proposal, built on top of #2111263: Toolkit setup form displays settings for multiple toolkits and of #1069140: Requirements should be provided by image toolkit.
- drop the isAvailable() method from the toolkit.
- adds to the requirements() method a $form_state parameter, being the form_state of the Image toolkit setup form, to allow for bypassing stored config values while the toolkit is being setup (e.g. to change the path in ImageMagick).
- drop the getAvailableToolkits() method from the toolkit manager. Just use getDefinitions() instead, which bears all the toolkit plugins found (regardless if valid or not).
- getDefaultToolkit() in the manager will only return the configured toolkit. Will fail with an error message if no toolkit configured or toolkit is no longer existing (e.g. uninstalled).
- most of the checking is done via the toolkit setup form, at form validation. If this passes, we assume that the toolkit is configured properly, and make no runtime checks.
- the status report will report any error occurred 'outside' of Drupal toolkit configuration (e.g. change in PHP GD library, change in ImageMagick path).
do-not-test is diff against #2111263: Toolkit setup form displays settings for multiple toolkits and of #1069140: Requirements should be provided by image toolkit, the other is against HEAD.
Comment #7
fietserwin- Placing all of "Image toolkit configuration page" within the anchor tags looks more logical to me.
- replace the return FALSE by throwing an exception to fail more clearly. (Though I do not know the current consensus in Drupal regarding throwing exceptions), but returning FALSE will subsequently fail with a "Fatal error: Call to a member function ... on a non-object"." Throwing an exception with the given message makes the drupal_set_message redundant and shows more clearly what went wrong.
Not necessary to check for empty, a foreach over an empty array is perfectly possible and will yield the same result (and requirements() must return an (possibly empty) array).
Comment #8
mondrakedone
done
there are only two cases when getDefaultToolkit is used. One is in image.install, and if a FALSE value is returned, then it is managed. The other is more complex as it is connected to the instantiation of the image.factory service: here the default toolkit is looked for and instantiated as well, then passed on to the Image object. Not sure what will happen if we just throw an exception here. For the time being I just allowed the image.factory constructor to accept a FALSE value if no default toolkit is found. To me it seems we may need to sort this out with try/exception/catch in #2105863: [meta] Images, toolkits and operations in the interaction between Image and Toolkit - i.e. let an Image be created with no toolkit and catch the exceptions that Image will get into by calling a missing toolkit.
Comment #9
mondrakeBTW - I adjusted the Imagemagick conversion in the sandbox, to work with this patch. You can play with that to test if you wish.
Comment #10
fietserwinThanks for adapting the patch
Still not sure about this. Can an image exist without Toolkit? Apart from the reasons why one would like to be able to do so (an (Drupal) Image object is there to perform some manipulations on it, not for other reasons), looking at the code, almost every method of Image calls a method on the toolkit property (albeit indirectly) and thus will fail with a fatal.
The only methods of image that won't fail (if toolkit is not a toolkit object) are:
- setWidth/setHeight: there existence is an error in itself: width and height are adapted by operations from the toolkit, not by setting a property (I should create an issue for that).
- setResource/hasResource: GD specific, will only be called by the GD toolkit, thus if the toolkit is a toolkit after all.
- setSource/getSource: nice, but if you create an image only to set and retrieve a source ...
- chmod: there is a todo that this in the wrong place and it doesn't use $this at all...
So I would say: keep the type hinting and let's fail clearly with an exception.
Note: image_install could catch the exception and still do the same as when checking for FALSE.
Another option is to let ImageFactory::get() fail when toolkit is not a proper toolkit object. But then we would loose the type hinting again, unless we return null instead of false (which in itself is already more logical).
brackets not necessary
idem
Does the toolkit have a name that we can display to the user? (and is easily accessible here)
Comment #11
mondrakedone
done
I think not. For the time being I changed
ImageFactory::get()to return NULL if no toolkit is selected. There are a few points here:ImageFactory::get()can return NULL if no toolkit is set (which I believe it's sane), then currently in the codebase there are no checks that the returned Image object is valid. This leads to WSOD, e.g. in the image style preview. If we agree this is the way forward, then we need to add this checks (not done here).Comment #12
fietserwinGreat, patch looks good now.
11.1) That's why I want this data in Image, which implies:
- that an Image is created by the toolkit
- that A toolkit comes with its own class derived from Image (to rename to ImageBase)
11.2) MY fault, I thought that type hinting allows null (every other language I worked in does it that way). Thus, in practice, returning null turns out to be the same as returning false :(.
11.3) I still think it is not sane to allow images to be created without a working toolkit...
A search for all uses of ImageFactory (and thus Image I think) teaches me that Image is indeed only created:
- for processing (resizing on image upload if max dimensions are set for an image filed, image style handling)
- for testing if a (uploaded) file is an image and if its type is supported. This isSupported handling is (correctly) moved to Toolkit, as the toolkit defines which image types are supported.
So, I think, no toolkit is no image, hence throw an exception.
Comment #13
claudiu.cristeaI cannot review a "3 issues" combined patch. Can we have also a separate "do not test" patch only for this issue?
We don't want to popup anything here because we cannot anticipate where we'll use this manager method. Retuning
NULLshould be enough but I think even to throw here an exception instead.It seems that in code standards we cannot use both
{@inheritdoc}and custom docblock content. See {@inheritdoc}.I don't like, at all, the idea to mix requirements with form validation. Especially, to send
$form_stateas argument intorequirements(). The form is an UI element whilerequirements()is most something placed at the model level. I would let each toolkit simply provide its own form validation callback that should be called when validating — as the submit callback is called now. That one has the possibility to call its own requirements but ONLY if he decides that.Hm... See my final comment.
As generic comment: Even this issue goal seems to be "Drop isAvailable()!", I'm not convinced anymore that we need that removal. I see 3 level of errors here:
system.image.toolkitis empty). This one is already treated in #1069140: Requirements should be provided by image toolkit.isAvailable()and I think we can live with it. Action to take:image.toolkit) and add a proper description — handled inimage_requirements()Comment #14
mondrake#13, here's the do not test patch.
Comment #15
mondrakeYou're right. Changed approach here.
Corrected.
Not sure how to do that. Can you advise?
To the overall comment. I find having both isAvailable() and requirements() confusing. So I'd prefer if we can drop isAvailable().
Comment #16
fietserwinAgree. isAvailable() does not handle situation 2 from #13. It hides the problem instead of handling it (the way it currently is used). and there is no other factory that does some checking on each instantiation.
Exceptions:
Can we use an existing exception type?
- Exception
- PluginException
The former is to generic, the latter is also still qute non-descriptive and seems to be some kind of intermediate/base class. So I would go for creating our own type.
Name:
- ImageToolkitException
- Or we could differentiate the different error situations (config key not set, toolkit broken, class not available) and create 3 exception types. As all of them will be extremely rare, I would go for 1 exception type, thus not to differentiate.
Where should this class derive from?
- Exception
- PluginException
- UnknownPluginException
The latter seems to be specific for an incorrect id , so I would go for PluginException.
Comment #17
mondrake@fietserwin thanks. New patch tomorrow, with an ImageToolkitException extending PluginException.
Comment #18
mondrakeHere, ImageToolkitManager::getDefaultToolkit() returns null if it the default plugin is not set or missing, and ImageFactory::get() throws a new ImageToolkitException if the toolkit is not set.
Comment #19
mondrakeComment #20
fietserwinIsn't this by default? Anyway, this is more explicit, so perhaps better leave it.
@throws
I would throw the exception when getting the toolkit, it is a plugin failure after all. Moreover, it also solves point #13.1; and we retain the type hinting and don't have to complicate the docs by adding |NIULL (and describing it)
Comment #21
mondrake#20: OK, here we go. ImageFactory is now back to original. We have to try/catch getDefaultToolkit in image.install to manage invalid default.
Comment #23
mondrakeHm... #21 fails GD Toolkit unit test as there is no default toolkit set in config in that context, and the new exception is thrown on
at get('image.factory'), before the toolkit is reset in setToolkit($toolkit).
In other words, we can not instantiate the ImageFactory at all if there is not a configured toolkit, and we can set a different toolkit for the ImageFactory only if this is already instantiated. This is the effect of moving the exception throwing at the toolkit plugin level instead on ImageFactory::get().
Here I changed the test so to set explicity 'gd' as the default toolkit in config.
Comment #24
mondrakeComment #25
fietserwinOuch. This shows again how tied Image and Toolkit are to each other. IMO, setToolkit() on an already created Image looks ridiculous and dangerous. You can't change toolkit halfway, thus if you only can set it at the start, set it in the constructor. Let's get rid of this method. That would prevent this kind of code and errors.
(Other note: in my vision, the toolkit should be the factory, Doing it that way, would also state very clearly the link between the 2 classes).
Comment #26
mondrake#25 no, here we do not change the toolkit to the Image, we change it to the ImageFactory, so that if we get() an image from that factory, it will 'inherit' the toolkit set at that point (OK, in #11.1 we say there's a problem with 'inheriting' that toolkit instead of getting a new instance, but will address that elsewhere). I believe it's valid to provide developers the possibility to manage images with toolkits alternative to the default one; even if core won't use that.
Comment #27
mondrakeReroll, after #1069140: Requirements should be provided by image toolkit went in, and with latest patch from #2111263: Toolkit setup form displays settings for multiple toolkits.
This still requires #2111263: Toolkit setup form displays settings for multiple toolkits before being reviewable, so I'll mark it postponed after the test run.
Comment #28
mondrakeOoops...
Comment #30
mondrakeNeeds to wait for #2111263: Toolkit setup form displays settings for multiple toolkits
Comment #31
mondrake#2111263: Toolkit setup form displays settings for multiple toolkits landed, so we can reopen this. Needs reroll and re-thinking about how to manage ImageToolkitForm validation.
Comment #32
claudiu.cristeaThings that should be fixed here:
getDefaultToolkit().system.image.toolkitis empty.isAvailable(): (1) needs validation form error — invalid toolkits cannot be selected as default and (2) proper Status Report message — it might become invalid after selection.Didn't have a position now on form validation.
As a conclusion: I think that we should keep
isAvailable()as a hard flag, that tells if a toolkit is valid. Basically it's below the bottom line of requirements.Comment #33
fietserwinThis is a follow up issue to clean up the API regarding the creation of ImageToolkits. So, I would like to extend the scope of this cleaning a bit to the whole ImageToolkitManager class:
Comment #34
claudiu.cristea@fietserwin,
#33.1: Agree but as a followup to have a clear distinction between issues.
getActive()seems the more correct option.#33.2: I don't understand exactly what would be the reason for accessing other toolkit than the active one? Can you show us a use case? Do you suggest that modules will be able to use Drupal with more than one toolkit in the same time? Or? Will the
ImageToolkitManager::get($id)method be an alias ofImageToolkitManager::createInstance($id)? If yes, why not using that straight method?#33.3: Agree with
getAvailableToolkits()removal but I don't understand what you mean by "Drupal has a good requirements checking and reporting system, at install time, selection time and via the status report". So you want to replaceisAvailable()method with per-toolkit arbitrary checks? Why?#33.4: I'm not sure that I understood your point 2. "So ImageToolkits should not crash in their constructor...". Is crashing now in constructor? Or? Confused about this explanation. Can you illustrate this with a scenario or a piece of code?
What I feel is that you made strong scope from removing
isAvailable(). I'm not against but also not in favor. I don't see why this unified edge check should be replaced with per-toolkit arbitrary checks. Give me a reason.Also, let's synchronise our effort with the very slow progress of committing our work. Trying to radically change the image system (e.g. by letting multiple active toolkits) can ruin the whole plan.
Comment #35
mondrakeBasically just a reroll of #28, plus with the form validation logic moved to the new ImageToolkitBase. Only the currently selected toolkit is validated, however all the toolkits' submits handler are invoked.
This is just to set a new starting point; I did not review #32 and #33 yet.
Comment #36
mondrakeNW anyway to deal with #32, #33. Also unassigned myself as I am not sure about the time I can give to this in the short term.
Comment #37
fietserwin#34.re#33.1: OK, lets go with getActive
#34.re#33.2: didn't know that one existed in the base class. Drop this.
#34.re#33.3: I don't want any runtime checks at all, let the code just fail if the status report would not show green. However, the current code does contain some checks, e.g. whether functions exist, but (I guess) mainly on the warnings (rotate and desaturate). And because I don't see the need for runtime checks, both functions can be removed (we are not building a high availability embedded system, Drupal does not do much runtime error checking)
I don't want multiple active toolkits, but I want to be able to change the toolkit (at the start of the request) without messing around with the configuration..
Comment #38
mondrakeThis patch would introduce methods that overlap with those in #2096703: Image toolkits should use PluginFormInterface and ContainerFactoryPluginInterface. It'd be better have that one first. In any case the patch in #35 would need serious rework at this stage.
Comment #39
mgiffordComment #53
smustgrave commentedThank you for creating this issue to improve Drupal.
We are working to decide if this task is still relevant to a currently supported version of Drupal. There hasn't been any discussion here for over 8 years which suggests that this has either been implemented or is no longer relevant. Your thoughts on this will allow a decision to be made.
Since we need more information to move forward with this issue, the status is now Postponed (maintainer needs more info). If we don't receive additional information to help with the issue, it may be closed after three months.
Thanks!
Comment #54
smustgrave commentedbelieve to still be valid as isAvailable() is still part of this interface