i'm working on a graphql API using Laravel GraphQL.
As shown in the documentation "Privacy" section, it should be possible to add callback function to a GraphQLType fields privacy attribute. The field is supposed to return null, when the callback returns false.
Similar to the example in the laravel graphql Docs, i've added a privacy callback like so:
public function fields(): array {
return [
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
];
}
It appears to me, that this callback function never gets called.
I read something about a possible requirement, that i should resolve my query using the $getSelectFields function to query the $fields manually $with the selected columns. But unfortunately the $select
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
$fields = $getSelectFields();
$with = $fields->getRelations(); // empty array
$select = $fields->getSelect(); // empty array
return User::select($select)->with($with)->get();
}
In my case this does not make any difference.
In my query resolver i do as following:
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
/** @var SelectFields $fields */
$fields = $getSelectFields();
$select = $fields->getSelect();
$with = $fields->getRelations();
exit(var_dump($fields)); // #RESULT
}
My result looks like this:
object(Rebing\\GraphQL\\Support\\SelectFields)#4668 (2) {
[\"select\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
[\"relations\":\"Rebing\\GraphQL\\Support\\SelectFields\":private]=> array(0) {}
}
So my question is: "How do i use the privacy attribute callback in Laravel Rebing GraphQL?"
I'm using:
Thanks in advance,
greets Jules
When I look at your query resolver and the "My result looks like this" part, it seems to me that SelectFields isn't doing anything at all; so it's not just the 'privacy'-part 🤔
Did you specify the 'model' on the *Type, i.e. this part from the readme => https://github.com/rebing/graphql-laravel#creating-a-query => https://github.com/rebing/graphql-laravel/blame/03f10e40b68998f74b25d9d640abfddf0d62584e/README.md#L198
If so, please paste your whole query and type.
Alternatively you can also take a look at https://github.com/rebing/graphql-laravel/blob/master/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldsQuery.php and https://github.com/rebing/graphql-laravel/blob/master/tests/Database/SelectFields/ValidateFieldTests/ValidateFieldTest.php for guidance.
Hello @mfn,
thanks for your quick reply and sry for answering so late.
Yes, i specified the model in all my types. In this specific case, the UserType:
EpUser.php
namespace App\GraphQL\Type;
use App\CommunityImage;
use App\User;
use Carbon\Carbon;
use GraphQL\Type\Definition\Type;
use Illuminate\Support\Facades\Auth;
use Rebing\GraphQL\Support\Facades\GraphQL;
use Rebing\GraphQL\Support\Type as GraphQLType;
class EpUser extends GraphQLType {
protected $attributes = [
'name' => 'EpUser',
'description' => 'A type',
'model' => User::class,
];
public function fields(): array {
return [
'id' => [
'type' => Type::nonNull(Type::int()),
'description' => 'The id of the user',
'privacy' => function(User $user): bool {
return false;
}
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'privacy' => function(User $user): bool {
return $user->isMe();
}
],
'firstName' => [
'type' => Type::string(),
'description' => 'The firstName of user'
],
'lastName' => [
'type' => Type::string(),
'description' => 'The lastName of user'
],
'fullName' => [
'type' => Type::string(),
'description' => 'The fullName of user',
'selectable' => false,
'resolve' => function(User $user) {
return $user->firstName . " " . $user->lastName;
}
],
'gender' => [
'type' => Type::string(),
'description' => 'The gender of the user'
],
'isOnline' => [
'type' => Type::boolean(),
'description' => '',
'selectable' => false,
'resolve' => function(User $user, $args) {
return $user->isOnline();
}
]
];
}
[...]
And this is the UsersQuery which should respond with a user pagination object, that contains an array of users with a privacy attribute:
UsersQuery.php
namespace App\GraphQL\Query;
use App\Artist;
use App\FilePath;
use Closure;
use GraphQL\Type\Definition\Type;
use Rebing\GraphQL\Support\Query;
use Illuminate\Support\Facades\Auth;
use GraphQL\Type\Definition\ResolveInfo;
use Rebing\GraphQL\Support\Facades\GraphQL;
use App\User;
class UsersQuery extends Query {
protected $attributes = [
'name' => 'UsersQuery',
'description' => 'A query',
'model' => User::class,
];
public function type(): Type {
return GraphQL::type('userPagination');
}
public function authorize($root, array $args, $ctx, ResolveInfo $resolveInfo = NULL, $getSelectFields = NULL): bool {
return Auth::check();
}
public function args(): array {
return [
'id' => [
'type' => Type::int(),
'description' => 'The id of the user'
],
'slug' => [
'type' => Type::string(),
'description' => 'The slug of the user'
],
'pagination' => [
'type' => Type::nonNull(GraphQL::type('paginationInput')),
'description' => 'The pagination of the users to query',
'rules' => 'required',
],
'search' => [
'type' => Type::string(),
'description' => 'a string to search for users'
],
'roles' => [
'type' => Type::listOf(Type::string()),
'description' => 'The roles of the user',
'rules' => 'sometimes|required|array|in:user,developer,administrator'
]
];
}
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
if(isset($args['id']) || isset($args['slug'])) {
if(isset($args['slug'])) {
$user = User::where('slug', $args['slug'])->first();
} else {
$user = User::find($args['id']);
}
return [
'items' => $args['pagination']['limit'] > 0 && $user ? [$user] : NULL,
'itemTotal' => $user ? 1 : 0
];
}
$sortBy = $args['pagination']['sortBy'] ?? 'id';
$sortByDesc = isset($args['pagination']['sortByDesc']) ? $args['pagination']['sortByDesc'] : true;
$sortByType = $sortByDesc ? 'desc' : 'asc';
$search = false;
if(isset($args['search']) && $args['search']) {
$search = true;
$query = User::search($args['search']);
} else {
$query = User::query();
}
if(!empty($sortBy)) {
$query->orderBy($sortBy, $sortByType);
}
// Todo: eloquent search can't serach for whereHas
if(isset($args['roles']) && !$search) {
if(is_array($args['roles'])) {
foreach($args['roles'] as &$role) {
$query->whereHas('roles',
function($q) use ($role) {
$q->where('name', $role);
});
}
} else {
$query->whereHas('roles',
function($q) use ($args) {
$q->where('name', $args['roles']);
});
}
}
if($search) {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->getWithLimitAndOffset($args['pagination']['limit'],
$args['pagination']['offset'])
];
} else {
$userPaginationObject = [
'itemTotal' => $query->count(),
'items' => $query->limit($args['pagination']['limit'])->offset($args['pagination']['offset'])->get()
];
}
return $userPaginationObject;
}
}
Is there someone who could help me? 👀😊
If there is no way to get the 'privacy' callback working, i would open an issue for this.
Your last example is not using SelectFields, but this feature is solely implemented therein. It can't work otherwise.
Not using SelectFields also means that e.g. the 'model' key is irrelevant.
Do you have an example where you _use_ SelectFields and it doesn't work? As I suggested, this is covered with tests and I recommend to check them out.
OTOH, unless your _actual_ SQL query is very important, with your current code you can just add a custom resolver to achieve the same effect, e.g.
public function fields(): array {
return [
'id' => [
'type' => Type::int(),
'description' => 'The id of the user',
'resolve' => function(User $user): ?int {
return null;
}
],
'email' => [
'type' => Type::string(),
'description' => 'The email of user',
'resolve' => function(User $user): ?string {
if (!$user->email) {
return null;
}
return
$user->isMe()
? $user->email
: null;
}
],
Please note that for email I removed the Type::nonNull, as the point of this (or respectively the privacy check) is to just return null in case the conditions aren't met. Thus you can't use it on such fields.
HTH
@mfn, thanks for you answer!! 😃
You're right. My latest code snippet does not use the SelectFields type.
I've been looking into the documentation of laravel graphql and the privacy section does not say a word about a required usage of the SelectFields for this feature to work.
The fact, that you removed the Type::nonNull makes sense to me 😄
It seems to me, that i did not yet really understand the role of the SelectFields in this context.
What i may have understood is that the SelectFields is doing some magic behind the scenes which involves the actually requested fields inside the gql query coming from the client - right?
So,
public function resolve($root, $args, $context, ResolveInfo $info, Closure $getSelectFields) {
$fields = $getSelectFields();
$with = $fields->getRelations(); // empty array
$select = $fields->getSelect(); // empty array
return User::select($select)->with($with)->get();
}
This snippet is extract of what i entered into the UsersQuery.php resolver shown above. It was just inserted for testing and gave me the described result. (unfortunately i have no experience with automated testing - i'm fighting with my browser output 😆 )
The thing is - in that case my SelectFields does not return the expected value of the requested fields, nor the field itself.
My primary goal is to hide the value of the email adress, when a user does not have permission.
Could you show me a working example of how to use the 'privacy' field - probably in combination with a SelectFields resolver?
Thanks for your help :)
What i may have understood is that the SelectFields is doing some magic behind the scenes which involves the actually requested fields inside the gql query coming from the client
SelectFields tries to be clever in a few ways:
In a nutshell, it is a counter-approach to using e.g. a dataloader.
I've been looking into the documentation of laravel graphql and the privacy section does not say a word about a required usage of the SelectFields for this feature to work.
I agree that the documentation isn't clear about this.
It mixes "raw graphql" (as provided via graphql-php) with the custom configuration options provided by this library and it's unclear what is what and which context requires what 💥
The thing is - in that case my SelectFields does not return the expected value of the requested fields, nor the field itself.
I already tried to help, but it seems we're going circles here. If you still want help here, I recommend you create a self contained reproducible repository (laravel + graphql + sqlite for example) with non-working code and I can offer to take a look. However… (see my next response)
Could you show me a working example of how to use the 'privacy' field - probably in combination with a SelectFields resolver?
There are plenty in the tests! Did you check them out?
If you've custom needs SelectFields cannot solve, there's nothing wrong _not_ using it. You just can't use a few of the fancy things (which is actually hard to figure out what belongs to which…) and need to fall back to pure graphql. Also: nothing wrong with that.
In fact, my last examples using resolve does exactly that and if you've your pagination working correctly, adjusting the resolve to "emulate" the 'privacy' feature should be a easy.
If I may, I also recommend reading up on https://webonyx.github.io/graphql-php/ . This library builds on top of it and doesn't intend to hide that fact, aka it helps a lot to graph how graphql-php works to make efficient use of this library. The best analogy for this I've is, it's not helpful to start out with Laravel if there's no PHP experience 🤷♀️
Thanks for pointing out SelectFields @mfn! This solved the issue in my case. Simply calling the resolver method alone already fixed my problem:
$fields = $getSelectFields();
You don't even have to use the $select and $with that it resolves if you don't want to.
Hopefully this helps others (or future me)!
Glad it worked for you @roelofjan-elsinga
Closing as not having heard back from OP for months