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 — 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 — 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 — 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