Skip to content

Commit 8b128d4

Browse files
perf(ForeignModelField): index in-scope model objects
1 parent 49a68fd commit 8b128d4

File tree

2 files changed

+66
-2
lines changed

2 files changed

+66
-2
lines changed

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Core/Model.inc

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@ class Model {
3939
use BaseTraits;
4040

4141
/**
42-
* @const string $READ_LOCK_FILE
42+
* @var string $WRITE_LOCK_FILE
43+
* The file path used to create a lock when writing to the pfSense configuration file. This prevents multiple
44+
* simultaneous writes to the config file that could corrupt the config.
4345
*/
4446
const WRITE_LOCK_FILE = '/tmp/.RESTAPI.write_config.lock';
4547

@@ -535,6 +537,9 @@ class Model {
535537
if (isset(self::$object_cache[$class])) {
536538
unset(self::$object_cache[$class]);
537539
}
540+
541+
# Clear foreign model indices
542+
RESTAPI\Fields\ForeignModelField::clear_model_index();
538543
}
539544

540545
/**

pfSense-pkg-RESTAPI/files/usr/local/pkg/RESTAPI/Fields/ForeignModelField.inc

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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
*/
1818
class 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

Comments
 (0)