mirror of
https://github.com/AnimeThemes/animethemes-server.git
synced 2026-07-11 01:24:46 +02:00
feat(graphql): sort pivot fields (#1003)
This commit is contained in:
@@ -11,7 +11,7 @@ use App\GraphQL\Support\ResolveInfo as CustomResolveInfo;
|
||||
use GraphQL\Type\Definition\ResolveInfo;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation as RelationLaravel;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation as EloquentRelation;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait ConstrainsEagerLoads
|
||||
@@ -57,13 +57,13 @@ trait ConstrainsEagerLoads
|
||||
|
||||
$relationType = $relation->getBaseType();
|
||||
|
||||
$eagerLoadRelations[$path] = function (RelationLaravel $relationLaravel) use ($relationSelection, $relationArgs, $relationType): void {
|
||||
if ($relationLaravel instanceof MorphTo) {
|
||||
$this->processMorphToRelation($relationSelection, $relationType, $relationLaravel);
|
||||
$eagerLoadRelations[$path] = function (EloquentRelation $eloquentRelation) use ($relationSelection, $relationArgs, $relationType, $relation): void {
|
||||
if ($eloquentRelation instanceof MorphTo) {
|
||||
$this->processMorphToRelation($relationSelection, $relationType, $eloquentRelation);
|
||||
} elseif ($relationType instanceof BaseUnion) {
|
||||
$this->processUnion($relationSelection, $relationLaravel, $relationType);
|
||||
$this->processUnion($relationSelection, $eloquentRelation, $relationType);
|
||||
} else {
|
||||
$this->processGenericRelation($relationLaravel, $relationArgs, $relationSelection, $relationType);
|
||||
$this->processGenericRelation($eloquentRelation, $relationArgs, $relationSelection, $relationType, $relation);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -96,9 +96,9 @@ trait ConstrainsEagerLoads
|
||||
/**
|
||||
* Process a union relation by applying the eager loads for each type in the union.
|
||||
*/
|
||||
private function processUnion(array $selection, RelationLaravel $relation, BaseUnion $union): void
|
||||
private function processUnion(array $selection, EloquentRelation $relation, BaseUnion $union): void
|
||||
{
|
||||
$query = $relation->getQuery();
|
||||
$builder = $relation->getQuery();
|
||||
|
||||
$unions = Arr::get($selection, 'selectionSet.data.data.unions', []);
|
||||
|
||||
@@ -109,25 +109,25 @@ trait ConstrainsEagerLoads
|
||||
foreach ($types as $type) {
|
||||
$typeSelection = Arr::get($unions, "{$type->getName()}.selectionSet", []);
|
||||
|
||||
$this->processEagerLoadForType($query, $typeSelection, $type);
|
||||
$this->processEagerLoadForType($builder, $typeSelection, $type);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a generic relation by applying filters, sorting and eager loads.
|
||||
*/
|
||||
private function processGenericRelation(RelationLaravel $relation, array $args, array $selection, BaseType $type): void
|
||||
private function processGenericRelation(EloquentRelation $relation, array $args, array $selection, BaseType $type, Relation $graphqlRelation): void
|
||||
{
|
||||
$query = $relation->getQuery();
|
||||
$builder = $relation->getQuery();
|
||||
|
||||
$this->filter($query, $args, $type);
|
||||
$this->filter($builder, $args, $type);
|
||||
|
||||
$this->sort($query, $args, $type);
|
||||
$this->sort($builder, $args, $type, $relation, $graphqlRelation);
|
||||
|
||||
$child = Arr::get($selection, 'selectionSet.data.data.selectionSet')
|
||||
$fields = Arr::get($selection, 'selectionSet.data.data.selectionSet')
|
||||
?? Arr::get($selection, 'selectionSet.nodes.nodes.selectionSet')
|
||||
?? Arr::get($selection, 'selectionSet', []);
|
||||
|
||||
$this->processEagerLoadForType($query, $child, $type);
|
||||
$this->processEagerLoadForType($builder, $fields, $type);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,15 @@ use App\GraphQL\Schema\Enums\SortableColumns;
|
||||
use App\GraphQL\Schema\Types\BaseType;
|
||||
use App\GraphQL\Schema\Unions\BaseUnion;
|
||||
use App\GraphQL\Support\Argument\SortArgument;
|
||||
use App\GraphQL\Support\Relations\Relation as GraphQLRelation;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Support\Arr;
|
||||
|
||||
trait SortsModels
|
||||
{
|
||||
public function sort(Builder $builder, array $args, BaseType|BaseUnion $type): Builder
|
||||
public function sort(Builder $builder, array $args, BaseType|BaseUnion $type, ?Relation $relation = null, ?GraphQLRelation $graphqlRelation = null): Builder
|
||||
{
|
||||
$sorts = Arr::get($args, SortArgument::ARGUMENT);
|
||||
|
||||
@@ -22,7 +25,11 @@ trait SortsModels
|
||||
return $builder;
|
||||
}
|
||||
|
||||
$criterias = Arr::get(new SortableColumns($type)->getAttributes(), 'criterias');
|
||||
$relation = $relation instanceof BelongsToMany
|
||||
? $relation
|
||||
: null;
|
||||
|
||||
$criterias = Arr::get(new SortableColumns($type, $graphqlRelation?->getPivotType(), $relation)->getAttributes(), 'criterias');
|
||||
|
||||
foreach ($sorts as $sort) {
|
||||
/** @var SortCriteria $criteria */
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Criteria\Sort;
|
||||
|
||||
use App\Contracts\GraphQL\Fields\SortableField;
|
||||
use App\Enums\GraphQL\SortDirection;
|
||||
use App\GraphQL\Schema\Fields\Field;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class PivotSortCriteria extends SortCriteria
|
||||
{
|
||||
public function __construct(
|
||||
protected Field&SortableField $field,
|
||||
protected SortDirection $direction = SortDirection::ASC,
|
||||
protected ?BelongsToMany $relation = null,
|
||||
) {
|
||||
parent::__construct($field, $direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* Build the enum case for a direction.
|
||||
* Template: PIVOT_{COLUMN}.
|
||||
* Template: PIVOT_{COLUMN}_DESC.
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return (string) match ($this->direction) {
|
||||
SortDirection::ASC => Str::of($this->field->getName())->snake()->upper()->prepend('PIVOT_'),
|
||||
SortDirection::DESC => Str::of($this->field->getName())->snake()->upper()->prepend('PIVOT_')->append('_DESC'),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply the ordering to the current Eloquent builder.
|
||||
*/
|
||||
public function sort(Builder $builder): Builder
|
||||
{
|
||||
return $builder->orderBy(
|
||||
$this->relation?->qualifyPivotColumn($this->field->getColumn()),
|
||||
$this->direction->value
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,13 +10,16 @@ use App\Enums\GraphQL\SortType;
|
||||
use App\GraphQL\Criteria\Sort\AggregateSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\CountSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\FieldSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\PivotSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\RandomSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\RelationSortCriteria;
|
||||
use App\GraphQL\Criteria\Sort\SortCriteria;
|
||||
use App\GraphQL\Schema\Fields\Field;
|
||||
use App\GraphQL\Schema\Types\BaseType;
|
||||
use App\GraphQL\Schema\Types\Pivot\PivotType;
|
||||
use App\GraphQL\Support\Relations\BelongsToRelation;
|
||||
use App\GraphQL\Support\Relations\Relation;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Support\Collection;
|
||||
use Rebing\GraphQL\Support\EnumType;
|
||||
|
||||
@@ -27,15 +30,23 @@ class SortableColumns extends EnumType
|
||||
{
|
||||
final public const string SUFFIX = 'SortableColumns';
|
||||
|
||||
public function __construct(protected BaseType $type) {}
|
||||
public function __construct(
|
||||
protected BaseType $type,
|
||||
protected ?PivotType $pivotType = null,
|
||||
protected ?BelongsToMany $relation = null,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function attributes(): array
|
||||
{
|
||||
$name = $this->pivotType instanceof PivotType
|
||||
? $this->pivotType->getName()
|
||||
: $this->type->getName();
|
||||
|
||||
return [
|
||||
'name' => $this->type->getName().self::SUFFIX,
|
||||
'name' => $name.self::SUFFIX,
|
||||
'values' => $this->getCriterias()->mapWithKeys(fn (SortCriteria $sort): array => [$sort->__toString() => $sort->__toString()])->toArray(),
|
||||
'criterias' => $this->getCriterias()->mapWithKeys(fn (SortCriteria $sort): array => [$sort->__toString() => $sort])->toArray(),
|
||||
];
|
||||
@@ -53,6 +64,7 @@ class SortableColumns extends EnumType
|
||||
private function getCriterias(): Collection
|
||||
{
|
||||
return $this->getFieldSortCriterias()
|
||||
->merge($this->getPivotSortCriterias())
|
||||
->merge($this->getCountSortCriterias())
|
||||
->merge($this->getAggregateSortCriterias())
|
||||
->merge($this->getRelationSortCriterias())
|
||||
@@ -63,7 +75,21 @@ class SortableColumns extends EnumType
|
||||
{
|
||||
return $this->getSortableFields()
|
||||
->filter(fn (Field $field): bool => $field->sortType() === SortType::ROOT)
|
||||
->map(fn (Field $field): array => [new FieldSortCriteria($field), new FieldSortCriteria($field, SortDirection::DESC)])
|
||||
->map(fn (Field $field): array => [
|
||||
new FieldSortCriteria($field),
|
||||
new FieldSortCriteria($field, SortDirection::DESC),
|
||||
])
|
||||
->flatten();
|
||||
}
|
||||
|
||||
private function getPivotSortCriterias(): Collection
|
||||
{
|
||||
return collect($this->pivotType?->fieldClasses() ?? [])
|
||||
->filter(fn (Field $field): bool => $field instanceof SortableField)
|
||||
->map(fn (Field $field): array => [
|
||||
new PivotSortCriteria($field, SortDirection::ASC, $this->relation),
|
||||
new PivotSortCriteria($field, SortDirection::DESC, $this->relation),
|
||||
])
|
||||
->flatten();
|
||||
}
|
||||
|
||||
@@ -71,7 +97,10 @@ class SortableColumns extends EnumType
|
||||
{
|
||||
return $this->getSortableFields()
|
||||
->filter(fn (Field $field): bool => $field->sortType() === SortType::COUNT_RELATION)
|
||||
->map(fn (Field $field): array => [new CountSortCriteria($field), new CountSortCriteria($field, SortDirection::DESC)])
|
||||
->map(fn (Field $field): array => [
|
||||
new CountSortCriteria($field),
|
||||
new CountSortCriteria($field, SortDirection::DESC),
|
||||
])
|
||||
->flatten();
|
||||
}
|
||||
|
||||
@@ -79,7 +108,10 @@ class SortableColumns extends EnumType
|
||||
{
|
||||
return $this->getSortableFields()
|
||||
->filter(fn (Field $field): bool => $field->sortType() === SortType::AGGREGATE)
|
||||
->map(fn (Field $field): array => [new AggregateSortCriteria($field), new AggregateSortCriteria($field, SortDirection::DESC)])
|
||||
->map(fn (Field $field): array => [
|
||||
new AggregateSortCriteria($field),
|
||||
new AggregateSortCriteria($field, SortDirection::DESC),
|
||||
])
|
||||
->flatten();
|
||||
}
|
||||
|
||||
|
||||
@@ -6,6 +6,7 @@ namespace App\GraphQL\Support\Argument;
|
||||
|
||||
use App\GraphQL\Schema\Enums\SortableColumns;
|
||||
use App\GraphQL\Schema\Types\BaseType;
|
||||
use App\GraphQL\Schema\Types\Pivot\PivotType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Illuminate\Support\Arr;
|
||||
use Rebing\GraphQL\Support\Facades\GraphQL;
|
||||
@@ -14,9 +15,11 @@ class SortArgument extends Argument
|
||||
{
|
||||
final public const string ARGUMENT = 'sort';
|
||||
|
||||
public function __construct(protected BaseType $type)
|
||||
public function __construct(protected BaseType $type, ?PivotType $pivotType = null)
|
||||
{
|
||||
$sortableColumns = new SortableColumns($type);
|
||||
$sortableColumns = new SortableColumns($type, $pivotType);
|
||||
|
||||
GraphQL::addType($sortableColumns);
|
||||
|
||||
$name = Arr::get($sortableColumns->getAttributes(), 'name');
|
||||
|
||||
|
||||
@@ -8,6 +8,9 @@ use App\Enums\GraphQL\PaginationType;
|
||||
use App\GraphQL\Schema\Types\ConnectionType;
|
||||
use App\GraphQL\Schema\Types\EdgeType;
|
||||
use App\GraphQL\Schema\Types\EloquentType;
|
||||
use App\GraphQL\Schema\Types\Pivot\PivotType;
|
||||
use App\GraphQL\Support\Argument\Argument;
|
||||
use App\GraphQL\Support\Argument\SortArgument;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Rebing\GraphQL\Support\Facades\GraphQL;
|
||||
|
||||
@@ -20,7 +23,7 @@ class BelongsToManyRelation extends Relation
|
||||
protected EloquentType $ownerType,
|
||||
protected string $nodeType,
|
||||
protected string $relationName,
|
||||
protected ?string $pivotType = null,
|
||||
protected PivotType|string|null $pivotType = null,
|
||||
) {
|
||||
$this->edgeType = new EdgeType($ownerType, $nodeType, $pivotType);
|
||||
$this->connectionType = new ConnectionType($this->edgeType);
|
||||
@@ -28,6 +31,28 @@ class BelongsToManyRelation extends Relation
|
||||
GraphQL::addType($this->connectionType, $this->connectionType->getName());
|
||||
|
||||
parent::__construct(new $nodeType, $relationName);
|
||||
|
||||
$this->pivotType = class_exists($pivotType ?? '') ? new $pivotType : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the arguments of the sub-query.
|
||||
*
|
||||
* @return Argument[]
|
||||
*/
|
||||
protected function arguments(): array
|
||||
{
|
||||
$pivotType = $this->pivotType;
|
||||
|
||||
if ($pivotType instanceof PivotType) {
|
||||
return [
|
||||
...parent::arguments(),
|
||||
|
||||
new SortArgument($this->baseType, $pivotType),
|
||||
];
|
||||
}
|
||||
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -38,6 +63,14 @@ class BelongsToManyRelation extends Relation
|
||||
return $this->edgeType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot type of the belongs to many relationship.
|
||||
*/
|
||||
public function getPivotType(): ?PivotType
|
||||
{
|
||||
return $this->pivotType;
|
||||
}
|
||||
|
||||
public function type(): Type
|
||||
{
|
||||
return Type::nonNull(GraphQL::type($this->connectionType->getName()));
|
||||
|
||||
@@ -4,36 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Support\Relations;
|
||||
|
||||
use App\Enums\GraphQL\PaginationType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class HasOneRelation extends Relation
|
||||
{
|
||||
public function type(): Type
|
||||
{
|
||||
if (! $this->nullable) {
|
||||
return Type::nonNull($this->type);
|
||||
}
|
||||
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pagination type if applicable.
|
||||
*/
|
||||
public function paginationType(): PaginationType
|
||||
{
|
||||
return PaginationType::NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the relation.
|
||||
*
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function resolve(Model $root, array $args): mixed
|
||||
{
|
||||
return $root->{$this->relationName};
|
||||
}
|
||||
}
|
||||
class HasOneRelation extends BelongsToRelation {}
|
||||
|
||||
@@ -4,22 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Support\Relations;
|
||||
|
||||
use App\Enums\GraphQL\PaginationType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Rebing\GraphQL\Support\Facades\GraphQL;
|
||||
|
||||
class MorphManyRelation extends Relation
|
||||
{
|
||||
public function type(): Type
|
||||
{
|
||||
return Type::nonNull(Type::listOf(Type::nonNull(GraphQL::type($this->baseType->getName()))));
|
||||
}
|
||||
|
||||
/**
|
||||
* The pagination type if applicable.
|
||||
*/
|
||||
public function paginationType(): PaginationType
|
||||
{
|
||||
return PaginationType::SIMPLE;
|
||||
}
|
||||
}
|
||||
class MorphManyRelation extends HasManyRelation {}
|
||||
|
||||
@@ -4,50 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Support\Relations;
|
||||
|
||||
use App\Enums\GraphQL\PaginationType;
|
||||
use App\GraphQL\Schema\Types\ConnectionType;
|
||||
use App\GraphQL\Schema\Types\EdgeType;
|
||||
use App\GraphQL\Schema\Types\EloquentType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Rebing\GraphQL\Support\Facades\GraphQL;
|
||||
|
||||
class MorphToManyRelation extends Relation
|
||||
{
|
||||
protected EdgeType $edgeType;
|
||||
protected ConnectionType $connectionType;
|
||||
|
||||
public function __construct(
|
||||
protected EloquentType $ownerType,
|
||||
protected string $nodeType,
|
||||
protected string $relationName,
|
||||
protected ?string $pivotType = null,
|
||||
) {
|
||||
$this->edgeType = new EdgeType($ownerType, $nodeType, $pivotType);
|
||||
$this->connectionType = new ConnectionType($this->edgeType);
|
||||
|
||||
GraphQL::addType($this->connectionType, $this->connectionType->getName());
|
||||
|
||||
parent::__construct(new $nodeType, $relationName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the edge type of the belongs to many relationship.
|
||||
*/
|
||||
public function getEdgeType(): EdgeType
|
||||
{
|
||||
return $this->edgeType;
|
||||
}
|
||||
|
||||
public function type(): Type
|
||||
{
|
||||
return Type::nonNull(GraphQL::type($this->connectionType->getName()));
|
||||
}
|
||||
|
||||
/**
|
||||
* The pagination type if applicable.
|
||||
*/
|
||||
public function paginationType(): PaginationType
|
||||
{
|
||||
return PaginationType::CONNECTION;
|
||||
}
|
||||
}
|
||||
class MorphToManyRelation extends BelongsToManyRelation {}
|
||||
|
||||
@@ -4,36 +4,4 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\GraphQL\Support\Relations;
|
||||
|
||||
use App\Enums\GraphQL\PaginationType;
|
||||
use GraphQL\Type\Definition\Type;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class MorphToRelation extends Relation
|
||||
{
|
||||
public function type(): Type
|
||||
{
|
||||
if (! $this->nullable) {
|
||||
return Type::nonNull($this->type);
|
||||
}
|
||||
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* The pagination type if applicable.
|
||||
*/
|
||||
public function paginationType(): PaginationType
|
||||
{
|
||||
return PaginationType::NONE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the relation.
|
||||
*
|
||||
* @param array<string, mixed> $args
|
||||
*/
|
||||
public function resolve(Model $root, array $args): mixed
|
||||
{
|
||||
return $root->{$this->relationName};
|
||||
}
|
||||
}
|
||||
class MorphToRelation extends BelongsToRelation {}
|
||||
|
||||
@@ -9,6 +9,7 @@ use App\Concerns\GraphQL\ResolvesArguments;
|
||||
use App\Enums\GraphQL\PaginationType;
|
||||
use App\GraphQL\Schema\Fields\Base\DeletedAtField;
|
||||
use App\GraphQL\Schema\Types\BaseType;
|
||||
use App\GraphQL\Schema\Types\Pivot\PivotType;
|
||||
use App\GraphQL\Schema\Unions\BaseUnion;
|
||||
use App\GraphQL\Support\Argument\Argument;
|
||||
use App\GraphQL\Support\Argument\FirstArgument;
|
||||
@@ -106,28 +107,19 @@ abstract class Relation
|
||||
return Arr::flatten($arguments);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public function args(): array
|
||||
{
|
||||
return collect($this->arguments())
|
||||
->mapWithKeys(fn (Argument $argument): array => [
|
||||
$argument->name => [
|
||||
'name' => $argument->name,
|
||||
'type' => $argument->getType(),
|
||||
|
||||
...(is_null($argument->getDefaultValue()) ? [] : ['defaultValue' => $argument->getDefaultValue()]),
|
||||
],
|
||||
])
|
||||
->toArray();
|
||||
}
|
||||
|
||||
public function getBaseType(): BaseType|BaseUnion
|
||||
{
|
||||
return $this->baseType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the pivot type if it exists.
|
||||
*/
|
||||
public function getPivotType(): ?PivotType
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the relation.
|
||||
*
|
||||
|
||||
@@ -142,8 +142,7 @@ class Song extends BaseModel implements HasResources, SoftDeletable
|
||||
*/
|
||||
public function performances(): HasMany
|
||||
{
|
||||
return $this->hasMany(Performance::class, Performance::ATTRIBUTE_SONG)
|
||||
->orderBy(Performance::ATTRIBUTE_RELEVANCE);
|
||||
return $this->hasMany(Performance::class, Performance::ATTRIBUTE_SONG);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user