Problem/Motivation

If you want to add functionality to an existing toolkit (e.g. extend image type support) you have to extend the toolkit and all it's operations (crop, scale, ...). For example, when trying to add WebP support to Drupal Core's GDToolkit (see #2340645: image effect to convert to webp).

Proposed resolution

Support plugin derivatives for image toolkits. A derivative may extend its base and should have the ID like id = "gd:whatever".

Remaining tasks

User interface changes

None.

API changes

Derivative IDs will have the base plugin ID as prefix: id = "gd:whatever:.

Original report by @attiks

If you want to extend an existing toolkit, you are now forced to extend all effects as well, this is - to say the least - cumbersome.

As an example see #2340645: image effect to convert to webp

Beta phase evaluation

Reference: https://www.drupal.org/core/beta-changes
Issue category Task, because this is just extending to image toolkits the plugin derivatives concept.
Issue priority Normal, because workaround exists.
Disruption Not disruptive.

Comments

jelle_s’s picture

claudiu.cristea’s picture

This is a big architectural issue of the toolkit system. Don't think that this is the right way

claudiu.cristea’s picture

Title: Improve DX of extending a toolkit » Allow image toolkit derivatives
Issue summary: View changes
Status: Active » Needs review
Related issues: +#2359391: The deriver class should receive the decorated plugin discovery
StatusFileSize
new38 KB
new14.88 KB

My approach is to allow declaration of derivative plugins for image toolkits. The effect is the same but it's more elegant. A derivative is declared id = "gd:whatever". If you want to override operations, just implement new one having the toolkit definition pointing to the derivative id.

This issue is blocked by #2359391: The deriver class should receive the decorated plugin discovery. Attached 2 patches: one for this issue and the other combined with #2359391: The deriver class should receive the decorated plugin discovery for testing.

claudiu.cristea’s picture

StatusFileSize
new853 bytes
new38.03 KB
new14.92 KB

Additional check added.

The last submitted patch, 3: 2357119-3-combined.patch, failed testing.

claudiu.cristea’s picture

Issue summary: View changes
Related issues: -#2359391: The deriver class should receive the decorated plugin discovery
StatusFileSize
new27.69 KB
new11.59 KB

It seems that we don't need a deriver class. Derivatives are discovered exactly as the base toolkits and the whole logic goes into ImageToolkitOperationManager::getToolkitOperationPluginId() where we need to check also the base plugin for operations. Now the patch is more compact and does not depend on #2359391: The deriver class should receive the decorated plugin discovery anymore. Somehow we are back to @attiks initial solution but instead of having the "base toolkit" key, we are using the derivative like ID prefixing.

The interdiff is against the combined patch.

claudiu.cristea’s picture

StatusFileSize
new11.34 KB
new585 bytes

Small fix: removed unused "use" statement.

The last submitted patch, 6: 2357119-6.patch, failed testing.

claudiu.cristea’s picture

Changed the existing change request to reflect this improvement: https://www.drupal.org/node/1993056/revisions/view/7175637/7745505

attiks’s picture

Quick code review, looks good to me.

+++ b/core/modules/system/src/Tests/Image/ToolkitTest.php
@@ -71,4 +72,27 @@ function testApplyNoParameters() {
+    /** @var \Drupal\Core\ImageToolkit\ImageToolkitManager $manager */
...
+    /** @var \Drupal\Core\ImageToolkit\ImageToolkitInterface $toolkit */
...
+    /** @var \Drupal\Core\ImageToolkit\ImageToolkitOperationManager $operation */
...
+    /** @var \Drupal\Core\ImageToolkit\ImageToolkitOperationInterface $blur */
...
+    /** @var \Drupal\Core\ImageToolkit\ImageToolkitOperationInterface $invert */

Comments have to be removed

claudiu.cristea’s picture

Those are not comments. Those are making your life easier in PhpStorm by giving you rich autocomplete features. And are also good for documenting the types of objects that we are using there.

attiks’s picture

I know, but they are not 'allowed' in core, sad but true.

claudiu.cristea’s picture

StatusFileSize
new11.05 KB
new1.78 KB

Ok, ok :)

claudiu.cristea queued 14: 2357119-14.patch for re-testing.

claudiu.cristea queued 14: 2357119-14.patch for re-testing.

claudiu.cristea’s picture

Assigned: Unassigned » attiks
StatusFileSize
new10.88 KB

Hey @attiks, this is here for a long time waiting a review. Is this still needed? I guess, yes.

PS: Rerolled.

mondrake’s picture

Status: Needs review » Needs work
+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationManager.php
@@ -77,6 +88,14 @@ function ($definition) use ($toolkit_id, $operation) {
+      if (($toolkit_id != $base_toolkit_id) && !empty($base_toolkit_id)) {
+        $base_toolkit = $this->toolkitManager->createInstance($base_toolkit_id);
+        return $this->getToolkitOperationPluginId($base_toolkit, $operation);
+      }

1. Should check if $base_toolkit_id is available before creating an instance? The base toolkit may have been uninstalled.

+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationManager.php
@@ -92,17 +111,17 @@ function ($definition) use ($toolkit_id, $operation) {
-  public function createInstance($plugin_id, array $configuration = array(), ImageToolkitInterface $toolkit = NULL) {
+  public function createInstance($plugin_id, array $configuration = array(), ImageToolkitInterface $toolkit = NULL, ImageToolkitManager $toolkit_manager = NULL) {
     $plugin_definition = $this->getDefinition($plugin_id);
     $plugin_class = DefaultFactory::getPluginClass($plugin_id, $plugin_definition);
-    return new $plugin_class($configuration, $plugin_id, $plugin_definition, $toolkit, $this->logger);
+    return new $plugin_class($configuration, $plugin_id, $plugin_definition, $toolkit, $this->logger, $this->toolkitManager);
   }

2. Why? We are not passing the ImageToolkitManager to ImageToolkitOperation objects.

+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/DerivedToolkit.php
index 0000000..780dbe5
--- /dev/null

--- /dev/null
+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/Bar.php

+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/Bar.php
+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/Bar.php
@@ -0,0 +1,21 @@

@@ -0,0 +1,21 @@
+<?php

3. To keep consistency with gd toolkit, I think this should go under file core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/test/Bar.php and class \Drupal\image_test\Plugin\ImageToolkit\Operation\test\Bar. Same for Foo. Not sure about FooDerived though.

Other than that, nice and useful if anyone wants to support additional image file formats without rewriting entire toolkits. Test coverage is good.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new10.9 KB
new2.63 KB
  • #18.1: If a bad $base_toolkit_id is passed, a \Drupal\Component\Plugin\Exception\PluginNotFoundException is thrown. An if() {} doesn't help. But I'm happy with the exception because no plugin derivative should exists without its base plugin.
  • #18.2, 3: Fixed, thank you.

Status: Needs review » Needs work

The last submitted patch, 19: 2357119-19.patch, failed testing.

mondrake’s picture

+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationManager.php
@@ -92,7 +111,7 @@ function ($definition) use ($toolkit_id, $operation) {
-  public function createInstance($plugin_id, array $configuration = array(), ImageToolkitInterface $toolkit = NULL) {
+  public function createInstance($plugin_id, array $configuration = array(), ImageToolkitInterface $toolkit = NULL, ImageToolkitManager $toolkit_manager = NULL) {
     $plugin_definition = $this->getDefinition($plugin_id);

1. This is also not necessary, then.

+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/test/Bar.php
@@ -0,0 +1,21 @@
+ * Contains \Drupal\image_test\Plugin\ImageToolkit\Operation\Bar

2. \Drupal\image_test\Plugin\ImageToolkit\Operation\test\Bar

+++ b/core/modules/system/tests/modules/image_test/src/Plugin/ImageToolkit/Operation/test/Bar.php
@@ -0,0 +1,21 @@
+namespace Drupal\image_test\Plugin\ImageToolkit\Operation;

3. Drupal\image_test\Plugin\ImageToolkit\Operation\test;

2. and 3. also in the other files moved

mondrake’s picture

Issue summary: View changes

Added beta evaluation.

mondrake’s picture

Issue summary: View changes

.

claudiu.cristea’s picture

Status: Needs work » Needs review
StatusFileSize
new10.31 KB
new4.09 KB

Thank you.

attiks’s picture

Assigned: attiks » Unassigned

Sorry for the late reply, a bit too busy, patch is looking good

mondrake’s picture

Status: Needs review » Reviewed & tested by the community

Thanks @claudiu.cristea.

@attiks is happy too, quoting myself from #18

nice and useful if anyone wants to support additional image file formats without rewriting entire toolkits. Test coverage is good.

RTBC

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 24: 2357119-24.patch, failed testing.

claudiu.cristea queued 24: 2357119-24.patch for re-testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

#27 seems a testbot failure.

xjm’s picture

Version: 8.0.x-dev » 8.1.x-dev
Status: Reviewed & tested by the community » Postponed

This issue does not meet the criteria for allowed changes during the beta phase (see https://www.drupal.org/core/beta-changes).

Since the patch is BC-compatible (API addition with an added service via constructor injection), it can be made once the 8.1.x branch is open for development, so postponing to that branch. Thanks!

attiks’s picture

#30 Not to start an argument, but not adding this, means contrib cannot override only one method, it has to override everything, postponing this will make it very hard for contrib.

claudiu.cristea’s picture

It was RTBC for 1 months :(

Status: Postponed » Needs work

The last submitted patch, 24: 2357119-24.patch, failed testing.

mondrake’s picture

Status: Needs work » Reviewed & tested by the community

#24 is still green.

alexpott’s picture

Status: Reviewed & tested by the community » Needs work
+++ b/core/lib/Drupal/Core/ImageToolkit/ImageToolkitOperationManager.php
@@ -67,7 +77,8 @@ public function __construct(\Traversable $namespaces, CacheBackendInterface $cac
-  protected function getToolkitOperationPluginId($toolkit_id, $operation) {
+  protected function getToolkitOperationPluginId(ImageToolkitInterface $toolkit, $operation) {
+    $toolkit_id = $toolkit->getPluginId();

I think we should be able to make the change without changing the protected method signature which is a BC break that should only be done in major releases unless the method is agreed to be internal. Protected methods are not like service construction which we've agreed can change (within reason) in minor releases.

alexpott’s picture

Status: Needs work » Reviewed & tested by the community

According to @xjm we protected methods are automatically considered internal see https://www.drupal.org/node/2562903. I've debated whether or not we should avoid the signature change but I think in this case it keeps the code more maintainable and sub classing the ImageToolkitOperationManager should be extremely rare.

alexpott’s picture

Status: Reviewed & tested by the community » Fixed

Committed 3ef563d and pushed to 8.1.x. Thanks!

  • alexpott committed e4dcadd on 8.1.x
    Issue #2357119 by claudiu.cristea, mondrake, attiks: Allow image toolkit...

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

xjm’s picture

Issue tags: +8.1.0 release notes