mirror of
https://github.com/flarum/core.git
synced 2025-07-30 21:20:24 +02:00
perf(core,mentions): limit mentionedBy
post relation results (#3780)
* perf(core,mentions): limit `mentionedBy` post relation results Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * Apply fixes from StyleCI * chore: use a static property to allow customization Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * chore: use a static property to allow customization Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * chore: include count in show post endpoint Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * chore: consistent locale key format Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * chore: forgot to delete `FilterVisiblePosts` Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * test: `mentionedByCount` must not include invisible posts to actor Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * fix: visibility scoping on `mentionedByCount` Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * fix: `loadAggregates` conflicts with visibility scopers Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> * Apply fixes from StyleCI * chore: phpstan Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> --------- Signed-off-by: Sami Mazouz <sychocouldy@gmail.com> Co-authored-by: StyleCI Bot <bot@styleci.io>
This commit is contained in:
@@ -76,6 +76,7 @@
|
||||
"psr/http-server-handler": "^1.0",
|
||||
"psr/http-server-middleware": "^1.0",
|
||||
"s9e/text-formatter": "^2.3.6",
|
||||
"staudenmeir/eloquent-eager-limit": "^1.0",
|
||||
"sycho/json-api": "^0.5.0",
|
||||
"sycho/sourcemap": "^2.0.0",
|
||||
"symfony/config": "^5.2.2",
|
||||
|
@@ -79,6 +79,11 @@
|
||||
left: auto;
|
||||
right: 0;
|
||||
}
|
||||
.Dropdown-menu--inline {
|
||||
position: relative;
|
||||
display: block;
|
||||
box-shadow: none;
|
||||
}
|
||||
.Dropdown-header {
|
||||
padding: 10px 15px;
|
||||
color: var(--heading-color);
|
||||
|
@@ -353,11 +353,14 @@
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.Avatar {
|
||||
&-badge, .Avatar {
|
||||
float: left;
|
||||
margin-left: -50px;
|
||||
.Avatar--size(32px);
|
||||
}
|
||||
&-badge {
|
||||
color: var(--control-color);
|
||||
}
|
||||
.username {
|
||||
color: var(--text-color);
|
||||
font-weight: bold;
|
||||
|
@@ -94,7 +94,8 @@ class ShowDiscussionController extends AbstractShowController
|
||||
$discussion = $this->discussions->findOrFail($discussionId, $actor);
|
||||
}
|
||||
|
||||
if (in_array('posts', $include)) {
|
||||
// If posts is included or a sub relation of post is included.
|
||||
if (in_array('posts', $include) || Str::contains(implode(',', $include), 'posts.')) {
|
||||
$postRelationships = $this->getPostRelationships($include);
|
||||
|
||||
$this->includePosts($discussion, $request, $postRelationships);
|
||||
@@ -119,7 +120,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
$offset = $this->getPostsOffset($request, $discussion, $limit);
|
||||
|
||||
$allPosts = $this->loadPostIds($discussion, $actor);
|
||||
$loadedPosts = $this->loadPosts($discussion, $actor, $offset, $limit, $include);
|
||||
$loadedPosts = $this->loadPosts($discussion, $actor, $offset, $limit, $include, $request);
|
||||
|
||||
array_splice($allPosts, $offset, $limit, $loadedPosts);
|
||||
|
||||
@@ -136,11 +137,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
return $discussion->posts()->whereVisibleTo($actor)->orderBy('number')->pluck('id')->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $include
|
||||
* @return array
|
||||
*/
|
||||
private function getPostRelationships(array $include)
|
||||
private function getPostRelationships(array $include): array
|
||||
{
|
||||
$prefixLength = strlen($prefix = 'posts.');
|
||||
$relationships = [];
|
||||
@@ -183,11 +180,11 @@ class ShowDiscussionController extends AbstractShowController
|
||||
* @param array $include
|
||||
* @return mixed
|
||||
*/
|
||||
private function loadPosts($discussion, $actor, $offset, $limit, array $include)
|
||||
private function loadPosts($discussion, $actor, $offset, $limit, array $include, ServerRequestInterface $request)
|
||||
{
|
||||
$query = $discussion->posts()->whereVisibleTo($actor);
|
||||
|
||||
$query->orderBy('number')->skip($offset)->take($limit)->with($include);
|
||||
$query->orderBy('number')->skip($offset)->take($limit);
|
||||
|
||||
$posts = $query->get();
|
||||
|
||||
@@ -195,7 +192,7 @@ class ShowDiscussionController extends AbstractShowController
|
||||
$post->discussion = $discussion;
|
||||
}
|
||||
|
||||
$this->loadRelations($posts, $include);
|
||||
$this->loadRelations($posts, $include, $request);
|
||||
|
||||
return $posts->all();
|
||||
}
|
||||
@@ -221,8 +218,13 @@ class ShowDiscussionController extends AbstractShowController
|
||||
|
||||
$postCallableRelationships = $this->getPostRelationships(array_keys($addedCallableRelations));
|
||||
|
||||
return array_intersect_key($addedCallableRelations, array_flip(array_map(function ($relation) {
|
||||
$relationCallables = array_intersect_key($addedCallableRelations, array_flip(array_map(function ($relation) {
|
||||
return "posts.$relation";
|
||||
}, $postCallableRelationships)));
|
||||
|
||||
// remove posts. prefix from keys
|
||||
return array_combine(array_map(function ($relation) {
|
||||
return substr($relation, 6);
|
||||
}, array_keys($relationCallables)), array_values($relationCallables));
|
||||
}
|
||||
}
|
||||
|
@@ -12,6 +12,7 @@ namespace Flarum\Api\Controller;
|
||||
use Flarum\Api\Serializer\PostSerializer;
|
||||
use Flarum\Http\RequestUtil;
|
||||
use Flarum\Post\PostRepository;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Psr\Http\Message\ServerRequestInterface;
|
||||
use Tobscure\JsonApi\Document;
|
||||
@@ -52,6 +53,12 @@ class ShowPostController extends AbstractShowController
|
||||
*/
|
||||
protected function data(ServerRequestInterface $request, Document $document)
|
||||
{
|
||||
return $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request));
|
||||
$post = $this->posts->findOrFail(Arr::get($request->getQueryParams(), 'id'), RequestUtil::getActor($request));
|
||||
|
||||
$include = $this->extractInclude($request);
|
||||
|
||||
$this->loadRelations(new Collection([$post]), $include, $request);
|
||||
|
||||
return $post;
|
||||
}
|
||||
}
|
||||
|
@@ -9,9 +9,11 @@
|
||||
|
||||
namespace Flarum\Database;
|
||||
|
||||
use Flarum\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model as Eloquent;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use LogicException;
|
||||
|
||||
/**
|
||||
@@ -61,6 +63,14 @@ abstract class AbstractModel extends Eloquent
|
||||
*/
|
||||
public static $defaults = [];
|
||||
|
||||
/**
|
||||
* An alias for the table name, used in queries.
|
||||
*
|
||||
* @var string|null
|
||||
* @internal
|
||||
*/
|
||||
protected $tableAlias = null;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -213,4 +223,41 @@ abstract class AbstractModel extends Eloquent
|
||||
|
||||
return parent::__call($method, $arguments);
|
||||
}
|
||||
|
||||
public function newModelQuery()
|
||||
{
|
||||
$query = parent::newModelQuery();
|
||||
|
||||
if ($this->tableAlias) {
|
||||
$query->from($this->getTable().' as '.$this->tableAlias);
|
||||
}
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
public function qualifyColumn($column)
|
||||
{
|
||||
if (Str::contains($column, '.')) {
|
||||
return $column;
|
||||
}
|
||||
|
||||
return ($this->tableAlias ?? $this->getTable()).'.'.$column;
|
||||
}
|
||||
|
||||
public function withTableAlias(callable $callback)
|
||||
{
|
||||
static $aliasCount = 0;
|
||||
$this->tableAlias = 'flarum_reserved_'.++$aliasCount;
|
||||
|
||||
$result = $callback();
|
||||
|
||||
$this->tableAlias = null;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function newCollection(array $models = [])
|
||||
{
|
||||
return new Collection($models);
|
||||
}
|
||||
}
|
||||
|
50
framework/core/src/Database/Eloquent/Collection.php
Normal file
50
framework/core/src/Database/Eloquent/Collection.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of Flarum.
|
||||
*
|
||||
* For detailed copyright and license information, please view the
|
||||
* LICENSE file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Flarum\Database\Eloquent;
|
||||
|
||||
use Illuminate\Database\Eloquent\Collection as BaseCollection;
|
||||
|
||||
class Collection extends BaseCollection
|
||||
{
|
||||
/**
|
||||
* This is done to prevent conflicts when using visibility scopes.
|
||||
* Without this, we get the following example query when using a visibility scope
|
||||
* and eager loading the count of `mentionedBy`:.
|
||||
*
|
||||
* ```sql
|
||||
* SELECT `id`, (
|
||||
* SELECT count(*)
|
||||
* FROM `posts` AS `laravel_reserved_0`
|
||||
* INNER JOIN `post_mentions_post` ON `laravel_reserved_0`.`id` = `post_mentions_post`.`post_id`
|
||||
* WHERE `posts`.`id` = `post_mentions_post`.`mentions_post_id`
|
||||
* --- ^^^^^^^ this is the problem, visibility scopes always assume the default table name, rather than
|
||||
* --- the Laravel auto-generated alias.
|
||||
*
|
||||
* AND `TYPE` in ('discussionTagged', 'discussionStickied', 'discussionLocked', 'comment', 'discussionRenamed')
|
||||
* ) AS `mentioned_by_count`
|
||||
* FROM `posts`
|
||||
* WHERE `posts`.`id` in (23642)
|
||||
* ```
|
||||
*
|
||||
* So by applying an alias on the parent query, we prevent Laravel from auto aliasing the sub-query.
|
||||
*
|
||||
* @link https://github.com/flarum/framework/pull/3780
|
||||
*/
|
||||
public function loadAggregate($relations, $column, $function = null)
|
||||
{
|
||||
if ($this->isEmpty()) {
|
||||
return $this;
|
||||
}
|
||||
|
||||
return $this->first()->withTableAlias(function () use ($relations, $column, $function) {
|
||||
return parent::loadAggregate($relations, $column, $function);
|
||||
});
|
||||
}
|
||||
}
|
@@ -313,7 +313,7 @@ class ApiController implements ExtenderInterface
|
||||
* 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
|
||||
* @param array|(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.
|
||||
@@ -322,7 +322,7 @@ class ApiController implements ExtenderInterface
|
||||
*
|
||||
* @return self
|
||||
*/
|
||||
public function loadWhere(string $relation, callable $callback): self
|
||||
public function loadWhere(string $relation, callable $callback): self // @phpstan-ignore-line
|
||||
{
|
||||
$this->loadCallables = array_merge($this->loadCallables, [$relation => $callback]);
|
||||
|
||||
|
@@ -18,6 +18,7 @@ use Flarum\Post\Event\Deleted;
|
||||
use Flarum\User\User;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Staudenmeir\EloquentEagerLimit\HasEagerLimit;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@@ -42,6 +43,7 @@ class Post extends AbstractModel
|
||||
{
|
||||
use EventGeneratorTrait;
|
||||
use ScopeVisibilityTrait;
|
||||
use HasEagerLimit;
|
||||
|
||||
protected $table = 'posts';
|
||||
|
||||
|
Reference in New Issue
Block a user