mirror of
https://github.com/flarum/framework.git
synced 2025-03-31 04:15:15 +08:00
feat: Create loadWhere
relations extender (#3116)
This commit is contained in:
parent
7b80d3932d
commit
2b163025d6
@ -88,10 +88,15 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
protected static $beforeSerializationCallbacks = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
* @var string[]
|
||||
*/
|
||||
protected static $loadRelations = [];
|
||||
|
||||
/**
|
||||
* @var array<string, callable>
|
||||
*/
|
||||
protected static $loadRelationCallables = [];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@ -149,6 +154,8 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
|
||||
/**
|
||||
* Returns the relations to load added by extenders.
|
||||
*
|
||||
* @return string[]
|
||||
*/
|
||||
protected function getRelationsToLoad(): array
|
||||
{
|
||||
@ -164,15 +171,34 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager loads the required relationships.
|
||||
* Returns the relation callables to load added by extenders.
|
||||
*
|
||||
* @param Collection $models
|
||||
* @param array $relations
|
||||
* @return void
|
||||
* @return array<string, callable>
|
||||
*/
|
||||
protected function loadRelations(Collection $models, array $relations): void
|
||||
protected function getRelationCallablesToLoad(): array
|
||||
{
|
||||
$addedRelationCallables = [];
|
||||
|
||||
foreach (array_reverse(array_merge([static::class], class_parents($this))) as $class) {
|
||||
if (isset(static::$loadRelationCallables[$class])) {
|
||||
$addedRelationCallables = array_merge($addedRelationCallables, static::$loadRelationCallables[$class]);
|
||||
}
|
||||
}
|
||||
|
||||
return $addedRelationCallables;
|
||||
}
|
||||
|
||||
/**
|
||||
* Eager loads the required relationships.
|
||||
*/
|
||||
protected function loadRelations(Collection $models, array $relations, ServerRequestInterface $request = null): void
|
||||
{
|
||||
$addedRelations = $this->getRelationsToLoad();
|
||||
$addedRelationCallables = $this->getRelationCallablesToLoad();
|
||||
|
||||
foreach ($addedRelationCallables as $name => $relation) {
|
||||
$addedRelations[] = $name;
|
||||
}
|
||||
|
||||
if (! empty($addedRelations)) {
|
||||
usort($addedRelations, function ($a, $b) {
|
||||
@ -194,7 +220,29 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
|
||||
if (! empty($relations)) {
|
||||
$relations = array_unique($relations);
|
||||
$models->loadMissing($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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -430,4 +478,16 @@ abstract class AbstractSerializeController implements RequestHandlerInterface
|
||||
|
||||
static::$loadRelations[$controllerClass] = array_merge(static::$loadRelations[$controllerClass], $relations);
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public static function setLoadRelationCallables(string $controllerClass, array $relations)
|
||||
{
|
||||
if (! isset(static::$loadRelationCallables[$controllerClass])) {
|
||||
static::$loadRelationCallables[$controllerClass] = [];
|
||||
}
|
||||
|
||||
static::$loadRelationCallables[$controllerClass] = array_merge(static::$loadRelationCallables[$controllerClass], $relations);
|
||||
}
|
||||
}
|
||||
|
@ -125,7 +125,7 @@ class ListDiscussionsController extends AbstractListController
|
||||
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
$this->loadRelations($results, $include, $request);
|
||||
|
||||
if ($relations = array_intersect($include, ['firstPost', 'lastPost', 'mostRelevantPost'])) {
|
||||
foreach ($results as $discussion) {
|
||||
|
@ -82,6 +82,10 @@ class ListGroupsController extends AbstractListController
|
||||
$queryResults->areMoreResults() ? null : 0
|
||||
);
|
||||
|
||||
return $queryResults->getResults();
|
||||
$results = $queryResults->getResults();
|
||||
|
||||
$this->loadRelations($results, [], $request);
|
||||
|
||||
return $results;
|
||||
}
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ class ListNotificationsController extends AbstractListController
|
||||
|
||||
$notifications = $this->notifications->findByUser($actor, $limit + 1, $offset);
|
||||
|
||||
$this->loadRelations($notifications, array_diff($include, ['subject.discussion']));
|
||||
$this->loadRelations($notifications, array_diff($include, ['subject.discussion']), $request);
|
||||
|
||||
$notifications = $notifications->all();
|
||||
|
||||
|
@ -108,7 +108,7 @@ class ListPostsController extends AbstractListController
|
||||
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
$this->loadRelations($results, $include, $request);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class ListUsersController extends AbstractListController
|
||||
|
||||
$results = $results->getResults();
|
||||
|
||||
$this->loadRelations($results, $include);
|
||||
$this->loadRelations($results, $include, $request);
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ class ApiController implements ExtenderInterface
|
||||
private $removeSortFields = [];
|
||||
private $sort;
|
||||
private $load = [];
|
||||
private $loadCallables = [];
|
||||
|
||||
/**
|
||||
* @param string $controllerClass: The ::class attribute of the controller you are modifying.
|
||||
@ -303,7 +304,27 @@ class ApiController implements ExtenderInterface
|
||||
*/
|
||||
public function load($relations): self
|
||||
{
|
||||
$this->load = array_merge($this->load, (array) $relations);
|
||||
$this->load = array_merge($this->load, array_map('strval', (array) $relations));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows loading a relationship with additional query modification.
|
||||
*
|
||||
* @param string $relation: Relationship name, see load method description.
|
||||
* @param callable(\Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Relations\Relation, \Psr\Http\Message\ServerRequestInterface|null, array): void $callback
|
||||
*
|
||||
* The callback to modify the query, should accept:
|
||||
* - \Illuminate\Database\Query\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.
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function loadWhere(string $relation, callable $callback): self
|
||||
{
|
||||
$this->loadCallables = array_merge($this->loadCallables, [$relation => $callback]);
|
||||
|
||||
return $this;
|
||||
}
|
||||
@ -375,6 +396,7 @@ class ApiController implements ExtenderInterface
|
||||
}
|
||||
|
||||
AbstractSerializeController::setLoadRelations($this->controllerClass, $this->load);
|
||||
AbstractSerializeController::setLoadRelationCallables($this->controllerClass, $this->loadCallables);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -803,6 +803,126 @@ class ApiControllerTest extends TestCase
|
||||
|
||||
$this->assertTrue($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_callable_first_level_relation_is_loaded_if_added()
|
||||
{
|
||||
$users = null;
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->hasOne('firstLevelRelation', Post::class, 'user_id'),
|
||||
(new Extend\ApiController(ListUsersController::class))
|
||||
->loadWhere('firstLevelRelation', function ($query, $request) {})
|
||||
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
|
||||
$users = $data;
|
||||
|
||||
return [];
|
||||
})
|
||||
);
|
||||
|
||||
$this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertFalse($users->filter->relationLoaded('firstLevelRelation')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_callable_second_level_relation_is_loaded_if_added()
|
||||
{
|
||||
$users = null;
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->hasOne('firstLevelRelation', Post::class, 'user_id'),
|
||||
(new Extend\Model(Post::class))
|
||||
->belongsTo('secondLevelRelation', Discussion::class),
|
||||
(new Extend\ApiController(ListUsersController::class))
|
||||
->load('firstLevelRelation')
|
||||
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
|
||||
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
|
||||
$users = $data;
|
||||
|
||||
return [];
|
||||
})
|
||||
);
|
||||
|
||||
$this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertFalse($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_callable_second_level_relation_is_not_loaded_when_first_level_is_not()
|
||||
{
|
||||
$users = null;
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->hasOne('firstLevelRelation', Post::class, 'user_id'),
|
||||
(new Extend\Model(Post::class))
|
||||
->belongsTo('secondLevelRelation', Discussion::class),
|
||||
(new Extend\ApiController(ListUsersController::class))
|
||||
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
|
||||
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
|
||||
$users = $data;
|
||||
|
||||
return [];
|
||||
})
|
||||
);
|
||||
|
||||
$this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertTrue($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isEmpty());
|
||||
}
|
||||
|
||||
/**
|
||||
* @test
|
||||
*/
|
||||
public function custom_callable_second_level_relation_is_loaded_when_first_level_is()
|
||||
{
|
||||
$users = null;
|
||||
|
||||
$this->extend(
|
||||
(new Extend\Model(User::class))
|
||||
->hasOne('firstLevelRelation', Post::class, 'user_id'),
|
||||
(new Extend\Model(Post::class))
|
||||
->belongsTo('secondLevelRelation', Discussion::class),
|
||||
(new Extend\ApiController(ListUsersController::class))
|
||||
->loadWhere('firstLevelRelation', function ($query, $request) {})
|
||||
->loadWhere('firstLevelRelation.secondLevelRelation', function ($query, $request) {})
|
||||
->prepareDataForSerialization(function ($controller, $data) use (&$users) {
|
||||
$users = $data;
|
||||
|
||||
return [];
|
||||
})
|
||||
);
|
||||
|
||||
$this->send(
|
||||
$this->request('GET', '/api/users', [
|
||||
'authenticatedAs' => 1,
|
||||
])
|
||||
);
|
||||
|
||||
$this->assertTrue($users->pluck('firstLevelRelation')->filter->relationLoaded('secondLevelRelation')->isNotEmpty());
|
||||
}
|
||||
}
|
||||
|
||||
class CustomDiscussionSerializer extends DiscussionSerializer
|
||||
|
Loading…
x
Reference in New Issue
Block a user