Update: The render-time validation behavior from this issue has been reverted in #3583333: Revert Render-Time Validation for Smooth Upgrade Path from 9.1.2 with Existing Invalid Content (merged via MR !187).

This issue remains for historical context. A later policy-driven redesign will reintroduce render-time validation in a more manageable way.

Problem/Motivation

Pattern content can be saved to the database without validation through several code paths, including:

  • Direct entity manipulation via PatternkitBlock::set('data', $value)
  • Form save operations that bypass validation
  • Entity hooks that save blocks without validation checks
  • Pattern update events with $allowInvalid = TRUE flag
  • Bulk update operations (e.g., alt text population)

When invalid content is rendered, it can cause fatal errors or unexpected behavior during the rendering process. For example:

  • Non-string values (numbers, objects, arrays) in string fields cause type errors
  • Missing required fields cause validation failures
  • Wrong data types cause rendering exceptions

This creates a poor user experience where pages can crash when invalid content is encountered, even if that content was saved through legitimate (but unvalidated) workflows.

Steps to reproduce

  1. Create a pattern block with valid content using the JSON editor form
  2. Save the block and find the block entity ID:
    drush sqlq "SELECT id, info FROM patternkit_field_data ORDER BY id DESC LIMIT 5"

    Note the id value for your block.

  3. Update the block's data field directly in the database using Drush to contain invalid content:

    Option A: Non-string value in string field (number instead of string)

    drush sqlq "UPDATE patternkit_field_data SET data = '{\"text\": 123, \"formatted_text\": \"Valid text\"}' WHERE id = [BLOCK_ID]"

    Option B: Array value in string field

    drush sqlq "UPDATE patternkit_field_data SET data = '{\"text\": [\"array\", \"values\"], \"formatted_text\": \"Valid text\"}' WHERE id = [BLOCK_ID]"

    Option C: Object value in string field

    drush sqlq "UPDATE patternkit_field_data SET data = '{\"text\": {\"nested\": \"value\"}, \"formatted_text\": \"Valid text\"}' WHERE id = [BLOCK_ID]"

    Replace [BLOCK_ID] with the actual block entity ID from step 2.

  4. Clear Drupal cache: drush cr
  5. Attempt to view a page containing the block
  6. Expected: Page should display an error message for the invalid pattern
  7. Actual (before fix): Page may crash with fatal errors or exceptions

Proposed resolution

Add validation error handling in the pattern rendering pipeline to catch invalid content before it causes rendering failures. The implementation:

  1. Validates content during rendering in Pattern::preRenderPatternElement() after config is set but before processing
  2. Uses existing error handling infrastructure (PatternRenderFailureEvent) for graceful error display
  3. Displays error elements instead of crashing when invalid content is detected
  4. Works for all rendering paths (blocks, direct pattern elements, etc.)

Implementation details:

  • Injected PatternValidationHelper service into Pattern element via dependency injection
  • Added validation check that runs before processSchemaValues()
  • On validation failure, dispatches PatternRenderFailureEvent which is handled by existing RenderFailureErrorDisplaySubscriber
  • Returns PatternError element with user-friendly error message instead of crashing

Error handling flow:

Invalid Content Detected
  ↓
Validation fails → Get exception → Dispatch PatternRenderFailureEvent
  ↓
Event handled? 
  ├─ Yes → Return error element (pattern_error type)
  └─ No → Throw exception → Caught by outer catch → Default error message

Files modified:

  • src/Element/Pattern.php - Added validation helper injection and validation check
  • tests/src/Unit/Element/PatternElementTest.php - Added validation test scenarios
  • tests/src/Kernel/Element/PatternInvalidContentTypeTest.php - Added kernel tests for invalid content types

Potential follow-up tasks

  • Consider adding validation to entity save hooks as a preventive measure (separate from render-time validation)
  • Monitor performance impact of validation during rendering
  • Consider caching validation results if performance becomes an issue
  • Document validation behavior in user-facing documentation

User interface changes

Error display:

  • When invalid pattern content is encountered during rendering, users will see a user-friendly error message: "Failed to render pattern [pattern_name] ([pattern_id])"
  • Error messages are displayed in place of the pattern content
  • For users with "access devel information" permission, additional debug information is available in a collapsible details element
  • No fatal errors or white screens of death occur

No breaking changes:

  • Valid content continues to render normally
  • Existing error handling for other failure types remains unchanged
  • Form validation continues to work as before

API changes

New dependency injection:

  • Pattern element now requires PatternValidationHelper service in constructor
  • Service is injected via Pattern::create() method using patternkit.helper.validation service

No breaking changes:

  • Existing code using Pattern element will continue to work (service is auto-injected)
  • No changes to public API methods
  • No changes to hook implementations
  • No changes to event system (uses existing PatternRenderFailureEvent)

Internal changes:

  • Pattern::preRenderPatternElement() now validates content before processing
  • Validation happens transparently - no API changes required for callers

Data model changes

No database schema changes:

  • No new tables or fields added
  • No changes to existing entity structures
  • No migration required

Behavioral changes:

  • Invalid content that was previously saved will now be caught during rendering
  • Invalid content displays error messages instead of causing fatal errors
  • This is a defensive measure - it doesn't prevent invalid data from being saved, but handles it gracefully when rendered

Issue fork patternkit-3563760

Command icon 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

slucero created an issue. See original summary.

slucero’s picture

Status: Active » Needs review

slucero’s picture

I've uploaded a revised solution for this using a two-pass approach for validation to support the potential for pattern field processing to correct invalid content data. One example scenario would be having tokens saved in pattern content that would resolve to valid data items, but the token itself wouldn't validate.

slucero’s picture

Status: Needs review » Reviewed & tested by the community

Tested and approved internally by @minsharm. Marking reviewed for merging.

  • slucero committed b5a22725 on 9.1.x
    [#3563760] Add render-time validation safeguards and tests.
    
slucero’s picture

Status: Reviewed & tested by the community » Fixed

Merged for inclusion in the 9.1.3 release.

Now that this issue is closed, review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, credit people who helped resolve this issue.

Status: Fixed » Closed (fixed)

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

slucero’s picture

Issue summary: View changes

Update: The render-time validation behavior from this issue has been reverted on 9.1.x in #3583333: Revert Render-Time Validation for Smooth Upgrade Path from 9.1.2 with Existing Invalid Content and merged via MR !187.

The revert removed the two unconditional assertValidPatternContent() calls from PatternFieldProcessorPluginManager::processSchemaValues because they caused upgrade regressions relative to 9.1.2 behavior:

  • WARNING log noise on render for token-bearing values in format-constrained fields.
  • Visitor-facing "This content is unavailable." for schema-invalid stored content that previously rendered in 9.1.2.

The validation helper infrastructure was retained; only the unconditional render-path usage was reverted.

A later solution will reintroduce render-time validation with a more manageable, policy-driven design so upgrade behavior remains stable.