
Problem/Motivation
The ImageStyle class is a config entity that besides doing the config entity, also has methods that do process images into derivatives.
This is probably the result of the migration of the image system from D7.
As things stand, though, this is causing issues -
- the entity code is for instance invoking hooks
- it's difficult for custom/contrib to override parts of the image derivatives generation process, for instance to change the derivative URL/URI, or to pre/postprocess image derivatives
Several issues were filed in that respect, to list some:
#2359443: Allow creating image derivatives from an Image object
#1358896: Flexible scheme and URI for image derivatives
#2940016: Make possible to respond to image derivative creation
#1826362: ImageEffects of the same image style should be able to pass variables between them
#2685905: Refactor ImageStyleDownloadController so derivatives can be generated by contrib modules
#1903190: Allow image style derivatives of private images to be stored on the public file system
#2098247: Provide a hook for ImageStyle::createDerivative
#3218514: [PP-1] Deprecate passing path (instead of true URI) to ImageStyleInterface::buildUri($uri)
Proposed resolution
In #2359443-26: Allow creating image derivatives from an Image object, before stalling, we started discussing decoupling the config entity from the image derivative processing code.
This is implementing that:
- separate the image derivative processing methods from the ImageStyle into a plugin, that takes ImageStyle as the config entity as in input, like the source URI or the Image object directly
- deprecate the methods on the ImageStyle, leaving the code that calls the plugin methods instead
- the plugin implements a fluent API to pass the variables needed and then executes the process required, bieng determining the derivative URI, or creating the derivative image, or getting the safety token
- keeping full BC
Why a plugin?
So that the default implementation can be overridden e.g. to change the derivative URI calculation logic or to post-process an image derivative - only re-implementing the plugin methods that are necessary without touching the config entity code. But also, plugin allows to have parallel 'derivative processing engines' if someone wants that. For instance, Textimage contrib module has its own formatter and API that could leverage this plugin concept.
Remaining tasks
Review
User interface changes
None
API changes
Several methods deprecatedin ImageStyle, new plugin manager and a default plugin implemented.
Data model changes
None
Comment | File | Size | Author |
---|---|---|---|
#96 | 2986669-nr-bot.txt | 90 bytes | needs-review-queue-bot |
Issue fork drupal-2986669
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
- 2986669-II
changes, plain diff MR !2691
- 2986669-split-imagestyle-into
changes, plain diff MR !105
Comments
Comment #2
mondrakeKick-off patch.
Comment #3
mondrakeComment #6
mondrakeComment #7
mondrakeReroll
Comment #9
mondrakeFixing failures, changing calls of removed deprecated functions.
Comment #10
mondrakeComment #11
mondrakeThinking a bit about this, I start figuring out that what would be good to have here are 'pipelines', that norm the image derivation tasks, and the pipeline tasks as 'events'. The Image Optimize (or ImageAPI Optimize) module does something like that already. But IMO we need to extend that also to other tasks, like preparing the source image for derivation, post-processing, etc etc. Other modules would benefit (for instance the Imagemagick module needs to copy images stored in remote storage to local disk for processing, and then move the derivative from local file to remote storage - and that is done on the toolkit level right now). Starting something here.
Comment #12
mondrakeComment #13
mondrakeIntermediate step - this introduces the pipeline. Variables are set to the pipeline, and when you ask for an output or a task, the relevant subtasks required get recursively executed automatically. Next iteration will be moving the tasks to an event dispatch/subscribe mechanism.
Comment #14
berdirDrive-by review
I'm not sure if I like the API, having a service with a state like that is a IMHO a bad design.
maybe it should be like a factory that creates an object for each image style + source image?
Another thing, in light of #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it, we might want to make this API return URL objects, this currently not doing that is one reason why we had to go back to having 3 different methods on the new service there.
Comment #15
mondrake@Berdir thanks for the review and for the interest in this effort :)
This patch is introducing events and and an event subscriber to manage the derivatives generation process. The pipeline states have been removed.
Re. #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it I made a first step here to return an URL object from the pipeline in case of dirty URLs; in case of clean URLs (which is I believe 99.99% of the cases), it's still returning a string - I am not sure how to move on here since the API is calling file_create_url which is itself returning a string. Is this a catch 22?
Interdiff is likely useless.
Comment #16
mondrakeNow, the API always returns an URL object for the derivative image URL, no longer strings. To get there I introduced a new method to Url,
Url::fromExternalUrl
, which takes as input the output offile_create_url
which is a string. If in the refactoring in #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it we could get Url objects instead, that would probably not be needed.Comment #17
bradjones1Comment #18
mondrake@bradjones1 yes this is for no sooner than 9.1.x, but let's keep the version in the issue to 9.0.x ATM so patches can be tested by the bot, 9.1.x branch is not updated.
Comment #19
andypostSounds like media already made kind of it to redo thumbnails processing, and features are for 9.1
Comment #20
mondrakeLooks like we do not need an additional method in Url,
Url::fromUri
can do, albeit with a little bit more overhead than a dedicated method. In any case, as mentioned in #16, if in the refactoring in #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it we could get Url objects directly from the service methods, the overhead may be skipped.Comment #21
mondrakeComment #22
mondrakeHere, with all deprecated calls removed and without deprecation silencers.
For three test classes:
I marked existing tests as legacy and rewrote them refreshing the logic to PHPUnit - that's why the size of the patch increases a bit.
Comment #23
mondrakeComment #24
mondrakeComment #25
mondrakeRerolled.
Comment #26
mondrakeRerolled.
Comment #28
mondrakeFixing test failures.
Comment #29
mondrakeReroll.
Comment #30
mondrakereroll
Comment #32
mondrakedeprecation fixes
Comment #34
mondrakeFixes for #3153803: [Symfony 5] Update EventDispatcher::dispatch() to make it forward-compatible with Symfony 5.
Comment #35
mondrakeComment #36
mondrakeComment #37
mondrakeComment #39
mondrakeAddressing failures.
Comment #41
mondrakeComment #43
mondrakeComment #44
mondrakeComment #47
mondrakeChanged issue to Merge Request workflow.
Comment #48
mondrakeRerolled
Comment #49
bradjones1Per latest MR test failures; looks not necessarily related to the feature but the test setup.
Comment #50
mondrakeComment #52
mondrakeComment #53
mondrakeAdjustments after #2669074: Convert file_create_url() & file_url_transform_relative() to service, deprecate it.
Comment #54
mondrakeRerolled
Comment #56
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedI updated from 9.4.x and made a few very minor fixes while reviewing.
setVariable
and related methods is certainly flexible, I don't like how it's similar to arrays in that a single typo in a key can be hard to debug. My first thought is that those should be pulled out into an interface, and then instead of->hasVariable() ? ...
it changes to$pipeline instanceof DerivativeImageUrlInterface
.\Drupal::service(..)
calls in the ImageStyle entity should be pulled out into a method to assist type checking and method autocomplete in IDEs.ImageDerivativeSubscriber
class seems rather large, with many constructor arguments and imports. I noticed that flushing is the only place using the module handler, so perhaps that and other methods could be split out into a few different subscribers.\Drupal\image\ImageStyleInterface::createDerivative()
for example, it's not clear what to do instead of that method. Further docs could help here.I haven't reviewed any test code. The only failure is due to calling a newly marked deprecated method.
Regardless of the above, I would like to take a pass at implementing what's here to support alternative image processing methods. I have time reserved December 3rd, so if any of the above are clear next steps I can work on them then.
Comment #57
bradjones1This is next-level organization. I am in awe.
Comment #58
mondrakeGreat to see more hands and eyes on this patch @deviantintegral @bradjones1!
#56 sounds like an excellent plan. Honestly I believe you know more of this patch than what I remember of it ATM...
WRT interfaces I recall thinking that we would need some, but then just deciding to post the patch as was to see what feedback would bring in terms of changes before starting that piece.
In general I see the combination of plugin + events may seem overwhelming but my rationale was to get to enable overriding at the highest granularity possible - so that folks can override/provide alternatives both for the derivative generation process overall but also for single steps of it (for instance, defining the target URL, or calculate a security token).
Comment #60
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedI only have time because Lullabot gives us a few days to dedicate each year, and I've not used them yet! Thanks for the kind words.
I've got several commits locally that refactors to use interfaces in many places, but I realized there's a gap in the current architecture.
Agreed! However, as is it's not actually possible for a contrib module to easily replace what core's image styles do, but only alter them. For example, in the current patch's buildUri method in ImageStyle and similarly in template_preprocess_image_style():
Since the derivative plugin is completely tied to core's image style implementation, it has functionality to set clean URLs, deal with derivative tokens, etc. An external image service may not have any of these concepts at all.
As well, the "check for null, dispatch an event, set the value" loop seems like an easy way to accidentally introduce recursion (which I've done locally a few times now).
My thinking is that we reorganize the plugin and events so that:
Comment #61
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedAll unit and kernel tests, and at least some functional tests are passing. Let's see how far it gets.
Regardless, I've completed significant work on the branch, though I'm sure it still needs work. I've hacked in some dependencies via \Drupal, and docs are missing or need to be updated. As well, Derivative needs fresh eyes to break it down, and the events that are fired need review and likely tests.
The
Drupal\image\Pipeline
namespace contains different interfaces that represent various known features of image style rendering systems. For example, a system may only have the concept of derivative URLs (keeping source images opaque to clients), or have security tokens like Amazon S3's signed URLs or Drupal's itok parameter. The goal is that each plugin would implement some subset of these interfaces, with the current Derivative plugin implementing all of them (since it's very fully featured!).As well, I completed the switch from using events as an API, to using them more directly as an extension mechanism.
Finally, in solving the issue of determining what pipeline to use, I decided that making it a configurable option for image styles made the most sense. It currently exposes the available plugins when configuring the image styles, but I think we'd hide it if there's only one. This would allow existing sites to use one or more image providers, which could be especially helpful during migrations between them. Which reminds me, that totally needs a config dependency on any modules that expose pipeline plugins.
And the current hierarchy of the interfaces:
I have two more days available to work on this, currently set for December 9 and 10th.
Comment #62
mondrakeI have not seen the patch in details, but rationale sounds solid to me. Good progress!
This is something I'd like to question. To me, it should be the other way round - the pipeline run should have configuration where the image style is one of the variables. IMO an image style should only be a collection of image manipulation operations, nothing more.
Let's dream a bit and think a possibile scenario:
A site collecting landscape photography.
The reqs are to
What I'd fancy about is this would be defined respectively
Now, I won't say that Drupal core should support all this (it won't), nor that we should do all in this issue (it will never go through), but here we could agree on the framework and a base implementation of 'derivative' that could be compatible with these cases.
Comment #63
mondrakeNW to fix the CS issues
Comment #64
andypostWhen such kind of configuration for plugins discussed (#62) wanna point very old issues, I bet styles plugins could get benefit from that
- #2011038: Use Context system for actions
- #2347023: Move processing plugin context to data processors
Comment #65
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedSummary: Unless I hear otherwise, I will likely work tomorrow on refactoring pipelines to be bound to an image toolkit, instead of an image style.
For context, my own goal is for core to support what isn't viable today: Using Drupal's image style system to render images with a remote API like Cloudinary or Fastly Image Optimizer. If that is possible, for many sites it reduces or eliminates the need for fast shared disk storage, simplifying cloud and containerized deployments. While there is a cloudinary D8 module, in practice it doesn't work and couldn't be made to work without core hacks. We ran into similar challenges years ago with S3 support for images.
That would mean a separate config form for pipeline instances? It feels like an additional level of abstraction that's not got a clear benefit to me.
To go through all of the Image concepts we have as of the current patch:
In core as it exists today, I think the largest limitation is that image toolkits have no way to communicate to image styles what effect operations they support via the
apply
method. We have FALSE returns, but those can also happen because of a runtime error. Conceptually, image toolkits also expect to be literally "saving" a file, when most remote APIs just want a URL prepared.When I look at what has the most coupling, it supports your suggestion that styles are the wrong place to integrate with pipelines.
Instead, what if it was at the toolkit level? Pipelines become invisible to the administrator, and are a way for common toolkits like GD and ImageMagick to share code. I'm imagining a "Local file derivative" (shipped with core) and one or more "remote URL derivatives" (written in contrib).
From an admin perspective:
To think through your use cases:
This essentially implies "derivatives of derivatives". Or, perhaps a simpler method would be "apply this image style to the field when uploading". Is there any limitation in Drupal's core APIs to implement this in contrib today? I think it could be done by decorating or swapping out the ImageItem plugin, updating the field settings form and calling the image style in
\Drupal\image\Plugin\Field\FieldType\ImageItem::preSave()
. If this is possible today, then I think this issue should be limited to ensuring we don't break this extension point for contrib.I'm not clear how this is different from what can be done today. If an image field is created, we can set the image style in the Preview Image Style option.
I don't think this can be done today, in that the scheme kept the same for image styles. If an image is in the private stream wrapper, the derivatives stay there too. Perhaps this would be a new use case for a type of derivative plugin in contrib that extends the proposed "local derivative" plugin that ships in core, but exposes config to change the destination scheme. The multiple formats aspect seems to be handled given the contrib webp module, even if it's not an optimal implementation.
So, could this just be an image style? The uploading aspect would be handled by the selected stream wrapper.
This exposes the coupling to the image toolkit, in that it's toolkits that determine what formats are supported via
\Drupal\Core\ImageToolkit\ImageToolkitInterface::getSupportedExtensions
. If image formats need to be extracted from image styles, perhaps that's in the future? I think keeping formats as a part of image styles will help constrain scope here.This implies a push-based generation of derivatives instead of the pull-based workflow we have today. I think it's important to consider, as there are likely systems out there that benefit from pregenerating derivatives (or actually require it).
Comment #66
bradjones1I'm not sure if I'm chiming in at a very late date or this is orthogonal to the changes described here, but I have done some work in contrib-land to support what I've called external image styles.
https://www.drupal.org/project/external_image_styles
Also perhaps related, some prior art on supporting splitting the origin image and derivative image storage:
https://www.drupal.org/project/cloud_native_image_styles
I'll have to take a read of the full thread to get a flavor for how this might affect this pattern, but A) you're not the only one interested in this pattern, even if it isn't typical and B) I hope some of this helps at least validate the pattern or perhaps inform an approach that suits different use cases.
Comment #67
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedThanks for sharing these! I see External Image Styles:
hook_entity_type_alter
.\Drupal\external_image_styles\Entity\ImageStyle::buildUri
and\Drupal\external_image_styles\Entity\ImageStyle::createDerivative
to throw a runtime exception if used.I was curious how likely those methods are to be called in real sites. I see:
That leads me to believe that external image styles cannot be used without careful consideration, and for smaller or junior teams they could run into situations where images "break" without a clear reason why.
Cloud Native Image Styles is great! I see how it allows the default filesystem to be flysystem or S3, and derivatives to be stored in container instance storage (instead of shared storage). There'd be a higher temporary disk use since each container or web server would generate it's own cache of images, but I bet that's still way better then the slower performance and cost of something like EFS. This seems like a good solution for sites with containerized deployments, but still wanting to use core's image styles.
So... next steps? I think External Image Styles validates the approach of "the rendering pipeline is tied to the image style", given two of us came up with similar solutions independently. I'm going to work on cleaning up the current MR instead of the refactor I suggested above.
And, as an additional goal for this issue: When merged, a module like External Image Styles should no longer need to override and throw runtime exceptions for methods like buildUri().
Comment #68
mondrakeStrictly coupling the Image Style with its process pipeline means e.g. in Textimage (that currently uses a core Image Style through an alternative image derivation process), one would have to duplicate the image style used with the core 'derivative' pipeline with the same style used for the 'textimage' pipeline. Unless in Textimage we disregard the pipeline indicated in the image style, but that would sound odd.
Comment #69
deviantintegral CreditAttribution: deviantintegral at Lullabot commentedSince my last push and post, I set up https://github.com/cshum/imagor as an easy to run image service to test an implementation against. I got it working! Meaning, I was able to write a contrib module that used that service for image generation, and show the results in the image style edit form.
However, I found that nearly all of the logic was in the image toolkit implementation, and it basically ignored all the work on image pipelines. Making it work without a custom image toolkit wasn't possible. It makes me think what we may want is an even simpler version of this patch that _just_ makes it so buildUri and friends can work with alternative storage systems, and pipelines become their own later addition. I need to think on that.
I was getting ready to push up the work I did changing ImageStyles to select the toolkit instead of the pipeline... but my laptop disk died. If I have time, I may see about pulling out the commits from backups. If someone was planning on working on this over the next two weeks, let me know and I'll prioritize that.
Comment #70
dieterholvoet CreditAttribution: dieterholvoet as a volunteer commentedThis would be really useful for the Imgix module, we have had to override the
image_style
entity class in order to make it work.Comment #72
andypostComment #74
mondrakeI've rerolled 70ec65f8 and targeted to 10.1.x. in MR 2691.
I started from my latest version before #54, that's what I know and I stand by. I'm leaving MR 105 untouched for further work on @deviantintegral's approach.
Comment #75
mondrakeComment #76
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It either no longer applies to Drupal core, or fails the Drupal core commit checks. Therefore, this issue status is now "Needs work".
Apart from a re-roll or rebase, this issue may need more work to address feedback in the issue or MR comments. To progress an issue, incorporate this feedback as part of the process of updating the issue. This helps other contributors to know what is outstanding.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #77
mondrakeRebased.
Comment #78
bradjones1cspell error
/var/www/html/core/modules/user/tests/src/Functional/UserPictureTest.php:123:34 - Unknown word (getfile)
Comment #80
mondrakerebased onto 11.x
Comment #81
smustgrave CreditAttribution: smustgrave at Mobomo commentedSeems like something that could use a change record.
For any contrib modules that add an image style will they have to do anything or will this work automatically.
Comment #82
mondrake@smustgrave re #81:
For sure. However, 5 years after this was started, there seems to be little or no interest to move this forward - at least I cannot see reviews in this sense. So, either we simply just won't fix this, or a review in the sense of agreeing on the architecture (or a propositive disagreement) is needed. Writing a CR now would be simply too early/useless. Please leave to NR while we get a review - or close this.
Not sure I understand. You can add image effect plugins. Or configuration of image styles. Both are not affected by this.
Comment #83
andypostDid a first pass of review and wondered do we really need so many events fired while generation happening.
Very probably it affects performance as each event is dispatched (as I got).
Maybe instead it could use less events as some events can carry state of process
Comment #84
smustgrave CreditAttribution: smustgrave at Mobomo commentedComment #85
smustgrave CreditAttribution: smustgrave at Mobomo commentedFor #83
Comment #86
mondrakeI give up, sorry.
I could think of doing something in contrib, but core should at a minimum split the entity from the image processing code - in a way that it could be overridable. AFAICU it's not possible at the moment.
Comment #87
smustgrave CreditAttribution: smustgrave at Mobomo commentedWasn't trying to shoot down the idea. Not something I can speak heavily on though.
Comment #88
mondrakeNo hard feelings, @smustgrave. Just I think after 5 years being open, there's no community interest at carrying this on. So I won't spend time on rerolls.
Comment #89
cmlaraI really don't think this should be closed.
I have been a bit booked by other tasks so I haven't been able to give it a test run, which I really wanted to do before commenting to hopefully better comment, but given there is a move to close it I feel a need to comment without having done the testing yet.
@andypost in #83 you assert there is a 'probable' performance impact but did not provide a profile report with your post? Can you please attach the generated performance report file for the rest of us to review?
While I've not traced every single event yet in this MR, the majority of the ones I have are only needed when an image derivative doesn't exist, the majority of times images are served from the file on disk. The most common to trigger event I would expect to be the buildUrl() events which need to be fired to generate links to ImageStyles Derivatives, though the flexibility of an event is necessary to allow all the changes that have been requested over the years.
Could some events be combined? Without looking at every line, Maybe? But would it make sense? Most of these events are in response to a specific method being called, if no-one ever calls that method, why invoke the expense of gathering data that no-one will use?
IMHO it would be a shame for this work to be lost especially since #83 didn't provide any objective data to justify moving to needs-work.
Comment #91
larowlanClosed the outdated MR
Looking at the patch next
Comment #92
larowlanLeft some comments on the MR
Keeping at NR and retaining the 'Needs architectural review' tag
Comment #93
larowlanAdded #3375453: [policy, no patch] Decide on policy regarding use of Events class with string constants for event names or class names instead for discussion
Comment #94
mondrakeThanks @larowlan for the review - I'll address your points but it may take a while.
Comment #95
mondrakeReplied to @larowlan's comments, inline.
Comment #96
needs-review-queue-bot CreditAttribution: needs-review-queue-bot as a volunteer commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #97
vlad.dancer@mondrake, deviantintegral, @bradjones1 thanks a lot for your work!
I'm working on a way of bringing UI to specify image styles beeing used for remote images for IntelligenceBank DAM and I think this issue is a good base for it. Typically DAMs like IB or PicturePark provides a way to generate image derivatives on their side, but there is a lack for selecting right image style in content context (like ckeditor, specific node, block). In Drupal we have many ways to map content item with right display settings, but it fails if image is remote and we don't need generate derivative ourselfs.
I will check this issue more deeply!
Comment #98
steinmb CreditAttribution: steinmb at University Of Bergen commented