Problem/Motivation
ImageInterface has currently methods setWidth() and setHeight(). They might give a user the impression that calling that method will somehow change the width /height of the image. It doesn't.
These methods were necessary due to the historically grown separation between ImageInterface and ImageToolkitInterface. If a toolkit changes the image by performing an operation on it (like resize or rotate), the Image must be able to tell the new dimensiions to users of its interface. So the only objects that should use these methods are toolkit objects. To all others these methods are confusing (and should not be used at all).
Proposed resolution
- Issue #2103621: Move GD logic from ImageInterface to toolkit, tie toolkit instance to Image instance, toolkits should no longer be instantiated separately creates a 1-1 relation between instances of ImageInterface and ImageToolkitInterface. This gives the opportunity to move the width and height properties to the toolkit and remove the set methods.
- The get methods on ImageInterface should be forwarded to and return the value from their toolkit counterparts.
- The GdToolkit should implement the get methods by returning imagesx()/imagesy() on its (own) resource property.
- The current patch contains some cleanup and other polishing changes as well (Document that Image and ImageFactory constructors can accept NULL $source; injected the ImageFactory services in the setUp methods of toolkit tests; add getToolkitId() to image factory (besides already existing setToolkitId())
Remaining tasks
Agree on "scope creep", I (@fietserwin, OP) do agree on it.
User interface changes
None
API changes
Removal of those 2 methods, currently only used by GdToolkit (and some tests)
| Comment | File | Size | Author |
|---|---|---|---|
| #57 | 2196067-remove_hw-57.patch | 30.31 KB | mondrake |
| #57 | interdiff_55-57.txt | 1.16 KB | mondrake |
| #55 | 2196067-remove_hw-55.patch | 29.4 KB | mondrake |
| #55 | interdiff_51-55.txt | 11.75 KB | mondrake |
| #51 | interdiff_44-51.txt | 2.83 KB | mondrake |
Comments
Comment #1
mondrakeDefinitely makes sense to me. We will have to be careful though and see there are no impacts (or alternative solutions) to effects that in D7 are cloning the stdClass Image object to start fresh a new image for overlaying on another, or for checking e.g. transformDimensions. Definitely cloning is a no go anymore in D8 (tested on my skin).
Comment #2
mondrakeI'll take at stab at this now that parent landed.
Comment #3
mondrakeThis will fail, but let's see where. It's removing height/width properties and setters from ImageInterface and moving to toolkit, but also addressing #1 and the OP in #2063373: Cannot save image created from scratch, by introducing a new method to the image factory that allows to create Image objects without reading the image from a file. More explanations when this will turn green.
Comment #5
mondrakeBot?
Comment #6
mondrakeComment #8
mondrakeComment #9
fietserwinI tried to review the patch. IMO this is far more than this issue is about. So I could highlight all things that do not belong to the original intent of this issue, but I will await your explanation. My idea is to not do the other features it in this patch, but in a follow-up (the already created and now related issue). This because the features seem so unrelated. Moreover, the whole image system is about processing existing images. The only use through Drupal (and that includes all of D7 contrib as known by me) is: take an existing image, process it (resize on file upload or create derivatives using image styles). So we are talking about a feature request! This issue is about misleading and incorrect interface methods. And now we are mixing in a feature request to this issue to be able to start an image from scratch (blank canvas). I'm not opposed to that, but can we defer that to #2063373: Cannot save image created from scratch?
If all these changes are needed to keep some tests running, I would think that these tests are wrong and should be removed or adapted. If so, what tests are the failing ones?
If #1 needs to be addressed in this issue (seems a bit unrelated as well), we need to define what this means: do you really need to create an image object or do you need to be able create another GD resource or do you need to be able to stack commands to imagemagick? In the latter case, the Image object is not the place to do so, we will have toolkit operations for that.
[EDIT: cross-post: I am referring to the patch in #5, I am awaiting your explanation]
Comment #10
mondrake#9 - Yes, this is going too far, agree. Let's step back to OP. Will post a reworked patch.
In Textimage 7, I am creating a blank canvas image in memory that I use to overlay text on via imagettftext. Then that canvass image is overlayed itself onto a background image via the imagecache_actions overlay effect. In D8 I am not able to start a blank canvass image anymore, I will have to always load it from disk as we stand. So it's not really a Feature Request IMO, but rather a regression.
Comment #11
mondrakeSo this patch:
isSupported($uri, $toolkit_id)andsupportedTypes($toolkit_id)methods to find a home for the image type checks to be discussed in #2211227: Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType)if ($this->container->has('image.toolkit.manager'))in ValidatorTest.php was wrongly converted in previous patch when->has('image.toolkit')had a sense)Comment #13
mondrakeReverted too much...
Comment #14
fietserwinSo Image is able to handle not passing in a source. Can you adapt the comment to something like:
* @param string $source
* The path to an image file or the empty string to start with no image source.
Or make it allow NULL as well
Not sure that this is needed here now, though it would mean we could remove all the calls to it in the getters (if we also add it to setSource() and save(), though that one has this call already).
A bit inconsistent with the change on the Image constructor, where we now do accept an empty source param.
I like this idea, though it will hardly have a normal use. But it does allow for easier and more dynamic toolkit selection. We can get the same with setToolkit and a getToolkit to be able to set the toolkit to its original value. Do we need a getToolkit anyway, for reasons of completeness? (may be a follow up).
I think that the Drupal standard is to specify array|FALSE (not official phpdoc syntax, but it is clearer).
Correct, but may be postponed to one of the follow-ups.
The type hinting will assure that you have an array, and NULL is not an array. So the if condition and the else branch can be removed.
The specification says "..., or 0 if the image is invalid". Tests should test the specification (and as such clarify the specification), so lets test for 0.
In general the patch is OK, though there are a few points above that need to be solved (1, 5, 7, 8). Other points are more a matter of do it here or in the designated follow ups that already exist: #2211227: Refactor image and imagetoolkit: isExisting, isSupported, supportedTypes, getMimeType and #2063373: Cannot save image created from scratch.
Comment #15
fietserwinComment #16
mondrake#14 - thanks for the review.
1. Done. Later in #2063373: Cannot save image created from scratch we should exclude NULL from the get() method of ImageFactory, i.e. it should check that a $source file exists before instantiating an Image object; $source = NULL should be left for the to be 'createNew' or 'createCanvas' method.
2. These were removed in the patch in #8, then reverted to manage scope. I suggest to go back to this in #2063373: Cannot save image created from scratch where we will have to manage the status of the $processed property in relation to starting an image from scratch.
3. Adapted to (1)
4. Good point, added a getToolkitId() method, and a test for it.
5. Done
6. It should have been like this in the parent patch...
7. Reworked
8. Reworked to test isExisting()
Comment #17
fietserwin@param string|NULL $source (change accordingly for constructor in Image as well)
Formerly, you did add them in all cases, now only if the array passed in is not empty?
With the 2 changes from 1 this is good to go for me. Updated issue summary to reflect current status and contents of patch.
Comment #18
mondrakere #17
1. Done
2. We always need save() and getPluginId() to be stubbed, regardless of other methods to be stubbed. So:
input: none or empty array() => output: $stubs = array('getPluginId', 'save');
input: array('rotate') => output: $stubs = array('rotate', 'getPluginId', 'save');
and yes, this is a change vs. current HEAD. In current HEAD, all methods of the toolkit get test doubles, if $stubs is empty. This means the toolkit is practically non-operational. With the change here, we let the toolkit do toolkit-ish job, and only get test doubles for getPluginId() (because that one would be returning null in PHPUnit, since we disable the toolkit's original constructor) and for save() as a precaution to avoid that test changes are actually written back to the test image (plus it would fail when invoking Image::chmod()). We need that change because otherwise the test for width and height getters would not be possible (before, these getters would return the value of the properties on the Image class, but now we removed those properties, and we need to have the toolkit respond to the request 'measuring' the actual test image).
Comment #19
mondrakeRe. issue summary update - Image and ImageFactory constructors would very well accept NULL as $source even currently, just it was not documented. What we do here is documenting that, and forcing 'processInfo' call in the Image constructor if a $source is specified (besides that it makes sense, this is also needed for PHPUnit tests). Updated in issue summary.
Comment #21
mondrake18: 2196067-remove_hw-18.patch queued for re-testing.
Comment #23
mondrake#20 failed because of #2216701: Random test failure in ToolbarAdminMenuTest, #22 no clue :(
Comment #24
mondrake18: 2196067-remove_hw-18.patch queued for re-testing.
Comment #26
mondrakeLooks like we need to wait for
#2216701: Random test failure in ToolbarAdminMenuTest and
#2216909: Random test failure in ConfigTranslationUiTest
Not sure about the tag, please just remove if not relevant.
EDIT - #2216909: Random test failure in ConfigTranslationUiTest landed, retrying-
Comment #27
mondrake18: 2196067-remove_hw-18.patch queued for re-testing.
Comment #28
mondrakeOK, back to green.
Comment #29
fietserwinSorry, I did not read the patch correctly. So my remark was a bit off and thus too vague for you:
Because we know that stubs is an array, the single line
$stubs += array('getPluginId', 'save');
will work both when stubs is an empty array and when it is not an empty array. So replace the whole if-else with this single line (from the if branch).
Comment #30
mondrake#29 - Indeed :) Thanks
Comment #31
fietserwinThanks.
Comment #32
mondrakeChange record to be updated after commit: https://drupal.org/node/2084547
Comment #33
mondrakeRelated, #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead.
Comment #34
catchWhy not empty string instead of string|NULL?
$this->toolkitId is never set if the $toolkit_parameter is set? If that's by design I think it could use a comment.
Comment #35
mondrakeThanks @catch.
#34.1 - made change, I hope I understood the direction :/
#34.2 - moved the initial setting of $this->toolkitId to the constructor, which makes resolveToolkitId() no longer necessary. Also added to the docblock of ::get() some comments on the ways to instantiate Image objects with alternative toolkits.
Comment #37
fietserwinpublic function get($source, $toolkit_id = '') {
#34.2: the way that #35 solves this is better, as getToolkitId() does not initialize the value,it just returns it. So it should indeed be set in the constructor.
I am looking into the test failure, but as that also happens in #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead I don't think it is related to this change.
Comment #38
mondrake#37 - thanks, there are testbot failures at the moment I understand, need to wait for them to be solved
Comment #39
fietserwinLocally the test passes. Though I did get an "PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 1130497 bytes) in ...\core\includes\common.inc on line 3917" While browsing to the test page. I think that rendering can be done more memory efficient, but that's something else.
@mondrake: can you make the little change from #37 and set to NR again?
Comment #40
mondrakeDone.
Comment #41
fietserwinOn a last review I found this tricky bug (already introduced in the parent issue):
All image objects that get created will now get a toolkit assigned based on the toolkitId property of the ImageFactory. This means that code outside the image system may no longer call getDefaultToolkitId() on the toolkit manager, because that may not be the image toolkit in use! So
If we don't do so, advanced scenario's in contrib, where e.g. toolkits are switched based on image field instance or whatever use case one can come up with, might fail.
This error was already introduced in the parent issue, but this issue allows us to solve it, as it adds a getToolkitId() method on Image Factory. Sometimes plans do come together...
Comment #42
mondrakeRe #41
1 - ValidatorTest - that if() is checking if a default toolkit is available before an image is loaded a few lines below. So we cannot get the toolkit from the image yet: there is no image... A few more lines of code below, there's an else with a TODO for the case no toolkit is available. We need to check existence of a default toolkit directly from the toolkit manager then - or refactor entirely the test which is not in scope here. Note - before the parent issue was committed, that line was
if ($this->container->has('image.toolkit')) {which had the same concept as the line as gets modified by this patch - that 'if' would have failed when no toolkit was available.2 - file.module - good catch. Corrected here.
3 - image.install - here we need an instance of the default image toolkit to be able to invoke its getRequirements() method to build the Status Report. We do not need to load an image arbitrarily for that, so it is correct we get this toollkit instance from the toolkit manager directly.
Comment #43
fietserwinIMO the validator test can and should use:
As that one returns exactly the same value in a set-up where the default toolkit is used (and thus also a value that evaluates to false when no toolkit is available). IMO this is better as toolkit becomes more and more a thing on the background. Image and ImageFactory are the classes that can be used by the rest of the system. Imagetoolkit and ImageToolkitManager should be avoided whenever possible. This would make the test reusable for other toolkits, although they are not the subject of the test.
Similarly image.install can use:
But I agree that is too far off for this issue.
Comment #44
mondrakeOK
Comment #45
fietserwinGreat, thanks.
Comment #46
claudiu.cristeaNo. Empty string it's still a string, it's still "something". We should change the constructor interface and make
$sourceoptional defaulting toNULL.Same here.
$sourceshould be optional, defaulting toNULL. This is the common pattern in PHP to say "it has no $source".Same here. Don't use empty string to denote "nothing". Use
NULLinstead.We should return
NULLfor no width|height. That would be conceptually correct.Use
falsewith lowercase. See https://drupal.org/coding-standards/docs#types.Again, let's fix this bug. I know it was inherited.
NULL, not 0.Am I missing something? We removed width & height from image but we added back only to
TestToolkit? And why here? Why we don't add those properties directly toImageToolkitBaseso that all implementations will inherit them?And again, let's initialize them to
NULL.Comment #47
fietserwinAll the empty string versus NULL is from @catch's remark in #34. To say that this is the common pattern for PHP is too strong. PHP is notoriously bad in handling edge cases like the absence of a value (too many ways, empty versus !isset etc.). if I can see any tendency, it is towards working more type strictly, thus specify 1 type only and have an empty value within that type. int and string do have such a value and as 0 is indeed not a valid value for a width or height it is usable.
RE #46.8: We removed width and height from Image, not toolkit. GD doesn't need to store the width and height in properties as it has imagesx() and imagesy(). OTOH, IM will need to keep track of width and height as it has no resource like GD that keeps track of this info.. So, these properties do not belong to a base object as they are really implementation specific.
For me, this issue remains RTBC, except perhaps 5, but that can be done on committing.
Comment #48
claudiu.cristeaWell, this time I have to disagree with @catch.
NULLmeans (literally) "nothing" and this is the case here. PHP is just flexible but nothing stops us to use it right. See comment #5: https://drupal.org/comment/7566547#comment-7566547.Comment #49
mondrakeMoving to RTBC only to see if we can have @catch feedback here before proceeding in any sense. My personal preference is to proceed with NULL on all points of #46 1 to 7, but before doing so I'd like signoff. #46.8 has been clarified I believe.
EDIT
The current implementation of the ::save() method in ImageInterface is using NULL:
Comment #51
mondrakeNeeded a reroll after #2217783: Remove $extension and $mimeType from Image, use image_type_to_xxx function in the getters instead got committed. Just merge conflicts resolved, no change vs #44.
Comment #52
mondrakeComment #53
fietserwinSetting to RTBC as per #49 and a review of the reroll. The article #48 refers to is not very explicit on the use of NULL, it also tells us to return 1 type only, or to throw an exception. So IMO an "empty" value within a type also does.
Comment #54
catchNo for return values, if you're returning an array, you should always return an array. Otherwise you constantly have to type check for array comparisons/foreach etc. I don't mind much about int/string vs. null, but especially arrays it is horrible to return FALSE.
Previous example where we changed from FALSE to array: #1927884: Always return an array from field_get_items().
Comment #55
mondrakeOK, this covers #46 - 1, 2, 3, 4, 6, 7.
#46.5 and #54 - I finally went for an empty array instead of FALSE when getInfo cannot process the source file. This was inherited here.
Comment #57
mondrakeFix for PHPUnit test.
Comment #58
fietserwinI have to (partially) disagree with @catch. The article from Larry Garfield teaches us: throw an exception or return null when the array is actually a struct and not a collection.
So returning false indeed is wrong, as is (IMO) returning an empty struct. But this can be handled when we really solve working with blank canvases #2063373: Cannot save image created from scratch. Then we'll see if we better throw an error.
Comment #59
catchYes in that case an exception would be better but OK with working in the followup. Let's unblock the other patches. Committed/pushed to 8.x, thanks!
Comment #61
mondrakeChange record updated, thanks @fietserwin:
https://drupal.org/node/2084547/revisions/view/7110713/7144009