Problem/Motivation
Since the Content templates 1.3.0 conversion to OOP hooks, the Quick Node Clone form submit callback is registered as an object callback using [$this, 'quickNodeCloneSubmit'].
This issue was introduced by #3596709: Automated Drupal 12 compatibility fixes for content_templates 1.0.x-dev
On /template/{node}/quick_clone, AJAX interactions that rebuild and cache the form, such as removing an image from a media field, cause the form array to be serialized. Because the #submit callback contains the hooks service object, serialization also attempts to serialize the service dependency graph, which includes an indirect reference to the database connection.
This results in the following exception during form cache storage:
LogicException: The database connection is not serializable.This is a regression from the previous procedural hook implementation, where the submit callback was registered as a serializable function name. The issue breaks AJAX rebuilds on the clone form and prevents users from completing normal form interactions involving AJAX-enabled widgets.
Steps to reproduce
- Install Content templates 1.3.0 with Drupal core 11.3 or later.
- Install and enable the Quick Node Clone integration.
- Use a content type that has a media image field and can be cloned from a template.
- Open
/template/{node}/quick_clone. - In a media image field, click Remove.
- Observe that the AJAX request fails with
LogicException: The database connection is not serializableinstead of rebuilding the widget.
Proposed resolution
Register the submit callback as a serializable static callback instead of an object callback.
The patch changes the callback registration from:
array_unshift($form['actions']['submit']['#submit'], [$this, 'quickNodeCloneSubmit']);to:
array_unshift($form['actions']['submit']['#submit'], [self::class, 'quickNodeCloneSubmit']);
It also changes quickNodeCloneSubmit() from an instance method to a static method:
public static function quickNodeCloneSubmit($form, FormStateInterface $form_state)
This resolves the issue because the form array now contains an array of serializable strings rather than the hooks service object. The callback does not use $this; it only reads data from $form_state and sets the template field on the cloned node, so making it static does not change the callback behavior.
Remaining tasks
Review the merge request.
User interface changes
None.
API changes
None.
Data model changes
None.
Issue fork content_templates-3605926
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:
Comments
Comment #3
lolgm commentedMR !13 implements the proposed solution in this issue.
Comment #4
csakiistvanComment #5
csakiistvanEnvironment
Prerequisites
(Replace
4with the node id used.)Root cause
Since the 1.3.0 OOP hook conversion, the Quick Node Clone form submit handler was registered as an object callback
[$this, 'quickNodeCloneSubmit']. On/template/{node}/quick_clone, AJAX interactions that rebuild and cache the form (e.g. removing an image) serialize the form array; the object callback drags in the hooks service and its dependency graph, which indirectly references the database connection, so serialization fails.Steps
[self::class, 'quickNodeCloneSubmit']) and makequickNodeCloneSubmit()a static method.ddev drush cr/template/4/quick_clone.Expected results
Actual results
Before the fix, clicking Remove on the image field of the clone form returned an AJAX HTTP 500 with
LogicException: The database connection is not serializable(thrown while writing the rebuilt form to the form cache), because the[$this, 'quickNodeCloneSubmit']object callback pulled the hooks service and its database-connection reference into serialization. After the fix, the callback is a serializable static reference, the form caches cleanly, and the Remove interaction rebuilds the widget without any error.Testing produced with the assistance of an LLM.
Comment #6
csakiistvanComment #8
a.dmitriiev commentedI have used dependency serialization trait instead. As the approach was moved to use hook classes, using static methods means using static calls to `\Drupal` global object to call services, etc. This prevents the hook class from using the DI that is actually the main reason for using hook class.
Please review the updated MR.
Comment #9
jesus_md commentedConfirmed and tested. Reproduced the original bug on 1.3.0: cloning a node with an image field via /clone/{node}/quick_clone and removing the image via AJAX throws LogicException: The database connection is not serializable, from FormBuilder::setCache() when the form (including the ContentTemplatesHooks service reference from the submit callback) gets serialized into the form cache.
Applied MR !13 as currently pushed (DependencySerializationTrait on ContentTemplatesHooks) — the AJAX rebuild now completes without error, and the clone submission itself still works correctly afterward.
Comment #10
jesus_md commentedComment #12
a.dmitriiev commentedAdded to merge train. Thank you everyone!