lastdragon-ru / lara-asp-graphql
The Awesome Set of Packages for Laravel - The GraphQL Extensions.
Requires
- php: ^8.0|^8.1|^8.2
- ext-mbstring: *
- laravel/framework: ^9.0.0|^10.0.0
- lastdragon-ru/lara-asp-core: 4.2.1
- lastdragon-ru/lara-asp-eloquent: 4.2.1
- lastdragon-ru/lara-asp-graphql-printer: 4.2.1
- nuwave/lighthouse: ^6.0.0
- phpdocumentor/reflection-docblock: ^5.2
Requires (Dev)
- ext-pdo_sqlite: *
- laravel/scout: ^9.2.1|^10.0.0
- lastdragon-ru/lara-asp-testing: 4.2.1
- mockery/mockery: ^1.4.2
- orchestra/testbench: ^6.9.0|^7.0.0|^8.0.0
- phpunit/phpunit: ^9.5.0|^10.1.0
Suggests
- ext-fileinfo: Required for league/flysystem
- laravel/scout: Required for the @sortBy directive to work with Scout Builder.
This package is auto-updated.
Last update: 2023-05-15 06:55:15 UTC
README
This package is the part of Awesome Set of Packages for Laravel.
This package provides highly powerful @searchBy
and @sortBy
directives for lighthouse-php. The @searchBy
directive provides basic conditions like =
, >
, <
, etc, relations, not (<condition>)
, enums, and custom operators support. All are strictly typed so you no need to use Mixed
type anymore. The @sortBy
is not only about standard sorting by columns but also allows use relations. 😎
Installation
composer require lastdragon-ru/lara-asp-graphql
Configuration
Config can be used, for example, to customize supported operators for each type. Before this, you need to publish it via the following command, and then you can edit config/lara-asp-graphql.php
.
php artisan vendor:publish --provider=LastDragon_ru\\LaraASP\\GraphQL\\Provider --tag=config
@searchBy
directive
At this moment this is probably the most powerful directive to provide search (where
conditions) for your GraphQL queries.
Basic usage
Out the box directives provides following features:
- Strictly typed - you can define supported operators for each Scalar;
- Eloquent Builder, Query Builder and Custom Builder support;
- Support almost all
where
operators; - Enums support;
not (<condition>)
support;- Relations support (Eloquent Builder only);
- Custom operators support
- easy to use and safe.
Let's start:
scalar Date @scalar(class: "Nuwave\\Lighthouse\\Schema\\Types\\Scalars\\Date") type Query { users(where: _ @searchBy): [User!]! @all comments(where: CommentsQuery @searchBy): [Comment!]! @all } input UsersQuery { id: ID! name: String! } input CommentsQuery { text: String! user: UsersQuery date: Date } type User { id: ID! name: String! } input Comment { text: String! user: User date: Date }
That's all, just search 😃 (or look at generated GraphQL schema)
query { # WHERE name = "LastDragon" users(where: { name: {equal: "LastDragon"} }) { id } # WHERE name != "LastDragon" users(where: { name: {notEqual: "LastDragon"} }) { id } # WHERE name = "LastDragon" or name = "Aleksei" users(where: { anyOf: [ {name: {equal: "LastDragon"}} {name: {equal: "Aleksei"}} ] }) { id } # WHERE NOT (name = "LastDragon" or name = "Aleksei") users(where: { not: { anyOf: [ {name: {equal: "LastDragon"}} {name: {equal: "Aleksei"}} ] } }) { id } # WHERE date IS NULL users(where: { date: {isNull: yes} }) { id } # Relation: WHERE EXIST (related table) comments(where: { user: { where: { date: {between: {min: "2021-01-01", max: "2021-04-01"}} } } }) { id } # Relation: WHERE COUNT (related table) = 2 comments(where: { user: { where: { date: {between: {min: "2021-01-01", max: "2021-04-01"}} } count: { equal: 2 } } }) { id } }
Input type auto-generation
As you can see in the example above you can use the special placeholder _
instead of real input
. In this case, @searchBy
will generate input
automatically by the actual type
of the query. While converting type
into input
following fields will be excluded:
- unions
- with
@field
directive - with
@searchByIgnored
directive - with any directive that implements
Ignored
- any
Type
that implementsIgnored
Operators
There are three types of operators:
-
Comparison - used to compare column with value(s), eg
{equal: "value"}
,{lt: 2}
, etc. To add your own you just need to implementOperator
and add it to type(s); -
Extra - used to add additional fields, by default package provides few Logical operators which allow you to do eg
anyOf([{equal: "a"}, {equal: "b"}])
. Adding your own is the same: implementOperator
and add it toOperators::Extra
type; -
Condition - used to create conditions for nested Input types and allow implement any logic eg
whereHas
,whereDoesntHave
, etc. All the same, but these operators should be explicitly added to the fields/input types, by default theRelation
operator will be used:type Query { users(where: UsersQuery @searchBy): ID! @all comments(where: CommentsQuery @searchBy): ID! @all } input UsersQuery { id: ID! name: String! } input CommentsQuery { text: String! user: UsersQuery @myComplexOperator }
By default, the package provide list of predefined operators for each build-in type. To extend/replace the list, you can use config and/or add directives to scalar/enum inside the schema. Directives is the recommended way and have priority over the config.
The package also defines a few own types in addition to the standard GraphQL types:
SearchByCondition
/Operators::Condition
- List of known Condition operators. If no directive is found, the first supported operator from the list will be used.SearchByNumber
/Operators::Number
- Any operator for this type will be available forInt
andFloat
.SearchByNull
/Operators::Null
- Additional operators available for nullable fields.SearchByExtra
/Operators::Extra
- List of additional extra operators for all types.SearchByEnum
/Operators::Enum
- Default operators for enums.
<?php declare(strict_types = 1); use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByOperatorBetweenDirective; use LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByOperatorEqualDirective; /** * ----------------------------------------------------------------------------- * GraphQL Settings * ----------------------------------------------------------------------------- * * @var array{ * search_by: array{ * operators: array<string, array<string|class-string<Operator>>> * } * } $settings */ $settings = [ /** * Settings for {@see \LastDragon_ru\LaraASP\GraphQL\SearchBy\Definitions\SearchByDirective @searchBy} directive. */ 'search_by' => [ /** * Operators * --------------------------------------------------------------------- * * You can (re)define types and supported operators here. * * @see Operator */ 'operators' => [ // You can define a list of operators for each type 'Date' => [ SearchByOperatorEqualDirective::class, SearchByOperatorBetweenDirective::class, MyCustomOperator::class, ], // Or re-use existing type 'DateTime' => [ 'Date', ], // You can also use enum name to redefine default operators for it: 'MyEnum' => [ 'Boolean', ], ], ], ]; return $settings;
# Only two operators will be available. enum MyEnum @searchByOperatorEqual @searchByOperatorNotEqual { Enabled Disabled } # All operators from `Int` type and `@myOperator` will be available. scalar MyScalar @searchByOperators(type: "Int") @myOperator
@sortBy
directive
Basic usage
How to use (and generated GraphQL schema):
type Query { "You can use normal input type" users(order: UsersSort @sortBy): ID! @all "or `_` to generate type automatically 😛" comments(order: _ @sortBy): [Comment!]! @all } input UsersSort { id: ID! name: String! } type Comment { text: String user: User } type User { id: ID! name: String! }
And:
query { # ORDER BY user.name ASC, text DESC comments(order: [ {user: {name: asc}} {text: desc} ]) }
Input type auto-generation
As you can see in the example above you can use the special placeholder _
instead of real input
. In this case, @sortBy
will generate input
automatically by the actual type
of the query. While converting type
into input
following fields will be excluded:
- unions
- with list/array type
- with
@field
directive - with
@sortByIgnored
directive - with any directive that implements
Ignored
- any
Type
that implementsIgnored
Operators
The package defines only one's own type. To extend/replace the list of its operators, you can use config and/or add directives to scalar/enum inside the schema. Directives is the recommended way and have priority over the config.
SortByExtra
/Operators::Extra
- List of additional extra operators for all types. The list is empty by default.
Eloquent/Database
Supported Relations
The main feature - the ability to sort results by relation properties, at the moment supported the following relation types:
HasOne
(https://laravel.com/docs/eloquent-relationships#one-to-one)HasMany
(https://laravel.com/docs/eloquent-relationships#one-to-many)HasManyThrough
(https://laravel.com/docs/eloquent-relationships#has-many-through)BelongsTo
(https://laravel.com/docs/eloquent-relationships#one-to-many-inverse)BelongsToMany
(https://laravel.com/docs/eloquent-relationships#many-to-many)MorphOne
(https://laravel.com/docs/eloquent-relationships#one-of-many-polymorphic-relations)MorphMany
(https://laravel.com/docs/eloquent-relationships#one-to-many-polymorphic-relations)MorphToMany
(https://laravel.com/docs/eloquent-relationships#many-to-many-polymorphic-relations)HasOneThrough
(https://laravel.com/docs/eloquent-relationships#has-one-through)
Order by random
It is also possible to sort records in random order, but it is not enabled by default. To enable it you just need to add Random
/@sortByOperatorRandom
operator/directive to Extra
type:
<?php declare(strict_types = 1); use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Operator; use LastDragon_ru\LaraASP\GraphQL\SortBy\Operators as SortByOperators; use LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByOperatorRandomDirective; /** * ----------------------------------------------------------------------------- * GraphQL Settings * ----------------------------------------------------------------------------- * * @var array{ * sort_by: array{ * operators: array<string, array<string|class-string<Operator>>> * }, * } $settings */ $settings = [ /** * Settings for {@see \LastDragon_ru\LaraASP\GraphQL\SortBy\Definitions\SortByDirective @sortBy} directive. */ 'sort_by' => [ /** * Operators * --------------------------------------------------------------------- * * You can (re)define types and supported operators here. * * @see Operator */ 'operators' => [ SortByOperators::Extra => [ SortByOperatorRandomDirective::class, ], ], ], ]; return $settings;
or
scalar SortByExtra @sortByOperatorRandom
And after this, you can 🎉
query { # ORDER BY RANDOM() comments(order: [ {random: yes} ]) }
Scout
Scout is also supported 🤩. By default @searchBy
/@sortBy
will convert nested/related properties into dot string: eg {user: {name: asc}}
will be converted into user.name
. You can redefine this behavior by FieldResolver
:
// AppProvider $this->app->bind( LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\Scout\FieldResolver::class, MyScoutColumnResolver::class, );
Relations
Important note about relations: they must have proper type-hint, so:
<?php declare(strict_types = 1); namespace App\Models; use Illuminate\Database\Eloquent\Relations\BelongsTo; class Comment extends Model { protected $table = 'comments'; /** * Will NOT work */ public function user() { return $this->belongsTo(User::class); } /** * Must be */ public function user(): BelongsTo { return $this->belongsTo(User::class); } }
Builder type detection
Directives like @searchBy
/@sortBy
may have a unique set of operators for each type of Builder (Eloquent/Scout/etc). Detection of the current Builder works fine for standard Lighthouse directives like @all
, @paginated
, @search
, etc in most cases (in others cases you probably just need to check/specify proper type hint the same as for relations). But if you implement custom directives which internally enhance the Builder (like standard directives do), you may get BuilderUnknown
error because the proper/expected builder type was not detected. In this case, your directive should implement BuilderInfoProvider
interface and to specify the builder type explicitly.
<?php declare(strict_types = 1); namespace App\GraphQL\Directives; use Illuminate\Database\Eloquent\Builder; use LastDragon_ru\LaraASP\GraphQL\Builder\BuilderInfo; use LastDragon_ru\LaraASP\GraphQL\Builder\Contracts\BuilderInfoProvider; use Nuwave\Lighthouse\Support\Contracts\Directive; class CustomDirective implements Directive, BuilderInfoProvider { public static function definition(): string { return 'directive @custom'; } public function getBuilderInfo(): BuilderInfo|string { return Builder::class; } public function __invoke(): mixed { // TODO: Implement __invoke() method. } }
Printer
The package provides bindings for Printer
so you can simply use:
<?php declare(strict_types = 1); use Illuminate\Container\Container; use LastDragon_ru\LaraASP\GraphQLPrinter\Printer; use Nuwave\Lighthouse\Schema\SchemaBuilder; $schema = Container::getInstance()->make(SchemaBuilder::class)->schema(); $printer = Container::getInstance()->make(Printer::class); $printed = $printer->printSchema($schema);
There are also few great new GraphQL Assertions.
Name | Description |
---|---|
assertDefaultGraphQLSchemaEquals |
Compares default schema. |