mirror of
https://github.com/flarum/framework.git
synced 2024-12-12 22:53:37 +08:00
feat: eager loading
This commit is contained in:
parent
7756e330b3
commit
d2bbd83492
|
@ -72,18 +72,24 @@ return [
|
|||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint): Endpoint\Index {
|
||||
return $endpoint->eagerLoad([
|
||||
return $endpoint->eagerLoadWhenIncluded([
|
||||
'firstPost' => [
|
||||
'firstPost.mentionsUsers', 'firstPost.mentionsPosts',
|
||||
'firstPost.mentionsPosts.user', 'firstPost.mentionsPosts.discussion', 'firstPost.mentionsGroups',
|
||||
],
|
||||
'lastPost' => [
|
||||
'lastPost.mentionsUsers', 'lastPost.mentionsPosts',
|
||||
'lastPost.mentionsPosts.user', 'lastPost.mentionsPosts.discussion', 'lastPost.mentionsGroups',
|
||||
],
|
||||
]);
|
||||
})
|
||||
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\Show {
|
||||
return $endpoint->addDefaultInclude(['posts.mentionedBy', 'posts.mentionedBy.user', 'posts.mentionedBy.discussion'])
|
||||
->eagerLoad([
|
||||
->eagerLoadWhenIncluded([
|
||||
'posts' => [
|
||||
'posts.mentionsUsers', 'posts.mentionsPosts', 'posts.mentionsPosts.user',
|
||||
'posts.mentionsPosts.discussion', 'posts.mentionsGroups'
|
||||
],
|
||||
]);
|
||||
}),
|
||||
|
||||
|
@ -124,10 +130,10 @@ return [
|
|||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint): Endpoint\Show {
|
||||
return $endpoint->eagerLoad(['posts.mentionsTags']);
|
||||
return $endpoint->eagerLoadWhenIncluded(['posts' => ['posts.mentionsTags']]);
|
||||
})
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint): Endpoint\Index {
|
||||
return $endpoint->eagerLoad(['firstPost.mentionsTags', 'lastPost.mentionsTags']);
|
||||
return $endpoint->eagerLoadWhenIncluded(['firstPost' => ['firstPost.mentionsTags'], 'lastPost' => ['lastPost.mentionsTags']]);
|
||||
}),
|
||||
|
||||
(new Extend\ApiResource(Resource\PostResource::class))
|
||||
|
|
|
@ -36,10 +36,6 @@ use Illuminate\Database\Eloquent\Builder;
|
|||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
|
||||
$eagerLoadTagState = function ($query, ServerRequestInterface $request, array $relations) {
|
||||
$query->withStateFor(RequestUtil::getActor($request));
|
||||
};
|
||||
|
||||
return [
|
||||
(new Extend\Frontend('forum'))
|
||||
->js(__DIR__.'/js/dist/forum.js')
|
||||
|
@ -95,29 +91,29 @@ return [
|
|||
return $endpoint->addDefaultInclude(['tags', 'tags.parent']);
|
||||
}),
|
||||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->fields(Api\DiscussionResourceFields::class),
|
||||
|
||||
(new Extend\ApiResource(Resource\PostResource::class))
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint) {
|
||||
return $endpoint->eagerLoad('discussion.tags');
|
||||
return $endpoint->eagerLoadWhenIncluded(['discussion' => ['discussion.tags']]);
|
||||
}),
|
||||
|
||||
(new Extend\Conditional())
|
||||
->whenExtensionEnabled('flarum-flags', fn () => [
|
||||
(new Extend\ApiResource(FlagResource::class))
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint) {
|
||||
return $endpoint->eagerLoad(['post.discussion.tags']);
|
||||
return $endpoint->eagerLoadWhenIncluded(['post.discussion' => ['post.discussion.tags']]);
|
||||
}),
|
||||
]),
|
||||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->fields(Api\DiscussionResourceFields::class)
|
||||
->endpoint(
|
||||
[Endpoint\Index::class, Endpoint\Show::class, Endpoint\Create::class],
|
||||
function (Endpoint\Index|Endpoint\Show|Endpoint\Create $endpoint) use ($eagerLoadTagState) {
|
||||
function (Endpoint\Index|Endpoint\Show|Endpoint\Create $endpoint) {
|
||||
return $endpoint
|
||||
->addDefaultInclude(['tags', 'tags.parent'])
|
||||
->eagerLoadWhere('tags', $eagerLoadTagState);
|
||||
->eagerLoadWhere('tags', function ($query, Context $context) {
|
||||
$query->withStateFor($context->getActor());
|
||||
});
|
||||
}
|
||||
),
|
||||
|
||||
|
@ -181,18 +177,12 @@ return [
|
|||
])
|
||||
->endpoint(Endpoint\Index::class, function (Endpoint\Index $endpoint) {
|
||||
return $endpoint
|
||||
->addDefaultInclude(['eventPostMentionsTags'])
|
||||
->eagerLoadWhere('eventPostMentionsTags', function (Relation|Builder $query, ServerRequestInterface $request) {
|
||||
$query->whereVisibleTo(RequestUtil::getActor($request));
|
||||
});
|
||||
->addDefaultInclude(['eventPostMentionsTags']);
|
||||
}),
|
||||
|
||||
(new Extend\ApiResource(Resource\DiscussionResource::class))
|
||||
->endpoint(Endpoint\Show::class, function (Endpoint\Show $endpoint) {
|
||||
return $endpoint
|
||||
->addDefaultInclude(['posts.eventPostMentionsTags'])
|
||||
->eagerLoadWhere('posts.eventPostMentionsTags', function (Relation|Builder $query, ServerRequestInterface $request) {
|
||||
$query->whereVisibleTo(RequestUtil::getActor($request));
|
||||
});
|
||||
->addDefaultInclude(['posts.eventPostMentionsTags']);
|
||||
}),
|
||||
];
|
||||
|
|
|
@ -1,155 +0,0 @@
|
|||
<?php
|
||||
|
||||
namespace Flarum\Api\Endpoint\Concerns;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Str;
|
||||
use Tobyz\JsonApiServer\Laravel\EloquentResource;
|
||||
|
||||
/**
|
||||
* This is directed at eager loading relationships apart from the request includes.
|
||||
*/
|
||||
trait HasEagerLoading
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
protected array $loadRelations = [];
|
||||
|
||||
/**
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected array $loadRelationCallables = [];
|
||||
|
||||
/**
|
||||
* Eager loads relationships needed for serializer logic.
|
||||
*
|
||||
* First level relationships will be loaded regardless of whether they are included in the response.
|
||||
* Sub-level 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|string[] $relations
|
||||
*/
|
||||
public function eagerLoad(array|string $relations): self
|
||||
{
|
||||
$this->loadRelations = array_merge($this->loadRelations, array_map('strval', (array) $relations));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows loading a relationship with additional query modification.
|
||||
*
|
||||
* @param string $relation: Relationship name, see load method description.
|
||||
* @template R of Relation
|
||||
* @param (callable(Builder|R, \Psr\Http\Message\ServerRequestInterface|null, array): void) $callback
|
||||
*
|
||||
* The callback to modify the query, should accept:
|
||||
* - \Illuminate\Database\Eloquent\Builder|\Illuminate\Database\Eloquent\Relations\Relation $query: A query object.
|
||||
* - \Psr\Http\Message\ServerRequestInterface|null $request: An instance of the request.
|
||||
* - array $relations: An array of relations that are to be loaded.
|
||||
*/
|
||||
public function eagerLoadWhere(string $relation, callable $callback): self
|
||||
{
|
||||
$this->loadRelationCallables = array_merge($this->loadRelationCallables, [$relation => $callback]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager loads the required relationships.
|
||||
*/
|
||||
protected function loadRelations(Collection $models, Context $context, array $included = []): void
|
||||
{
|
||||
if (! $context->collection instanceof EloquentResource) {
|
||||
return;
|
||||
}
|
||||
|
||||
$request = $context->request;
|
||||
|
||||
$included = $this->stringInclude($included);
|
||||
$models = $models->filter(fn ($model) => $model instanceof Model);
|
||||
|
||||
$addedRelations = $this->loadRelations;
|
||||
$addedRelationCallables = $this->loadRelationCallables;
|
||||
|
||||
$relations = $included;
|
||||
|
||||
foreach ($addedRelationCallables as $name => $relation) {
|
||||
$addedRelations[] = $name;
|
||||
}
|
||||
|
||||
if (! empty($addedRelations)) {
|
||||
usort($addedRelations, function ($a, $b) {
|
||||
return substr_count($a, '.') - substr_count($b, '.');
|
||||
});
|
||||
|
||||
foreach ($addedRelations as $relation) {
|
||||
if (str_contains($relation, '.')) {
|
||||
$parentRelation = Str::beforeLast($relation, '.');
|
||||
|
||||
if (! in_array($parentRelation, $relations, true)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$relations[] = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($relations)) {
|
||||
$relations = array_unique($relations);
|
||||
}
|
||||
|
||||
$callableRelations = [];
|
||||
$nonCallableRelations = [];
|
||||
|
||||
foreach ($relations as $relation) {
|
||||
if (isset($addedRelationCallables[$relation])) {
|
||||
$load = $addedRelationCallables[$relation];
|
||||
|
||||
$callableRelations[$relation] = function ($query) use ($load, $request, $relations) {
|
||||
$load($query, $request, $relations);
|
||||
};
|
||||
} else {
|
||||
$nonCallableRelations[] = $relation;
|
||||
}
|
||||
}
|
||||
|
||||
if (! empty($callableRelations)) {
|
||||
$models->loadMissing($callableRelations);
|
||||
}
|
||||
|
||||
if (! empty($nonCallableRelations)) {
|
||||
$models->loadMissing($nonCallableRelations);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* From format of: 'relation' => [ ...nested ] to ['relation', 'relation.nested']
|
||||
*/
|
||||
private function stringInclude(array $include): array
|
||||
{
|
||||
$relations = [];
|
||||
|
||||
foreach ($include as $relation => $nested) {
|
||||
$relations[] = $relation;
|
||||
|
||||
if (is_array($nested)) {
|
||||
foreach ($this->stringInclude($nested) as $nestedRelation) {
|
||||
$relations[] = $relation.'.'.$nestedRelation;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $relations;
|
||||
}
|
||||
}
|
|
@ -2,27 +2,17 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
|
||||
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Tobyz\JsonApiServer\Endpoint\Create as BaseCreate;
|
||||
|
||||
class Create extends BaseCreate implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->beforeSerialization(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
|
||||
return $model;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,11 @@ namespace Flarum\Api\Endpoint;
|
|||
use Flarum\Api\Endpoint\Concerns\ExtractsListingParams;
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
|
||||
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
|
||||
use Tobyz\JsonApiServer\Endpoint\Endpoint as BaseEndpoint;
|
||||
|
||||
class Endpoint extends BaseEndpoint implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasCustomHooks;
|
||||
use HasEagerLoading;
|
||||
use ExtractsListingParams;
|
||||
}
|
||||
|
|
|
@ -6,11 +6,9 @@ use Flarum\Api\Context;
|
|||
use Flarum\Api\Endpoint\Concerns\ExtractsListingParams;
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
|
||||
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
|
||||
use Flarum\Search\SearchCriteria;
|
||||
use Flarum\Search\SearchManager;
|
||||
use Illuminate\Contracts\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Tobyz\JsonApiServer\Endpoint\Index as BaseIndex;
|
||||
use Tobyz\JsonApiServer\Pagination\OffsetPagination;
|
||||
use Tobyz\JsonApiServer\Pagination\Pagination;
|
||||
|
@ -18,7 +16,6 @@ use Tobyz\JsonApiServer\Pagination\Pagination;
|
|||
class Index extends BaseIndex implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use ExtractsListingParams;
|
||||
use HasCustomHooks;
|
||||
|
||||
|
@ -63,9 +60,6 @@ class Index extends BaseIndex implements EndpointInterface
|
|||
}
|
||||
|
||||
return $context;
|
||||
})
|
||||
->beforeSerialization(function (Context $context, iterable $models) {
|
||||
$this->loadRelations(Collection::make($models), $context, $this->getInclude($context));
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -2,27 +2,19 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
use Flarum\Api\Endpoint\Concerns\ExtractsListingParams;
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
|
||||
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Tobyz\JsonApiServer\Endpoint\Show as BaseShow;
|
||||
|
||||
class Show extends BaseShow implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use ExtractsListingParams;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->beforeSerialization(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
});
|
||||
parent::setUp();;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,25 +2,17 @@
|
|||
|
||||
namespace Flarum\Api\Endpoint;
|
||||
|
||||
use Flarum\Api\Context;
|
||||
use Flarum\Api\Endpoint\Concerns\HasAuthorization;
|
||||
use Flarum\Api\Endpoint\Concerns\HasCustomHooks;
|
||||
use Flarum\Api\Endpoint\Concerns\HasEagerLoading;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Tobyz\JsonApiServer\Endpoint\Update as BaseUpdate;
|
||||
|
||||
class Update extends BaseUpdate implements EndpointInterface
|
||||
{
|
||||
use HasAuthorization;
|
||||
use HasEagerLoading;
|
||||
use HasCustomHooks;
|
||||
|
||||
public function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->beforeSerialization(function (Context $context, object $model) {
|
||||
$this->loadRelations(Collection::make([$model]), $context, $this->getInclude($context));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -94,6 +94,7 @@ class DiscussionResource extends AbstractDatabaseResource
|
|||
'mostRelevantPost.user'
|
||||
])
|
||||
->defaultSort('-lastPostedAt')
|
||||
->eagerLoad('state')
|
||||
->paginate(),
|
||||
];
|
||||
}
|
||||
|
@ -184,12 +185,14 @@ class DiscussionResource extends AbstractDatabaseResource
|
|||
->includable(),
|
||||
Schema\Relationship\ToOne::make('firstPost')
|
||||
->includable()
|
||||
->inverse('discussion')
|
||||
->type('posts'),
|
||||
Schema\Relationship\ToOne::make('lastPostedUser')
|
||||
->includable()
|
||||
->type('users'),
|
||||
Schema\Relationship\ToOne::make('lastPost')
|
||||
->includable()
|
||||
->inverse('discussion')
|
||||
->type('posts'),
|
||||
Schema\Relationship\ToMany::make('posts')
|
||||
->withLinkage(function (Context $context) {
|
||||
|
@ -216,6 +219,8 @@ class DiscussionResource extends AbstractDatabaseResource
|
|||
|
||||
$posts = $discussion->posts()
|
||||
->whereVisibleTo($actor)
|
||||
->with($context->endpoint->getEagerLoadsFor('posts', $context))
|
||||
->with($context->endpoint->getWhereEagerLoadsFor('posts', $context))
|
||||
->orderBy('number')
|
||||
->skip($offset)
|
||||
->take($limit)
|
||||
|
@ -236,6 +241,7 @@ class DiscussionResource extends AbstractDatabaseResource
|
|||
Schema\Relationship\ToOne::make('mostRelevantPost')
|
||||
->visible(fn (Discussion $model, Context $context) => $context->listing())
|
||||
->includable()
|
||||
->inverse('discussion')
|
||||
->type('posts'),
|
||||
Schema\Relationship\ToOne::make('hideUser')
|
||||
->type('users'),
|
||||
|
|
Loading…
Reference in New Issue
Block a user