@@ -210,13 +210,43 @@ public function relation(): Relation
210
210
*/
211
211
public function substituteBindings (): void
212
212
{
213
+ if ($ this ->hasSubstitutedBindings ()) {
214
+ $ this ->checkBinding ();
215
+ return ;
216
+ }
217
+
213
218
if ($ this ->hasResourceId ()) {
214
219
$ this ->setModel ($ this ->schema ()->repository ()->find (
215
220
$ this ->resourceId ()
216
221
));
217
222
}
218
223
}
219
224
225
+ /**
226
+ * Has the model binding already been substituted?
227
+ *
228
+ * In a normal Laravel application setup, the `api` middleware group will
229
+ * include Laravel's binding substitution middleware. This means that
230
+ * typically the boot JSON:API middleware will run *after* bindings have been
231
+ * substituted.
232
+ *
233
+ * If the route that is being executed has type-hinted the model, this means
234
+ * the model will already be substituted into the route. For example, this
235
+ * can occur if the developer has written their own controller action, or
236
+ * for custom actions.
237
+ *
238
+ * @return bool
239
+ */
240
+ private function hasSubstitutedBindings (): bool
241
+ {
242
+ if ($ name = $ this ->resourceIdName ()) {
243
+ $ expected = $ this ->schema ()->model ();
244
+ return $ this ->route ->parameter ($ name ) instanceof $ expected ;
245
+ }
246
+
247
+ return false ;
248
+ }
249
+
220
250
/**
221
251
* @param object|null $model
222
252
* @return void
@@ -235,6 +265,30 @@ private function setModel(?object $model): void
235
265
throw new NotFoundHttpException ();
236
266
}
237
267
268
+ /**
269
+ * Check the model that has already been substituted.
270
+ *
271
+ * If Laravel has substituted bindings before the JSON:API binding substitution
272
+ * is triggered, we need to check that the model that has been set on the route
273
+ * by Laravel does exist in our API. This is because the API's existence logic
274
+ * may not match the route binding query that Laravel executed to substitute
275
+ * the binding. E.g. if the developer has applied global scopes in the Server's
276
+ * `serving()` method, these global scopes may have been applied *after* the
277
+ * binding was substituted.
278
+ *
279
+ * @return void
280
+ */
281
+ private function checkBinding (): void
282
+ {
283
+ $ resourceId = $ this ->server ->resources ()->create (
284
+ $ this ->model (),
285
+ )->id ();
286
+
287
+ if (!$ this ->schema ()->repository ()->exists ($ resourceId )) {
288
+ throw new NotFoundHttpException ();
289
+ }
290
+ }
291
+
238
292
/**
239
293
* @return string|null
240
294
*/
0 commit comments