Skip to content

Commit 337fea4

Browse files
author
Smef
authored
Merge pull request #29 from BlueFeatherGroup/add-eager-loading
Add eager loading
2 parents 626246c + a897d1c commit 337fea4

File tree

3 files changed

+120
-6
lines changed

3 files changed

+120
-6
lines changed

src/Database/Eloquent/FMEloquentBuilder.php

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,17 @@ public function get($columns = ['*'])
2323
{
2424
$records = $this->tobase()->get();
2525
$models = $this->model->createModelsFromRecordSet($records);
26-
return $models;
26+
27+
// If we actually found models we will also eager load any relationships that
28+
// have been specified as needing to be eager loaded, which will solve the
29+
// n+1 query issue for the developers to avoid running a lot of queries.
30+
if (count($models) > 0) {
31+
$models = $this->eagerLoadRelations($models->all());
32+
} else {
33+
$models = $models->all();
34+
}
35+
36+
return $this->getModel()->newCollection($models);
2737
}
2838

2939

@@ -355,4 +365,4 @@ protected function getOnlyModifiedPortalFields($array1, $array2): array
355365
}
356366

357367

358-
}
368+
}

src/Database/Eloquent/Relations/BelongsTo.php

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,48 @@ public function addConstraints()
2020
$this->query->where($this->ownerKey, '==', $this->child->{$this->foreignKey});
2121
}
2222
}
23-
}
23+
24+
/**
25+
* Set the constraints for an eager load of the relation.
26+
*
27+
* @param array $models
28+
* @return void
29+
*/
30+
public function addEagerConstraints(array $models)
31+
{
32+
// We'll grab the primary key name of the related models since it could be set to
33+
// a non-standard name and not "id". We will then construct the constraint for
34+
// our eagerly loading query so it returns the proper models from execution.
35+
$key = $this->ownerKey;
36+
37+
$whereIn = $this->whereInMethod($this->related, $this->ownerKey);
38+
39+
$this->query->{$whereIn}($key, $this->getEagerModelKeys($models));
40+
}
41+
42+
/**
43+
* Gather the keys from an array of related models.
44+
*
45+
* @param array $models
46+
* @return array
47+
*/
48+
protected function getEagerModelKeys(array $models)
49+
{
50+
$keys = [];
51+
52+
// First we need to gather all of the keys from the parent models so we know what
53+
// to query for via the eager loading query. We will add them to an array then
54+
// execute a "where in" statement to gather up all of those related records.
55+
foreach ($models as $model) {
56+
$value = $model->{$this->foreignKey};
57+
// Unlike other DB Engines FM does not support nulls and uses an empty string instead.
58+
if (! is_null($value) && $value !== '') {
59+
$keys[] = $value;
60+
}
61+
}
62+
63+
sort($keys);
64+
65+
return array_values(array_unique($keys));
66+
}
67+
}

src/Database/Query/FMBaseBuilder.php

Lines changed: 63 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,13 @@
44
namespace BlueFeather\EloquentFileMaker\Database\Query;
55

66

7+
use _PHPStan_59fb0a3b2\Symfony\Component\String\Exception\RuntimeException;
78
use BlueFeather\EloquentFileMaker\Exceptions\FileMakerDataApiException;
89
use DateTimeInterface;
910
use Illuminate\Database\Query\Builder;
1011
use Illuminate\Http\File;
1112
use Illuminate\Http\UploadedFile;
13+
use Illuminate\Support\Arr;
1214
use Illuminate\Support\Collection;
1315
use InvalidArgumentException;
1416

@@ -124,6 +126,8 @@ class FMBaseBuilder extends Builder
124126
public $containerFieldName;
125127
public $containerFile;
126128

129+
protected $whereIns = [];
130+
127131

128132
/**
129133
* Add a basic where clause to the query.
@@ -160,7 +164,7 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
160164
// Create a new find array if null
161165
$count = sizeof($this->wheres);
162166
if ($count == 0) {
163-
$currentFind = collect([]);
167+
$currentFind = [];
164168
} else {
165169
$currentFind = $this->wheres[sizeof($this->wheres) - 1];
166170
}
@@ -183,13 +187,13 @@ public function where($column, $operator = null, $value = null, $boolean = 'and'
183187
public function delete($id = null): int
184188
{
185189
// If an ID is passed to the method we will delete the record with this internal FileMaker record ID
186-
if (! is_null($id)) {
190+
if (!is_null($id)) {
187191
$this->where($this->defaultKeyName(), '=', $id);
188192
}
189193
$this->applyBeforeQueryCallbacks();
190194

191195
// Check if we have a record ID to delete or if this is a query for a bulk delete
192-
if ($this->getRecordId() === null){
196+
if ($this->getRecordId() === null) {
193197
// There's no individual record ID to delete, so do a bulk delete
194198
return $this->bulkDeleteFromQuery();
195199
}
@@ -403,6 +407,8 @@ public function layoutResponse($name): FMBaseBuilder
403407
*/
404408
public function get($columns = ['*'])
405409
{
410+
$this->computeWhereIns();
411+
406412
// Run the query and catch a 401 error if there are no records found - just return an empty collection
407413
try {
408414
$response = $this->connection->performFind($this);
@@ -492,6 +498,58 @@ public function omit($boolean = true): FMBaseBuilder
492498
return $this;
493499
}
494500

501+
public function whereIn($column, $values, $boolean = 'and', $not = false)
502+
{
503+
throw_if($boolean === 'or', new \RuntimeException('Eloquent FileMaker does not currently support or within a where in'));
504+
505+
$this->whereIns[] = [
506+
'column' => $this->getMappedFieldName($column),
507+
'values' => $values,
508+
'boolean' => $boolean,
509+
'not' => $not
510+
];
511+
512+
return $this;
513+
}
514+
515+
protected function computeWhereIns()
516+
{
517+
// If no where in clauses return
518+
if (empty($this->whereIns)) {
519+
return;
520+
}
521+
522+
$whereIns = array_map(function ($whereIn) {
523+
$finds = [];
524+
525+
foreach ($whereIn['values'] as $value) {
526+
$find = [
527+
$whereIn['column'] => $value,
528+
];
529+
530+
if ($whereIn['not']) {
531+
$find['omit'] = true;
532+
}
533+
534+
$finds[] = $find;
535+
}
536+
537+
return $finds;
538+
}, $this->whereIns);
539+
540+
if (empty($this->wheres)) {
541+
$this->wheres = Arr::flatten($whereIns, 1);
542+
return;
543+
}
544+
545+
$arr = Arr::crossJoin($this->wheres, ...$whereIns);
546+
$function = function ($conditions) {
547+
return array_merge(...array_values($conditions));
548+
};
549+
550+
$this->wheres = array_map($function, $arr);
551+
}
552+
495553
/**
496554
* Retrieve the minimum value of a given column.
497555
*
@@ -621,6 +679,8 @@ public function update(array $values)
621679

622680
$this->fieldData($values);
623681

682+
$this->computeWhereIns();
683+
624684
return $this->connection->update($this);
625685
}
626686

0 commit comments

Comments
 (0)