@@ -16,8 +16,25 @@ use RESTAPI\Responses\ServerError;
1616 * For example, a ForeignModelField can be used to relate a static route to its parent Gateway model object.
1717 */
1818class ForeignModelField extends Field {
19+ /**
20+ * @var string $MODELS_NAMESPACE The namespace prefix for all Model classes.
21+ */
1922 const MODELS_NAMESPACE = 'RESTAPI \\Models \\' ;
23+
24+ /**
25+ * @var array $models An array of instantiated Model objects for each assigned $model_name.
26+ */
2027 public array $ models = [];
28+
29+ /**
30+ * @var array $model_index
31+ * An associative array that maps objects of the assigned $model_name classes by their $model_field values
32+ * for quick lookup. This ensures the most taxing reference operations are only performed once per request.
33+ * Indices for each corresponding $model_name will be cleared automatically when any Model object of that class
34+ * is created, updated, or deleted.
35+ */
36+ public static array $ model_index = [];
37+
2138 /**
2239 * Defines the ForeignModelField object and sets its options.
2340 * @param string|array $model_name The name(s) of the foreign Model class(es) this field relates to. This should be
@@ -323,6 +340,39 @@ class ForeignModelField extends Field {
323340 return $ in_scope_models ->query ($ this ->model_query );
324341 }
325342
343+ /**
344+ * Indexes all in scope Model objects by their $model_field values for quick lookup and returns
345+ * an associative array of the indexed Model objects. This method will also cache the indices
346+ * in the static $model_index property to prevent redundant indexing operations during the same request.
347+ * @return array An associative array mapping $model_field values to their corresponding Model objects.
348+ */
349+ public function get_model_index (): array {
350+ # Get the name of the Model this Field belongs to
351+ $ model_context_name = $ this ->context ->get_class_shortname ();
352+
353+ # Check if we have already indexed Model objects for this Field in this request
354+ if (self ::$ model_index [$ model_context_name ][$ this ->name ]) {
355+ return self ::$ model_index [$ model_context_name ][$ this ->name ];
356+ }
357+
358+ # Loop through each in scope Model object and index them by their $model_field values
359+ foreach ($ this ->get_in_scope_models ()->model_objects as $ model_object ) {
360+ $ foreign_model_field_value = $ model_object ->{$ this ->model_field_internal }->value ;
361+ self ::$ model_index [$ model_context_name ][$ this ->name ][$ foreign_model_field_value ] = $ model_object ;
362+ }
363+
364+ # Return the indexed Model objects for this Field
365+ return self ::$ model_index [$ model_context_name ][$ this ->name ];
366+ }
367+
368+ /**
369+ * Clears the cached model indices.
370+ */
371+ public static function clear_model_index (): void
372+ {
373+ self ::$ model_index = [];
374+ }
375+
326376 /**
327377 * Obtains a ModelSet of the Model(s) that match this field's criteria.
328378 * @param string $field_name The name of the field used to check for matching values. This is typically set to the
@@ -331,7 +381,16 @@ class ForeignModelField extends Field {
331381 * to the same value as $this->value.
332382 */
333383 private function __get_matches (string $ field_name , mixed $ field_value ): ModelSet {
334- return $ this ->get_in_scope_models ()->query (query_params: [$ field_name => $ field_value ]);
384+ # Create a ModelSet we can use to store matching objects
385+ $ modelset = new ModelSet ();
386+ $ match = $ this ->get_model_index ()[$ field_value ] ?? null ;
387+
388+ # Only add the Model object if it exists
389+ if ($ match ) {
390+ $ modelset ->model_objects [] = $ match ;
391+ }
392+
393+ return $ modelset ;
335394 }
336395
337396 /**
0 commit comments