1
0
mirror of https://github.com/flarum/core.git synced 2025-07-25 18:51:40 +02:00

Eager loading extender (#2724)

* Eager loading extender
* Add tests for the eager loading extender
This commit is contained in:
Sami Mazouz
2021-03-25 15:36:39 +01:00
committed by GitHub
parent 4cc9aeeb28
commit e60bf67c61
8 changed files with 255 additions and 7 deletions

View File

@@ -11,6 +11,8 @@ namespace Flarum\Api\Controller;
use Flarum\Api\JsonApiResponse;
use Illuminate\Contracts\Container\Container;
use Illuminate\Database\Eloquent\Collection;
use Illuminate\Support\Str;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Psr\Http\Server\RequestHandlerInterface;
@@ -84,6 +86,11 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
*/
protected static $beforeSerializationCallbacks = [];
/**
* @var array
*/
protected static $loadRelations = [];
/**
* {@inheritdoc}
*/
@@ -139,6 +146,47 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
*/
abstract protected function createElement($data, SerializerInterface $serializer);
/**
* Eager loads the required relationships.
*
* @param Collection $models
* @param array $relations
* @return void
*/
protected function loadRelations(Collection $models, array $relations): void
{
$addedRelations = [];
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
if (isset(static::$loadRelations[$class])) {
$addedRelations = array_merge($addedRelations, static::$loadRelations[$class]);
}
}
if (! empty($addedRelations)) {
usort($addedRelations, function ($a, $b) {
return substr_count($a, '.') - substr_count($b, '.');
});
foreach ($addedRelations as $relation) {
if (strpos($relation, '.') !== false) {
$parentRelation = Str::beforeLast($relation, '.');
if (! in_array($parentRelation, $relations, true)) {
continue;
}
}
$relations[] = $relation;
}
}
if (! empty($relations)) {
$relations = array_unique($relations);
$models->loadMissing($relations);
}
}
/**
* @param ServerRequestInterface $request
* @return array
@@ -348,4 +396,13 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
static::$beforeSerializationCallbacks[$controllerClass][] = $callback;
}
public static function setLoadRelations(string $controllerClass, array $relations)
{
if (! isset(static::$loadRelations[$controllerClass])) {
static::$loadRelations[$controllerClass] = [];
}
static::$loadRelations[$controllerClass] = array_merge(static::$loadRelations[$controllerClass], $relations);
}
}

View File

@@ -121,7 +121,9 @@ class ListDiscussionsController extends AbstractListController
}
}
$results = $results->getResults()->load($include);
$results = $results->getResults();
$this->loadRelations($results, $include);
if ($relations = array_intersect($include, ['firstPost', 'lastPost', 'mostRelevantPost'])) {
foreach ($results as $discussion) {

View File

@@ -28,6 +28,10 @@ class ListGroupsController extends AbstractListController
{
$actor = $request->getAttribute('actor');
return Group::whereVisibleTo($actor)->get();
$results = Group::whereVisibleTo($actor)->get();
$this->loadRelations($results, []);
return $results;
}
}

View File

@@ -76,9 +76,11 @@ class ListNotificationsController extends AbstractListController
$include[] = 'subject';
}
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset)
->load(array_diff($include, ['subject.discussion']))
->all();
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset);
$this->loadRelations($notifications, array_diff($include, ['subject.discussion']));
$notifications = $notifications->all();
$areMoreResults = false;

View File

@@ -104,7 +104,11 @@ class ListPostsController extends AbstractListController
$include[] = 'user.groups';
}
return $results->getResults()->load($include);
$results = $results->getResults();
$this->loadRelations($results, $include);
return $results;
}
/**

View File

@@ -105,6 +105,10 @@ class ListUsersController extends AbstractListController
$results->areMoreResults() ? null : 0
);
return $results->getResults()->load($include);
$results = $results->getResults();
$this->loadRelations($results, $include);
return $results;
}
}

View File

@@ -29,6 +29,7 @@ class ApiController implements ExtenderInterface
private $addSortFields = [];
private $removeSortFields = [];
private $sort;
private $load = [];
/**
* @param string $controllerClass The ::class attribute of the controller you are modifying.
@@ -216,6 +217,27 @@ class ApiController implements ExtenderInterface
return $this;
}
/**
* Eager loads relationships needed for serializer logic.
*
* First level relationships will be loaded regardless of whether they are included in the response.
* Sublevel relationships will only be loaded if the upper level was included or manually loaded.
*
* @example If a relationship such as: 'relation.subRelation' is specified,
* it will only be loaded if 'relation' is or has been loaded.
* To force load the relationship, both levels have to be specified,
* example: ['relation', 'relation.subRelation'].
*
* @param string|array
* @return self
*/
public function load($relations)
{
$this->load = array_merge($this->load, (array) $relations);
return $this;
}
public function extend(Container $container, Extension $extension = null)
{
$this->beforeDataCallbacks[] = function (AbstractSerializeController $controller) use ($container) {
@@ -281,6 +303,8 @@ class ApiController implements ExtenderInterface
$beforeSerializationCallback = ContainerUtil::wrapCallback($beforeSerializationCallback, $container);
AbstractSerializeController::addSerializationPreparationCallback($this->controllerClass, $beforeSerializationCallback);
}
AbstractSerializeController::setLoadRelations($this->controllerClass, $this->load);
}
/**