-
Notifications
You must be signed in to change notification settings - Fork 43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Filtering data by relationship fields #54
Comments
Hi! Thanks, glad you like the package! So my advice with this is always: write the query as an Eloquent builder query before working out what the filter should be So in your example, title and author id query would be: Post::query()->where('title', $title)->where('author_id', $authorId) So that's very simple to implement using the This works for author because the So if we wanted to find Post::query()->whereHas('comments', function ($query) use ($userId) {
$query->where('user_id', $userId);
}); So for that you'd need to write a JSON:API filter that calls the |
Hey @lindyhopchris , thanks for the response! Yeah, the author id was a bad example 😅 , better using author name. The whereHas seems to be the way to go! I'll try it! I'll start simple for now, but later on I was thinking of making a filter that receives the relationship schema and builds the filter reusing the available filters set on the schema, so I don't need to rewrite it (unless I want it to override it or something). It would be nice if I could reuse the validation structure that validates accepted filters. What do you think , is that viable and a good idea ? |
Yes, I definitely think a filter that allows you to use I think probably three filters need to be added:
I'm really pushed at the moment - spending an incredible amount of time on this package and need to crack on with real work! Any chance you'd have time to take a look at implementing |
Yep, I'll take a look at the structure in the next few days and try implementing it and get back to you with a proof of concept so we can align the best implementation ! Thanks ! |
@lindyhopchris , I've implemented a working poc of the whereHas method, but I noticed that I can't access the filter from the related schema, unless the ones that are defined using the "withFilters" method from the relationship configuration on schema. On the documentation It says you can provide additional filters (https://laraveljsonapi.io/docs/1.0/schemas/filters.html#relationship-filters) , using the withFilters method, but getting them from the If I don't use the withFilters method, no filters are returned. Is this intentional ? Because it would generate duplicate code if I were to allow all Schema fields to be filtered. Also I couldn't find a way to access the relationship schema to get those filters as well. Here's an example and filter: //...class definition
public function fields(): array
{
return [
// other fields...
HasMany::make('roles')->readOnly(
static fn ($request) => !$request->user()->can('edit user')
)->withoutDefaultPagination()->withFilters(
Where::make('name'),
WhereIn::make('id')
)
]
}
public function filters(): array
{
return [
WhereHas::make('roles', $this)
];
} WhereHas.php <?php
namespace App\JsonApi\Filters;
use LaravelJsonApi\Eloquent\Contracts\Filter;
use LaravelJsonApi\Eloquent\Filters\Concerns\DeserializesValue;
use LaravelJsonApi\Eloquent\Filters\Concerns\IsSingular;
use LaravelJsonApi\Eloquent\Schema;
class WhereHas implements Filter
{
use DeserializesValue;
use IsSingular;
/**
* @var string
*/
private string $relationName;
private Schema $schema;
/**
* @var string
*/
private string $operator;
/**
* SearchFilter constructor.
*
* @param string $relationName
* @param array $columns
*/
public function __construct(string $relationName, $schema)
{
$this->relationName = $relationName;
$this->schema = $schema;
if (!$this->schema->isRelationship($relationName)) {
throw new \LogicException("Relationship with name $relationName not defined in ". get_class($schema)." schema.");
}
}
/**
* Create a new filter.
*
* @param string $relationName
* @param Schema $schema
* @return WhereHas
*/
public static function make(string $relationName, Schema $schema): self
{
return new static($relationName, $schema);
}
/**
* @inheritDoc
*/
public function key(): string
{
return $this->relationName;
}
/**
* Apply the filter to the query.
*
* @param \Illuminate\Database\Eloquent\Builder $query
* @param mixed $value
* @return \Illuminate\Database\Eloquent\Builder
*/
public function apply($query, $value)
{
$deserializedValues = $this->deserialize($value);
$availableFilters = $this->schema->relationship($this->relationName)->filters();
$keyedFilters = collect($availableFilters)->keyBy(function ($filter) {
return $filter->key();
})->all();
return $query->whereHas($this->relationName, function ($query) use ($deserializedValues, $keyedFilters) {
foreach ($deserializedValues as $key => $value) {
if (isset($keyedFilters[$key])) {
$keyedFilters[$key]->apply($query, $value);
}
}
});
}
} |
So you need to get the filter from the inverse schema, as well as the relationship filters. The inverse schema is the schema for the resources on the other side of the relationship - i.e. the To access the inverse schema, you use the relationship's So for your $relation = $this->schema->relationship($this->relationName);
$availableFilters = collect($relation->schema()->filters())->merge($relation->filters()); |
Thanks @lindyhopchris ! Working like a charm now. I'm going to create a pull request on the filters repository, Is the WhereHas Filter OK as is now, or you think some new adjustments should be made ? Following your specs, they should work along thoses lines as I understand: Has::make($relationName, $schema) WhereHas::make($relationName, $schema) WhereDoesntHave::make($relationName, $schema) |
Also, I was taking a look at some filtering relationship ideas (since it's not strictly specified on the json api) and I encountered also this idea, that filters the result of the relationship instead of filtering the main resource. https://discuss.jsonapi.org/t/filtering-querying-deep-relationships-a-proposal-for-a-syntax/1746 But for this to work, it would need some adjustments on the package to identity the dot syntax on the filter name, right ? Examples: WhereHas($relationName,$this) WhereRelationHas($relationName,$this) |
@lcsdms if you submit a PR to the Eloquent package, I can comment on it when I review it. The class works but needs some tidying up - it'd be easier for me to do that via review comments. As for filtering included relationships. Sounds nice, but would be a considerable amount of work. The problem is it wouldn't be implemented via filters - it actually needs to modify the eager loading implemention, which is already significantly complex. That's because in Eloquent terms it would need to do this: I've never investigated how constraining eager loads works with nested relationships. The eager loading implementation supports nesting as the JSON:API include path uses dot notation. Can we keep this issue on topic and not venture into modifying the eager loading implementation?! Just to keep my sanity?! There's already too much for me to do at the moment so I can't really have mission creep into nice-to-have-crazily-complex features!! |
No problem! Let's keep this into topic, I'll create the pull request and we'll go from there. Thanks ! |
As I'm creating my API it seems that I could also use a "WhereHas" relationship filter. Or maybe there is an alternative for my use case? I have a Rescuer schema with the following indexQuery: public function indexQuery(?Request $request, Builder $query): Builder
{
return $query->where('account_id', $request->user()->account_id)->whereHas('patients');
} What I would like to have is a filter on the public function indexQuery(?Request $request, Builder $query): Builder
{
return $query->where('account_id', $request->user()->parent_account_id)->whereHas('patients', function ($query) use ($request) {
$query->where('admitted_at', '>=', $request->filters['dateFrom']);
});
} However this requires defining the filters in the Schema which would affect the query in other ways. |
@devinfd what you need to do is write a filter that does that Then in the schema's |
Closing and moving additional discussion on the correct repository: |
Hi! First of all, thank you for the great package!
I'm learning and testing it, and I wanted to know, if this package does support filtering data by model and its relationship (like a inner join) ?
Example:
Resources (Models) :
And so I want to get:
(The url sintax is just an example, there are use cases where people would use [author][id]=1 for example.
I've checked the issue #49 but could not find a conclusive answer.
The text was updated successfully, but these errors were encountered: