Support relationships between custom resource objects
>>> [!note] Migrated issue <!-- Drupal.org comment --> <!-- Migrated from issue #3591364. --> Reported by: [mglaman](https://www.drupal.org/user/2416470) Related to !26 >>> <h3 id="summary-problem-motivation">Problem/Motivation</h3> <p>Custom JSON:API resources built with this module cannot expose relationships to other custom resources. Two hard blockers exist in core JSON:API:</p> <ol> <li><code>ResourceObjectNormalizer::serializeField()</code> assumes any field that does not implement <code>FieldItemListInterface</code> needs no normalization. If a resource field holds a <code>Relationship</code> object, it wraps it in <code>CacheableNormalization::permanent()</code>, triggering an assertion failure because a normalization is being wrapped in another normalization.</li> <li><code>IncludeResolver::resolveIncludeTree()</code> only resolves includes for fields that implement <code>EntityReferenceFieldItemListInterface</code>. Custom <code>Relationship</code> fields are silently skipped, so <code>?include=</code> never works for non-entity-backed relationships.</li> </ol> <p>The result: a custom resource can only express relationships to real Drupal entities sourced from entity reference fields. There is no supported way to relate two custom resource objects to each other.</p> <h3 id="summary-proposed-resolution">Proposed resolution</h3> <p>Add three pieces to this module:</p> <p><strong>ResourceObjectRelationship / ResourceObjectRelationshipInterface</strong></p> <p>A <code>Relationship</code> subclass that holds the referenced <code>ResourceObject</code> instances directly. Provides a static factory <code>createFromResourceObjects()</code> for convenient construction. The interface lets the include resolver identify custom relationships without coupling to the concrete class.</p> <p><strong>Decorated ResourceObjectNormalizer</strong></p> <p>Extends core's <code>ResourceObjectNormalizer</code> and overrides <code>serializeField()</code> to short-circuit when the field is already a <code>Relationship</code> instance &mdash; normalizing it directly instead of wrapping it. Wired in via <code>JsonapiResourcesServiceProvider</code> using the same namespace-impostor technique used by JSON:API Extras (see <a href="https://www.drupal.org/project/jsonapi_extras/issues/3036904">#3036904</a>).</p> <p><strong>Decorated IncludeResolver</strong></p> <p>Wraps core's <code>IncludeResolver</code> as a service decorator. After the inner resolver runs its normal entity-backed include logic, the decorator walks the same include tree looking for fields that implement <code>ResourceObjectRelationshipInterface</code> and adds their referenced resources to the included data. Recursion handles nested includes.</p> <p><strong>What is NOT in scope</strong></p> <p>Non-entity-backed query support (<code>NonEntityQueryBase</code>) is intentionally excluded &mdash; that is a separate problem with design questions of its own and will be tracked separately.</p> <p><strong>Remaining tasks</strong></p> <ul> <li>Port <code>ResourceObjectRelationship</code>, <code>ResourceObjectRelationshipInterface</code>, <code>ResourceObjectNormalizer</code> impostor, <code>JsonapiResourcesServiceProvider</code>, and <code>IncludeResolver</code> decorator from MR !1 on <a href="https://www.drupal.org/project/jsonapi_resources/issues/3275484">#3275484</a></li> <li>Update <code>CurrentUserInfo</code> test resource to demonstrate entity-relationship support (roles as a real relationship)</li> <li>Add focused functional test assertions for custom resource-to-resource relationships and <code>?include=</code> support</li> <li>PHPCS / PHPStan clean</li> </ul> <p><strong>Related</strong></p> <p>Partially implemented in <a href="https://www.drupal.org/project/jsonapi_resources/issues/3275484">#3275484</a> MR !1 &mdash; this issue extracts the mergeable subset.</p> <h3 id="summary-api-changes">API changes</h3> <p>New public API:</p> <ul> <li><code>Drupal\jsonapi_resources\Resource\ResourceObjectRelationshipInterface</code></li> <li><code>Drupal\jsonapi_resources\Resource\ResourceObjectRelationship</code></li> </ul> <h3 id="summary-data-model-changes">Data model changes</h3> <p>None.</p>
issue