> ## Documentation Index
> Fetch the complete documentation index at: https://docs.eloquentfiltering.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Pivot Filters

## Overview

Pivot filters can be specified in two ways:

* on Custom Intermediate Table Models (`Pivot`, `MorphPivot`).
* on your main models (`Model`).

## With Custom Intermediate Table Models

<Note>
  Added in 2.1.0
</Note>

<Tip>
  This is the recommended way

  * Define the allowed pivot filters once in the Custom Intermediate Table Model.
  * Not required to call `->pivot()`.
  * **Caveat**: Must call `->includeRelationFields()` on `Filter::relation()`.
</Tip>

If you are utilising [Custom Intermediate Table Models](https://laravel.com/docs/10.x/eloquent-relationships#defining-custom-intermediate-table-models), you may define filters on that model.

Filters defined on this model will be applied to the intermediate table.

```php theme={null}
/*
 * User Model
 */
class User extends Model implements IsFilterable
{
    use Filterable;

    public function roles(): BelongsToMany
    {
        return $this->belongsToMany(Role::class)->using(RoleUser::class);
    }

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::relation('roles', [FilterType::HAS])->includeRelationFields(),
        );
    }
}

/*
 * Role Model
 */
class Role extends Model implements IsFilterable
{
    use Filterable;

    public function users(): BelongsToMany
    {
        return $this->belongsToMany(User::class)->using(RoleUser::class);
    }

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::relation('users', [FilterType::HAS])->includeRelationFields(),
        );
    }
}

/*
 * RoleUser Pivot Model
 */
class RoleUser extends Pivot implements IsFilterable
{
    use Filterable;

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::field('assigned_by', [FilterType::EQUAL]),
        );
    }
}

/*
 * Applying `assigned_by` pivot filter
 * within `roles` `$has` relation filter.
 */
User::filter([
    [
        'type'   => '$has',
        'target' => 'roles',
        'value'  => [
            [
                'type'   => '$eq',
                'target' => 'assigned_by',
                'value'  => 'admin',
            ]
        ]
    ]
]);

/*
 * Applying `assigned_by` pivot filter
 * within `users` `$has` relation filter.
 */
Role::filter([
    [
        'type'   => '$has',
        'target' => 'users',
        'value'  => [
            [
                'type'   => '$eq',
                'target' => 'assigned_by',
                'value'  => 'admin',
            ]
        ]
    ]
]);
```

### Restricting Allowed Pivot Filters.

Sometimes you may only want the pivot filter to be allowed when filtering by relationship one way but not the other way.

For example:

* **Allowed** when filtering `User` `$has` `Role`.
* **Denied** when filtering `Role` `$has` `User`.

To achieve this you can specify the allowed "from" model for the pivot filter.

```php {12} theme={null}
/*
 * RoleUser Pivot Model
 */
class RoleUser extends Pivot implements IsFilterable
{
    use Filterable;

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::field('assigned_by', [FilterType::EQUAL])
                ->pivot(User::class),
        );
    }
}
```

## Without Custom Intermediate Table Models

If you are **NOT** utilising [Custom Intermediate Table Models](https://laravel.com/docs/10.x/eloquent-relationships#defining-custom-intermediate-table-models),
you may define pivot filters on your main models.

`Filter::field()` filters can be marked as pivot filters if you want the filter to be applied to a column on the intermediate table linking the models.

You must specify the parent model fqcn.

```php {5} theme={null}
public function allowedFilters(): AllowedFilterList
{
    return Filter::only(
        Filter::field('tagged_by', [FilterType::EQUAL])
            ->pivot(Post::class),
    );
}
```

### BelongsToMany

In the below example of `class Post` and `class Tag`.

* The pivot filter is specified in the `allowedFilters` method of both classes.
* The pivot filter can **only** be used when in the context of the `posts` or `tags` relationship.

```php theme={null}
class Post extends Model implements IsFilterable
{
    use Filterable;

    public function tags(): BelongsToMany
    {
        return $this->belongsToMany(Tag::class);
    }

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::field('tagged_by', [FilterType::EQUAL])->pivot(Tag::class),
            Filter::relation('tags', [FilterType::HAS])->includeRelationFields()
        );
    }
}

class Tag extends Model implements IsFilterable
{
    use Filterable;

    public function posts(): BelongsToMany
    {
        return $this->belongsToMany(Post::class);
    }

    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::field('tagged_by', [FilterType::EQUAL])->pivot(Post::class),
            Filter::relation('posts', [FilterType::HAS])->includeRelationFields()
        );
    }
}
```

#### Allowed

With the models setup as described above. The following filters are allowed.

```php theme={null}
/*
 * Applying `tagged_by` pivot filter
 * within `tags` `$has` relation filter.
 */
Post::filter([
    [
        'type'   => '$has',
        'target' => 'tags',
        'value'  => [
            [
                'type'   => '$eq',
                'target' => 'tagged_by',
                'value'  => 'admin',
            ]
        ]
    ]
]);

/*
 * Applying `tagged_by` pivot filter
 * within `posts` `$has` relation filter.
 */
Tag::filter([
    [
        'type'   => '$has',
        'target' => 'posts',
        'value'  => [
            [
                'type'   => '$eq',
                'target' => 'tagged_by',
                'value'  => 'admin',
            ]
        ]
    ]
]);
```

#### DeniedFilterException

With the models setup as described above. The following filters are denied.

```php theme={null}
/*
 * Applying `tagged_by` pivot filter
 * when not in context of `tags` `$has` relation filter.
 * throws DeniedFilterException.
 */
Post::filter([
    [
        'type'   => '$eq',
        'target' => 'tagged_by',
        'value'  => 'admin',
    ]
]);

/*
 * Applying `tagged_by` pivot filter
 * when not in context of `posts` `$has` relation filter.
 * throws DeniedFilterException.
 */
Tag::filter([
    [
        'type'   => '$eq',
        'target' => 'tagged_by',
        'value'  => 'admin',
    ]
]);

/*
 * Applying `tagged_by` pivot filter
 * when in context of another BelongsToMany relation
 * but User::class not defined in the ->pivot() method in the tags model.
 * throws DeniedFilterException.
 */
User::filter([
    [
        'type'   => '$has',
        'target' => 'tags',
        'value'  => [
            [
                'type'   => '$eq',
                'target' => 'tagged_by',
                'value'  => 'admin',
            ]
        ]
    ]
]);
```

### MorphToMany (Polymorphic)

When defining a pivot filter for `MorphToMany` relations, you can specify a list of models in the `->pivot()` method.

```php theme={null}
class Epic extends Model implements IsFilterable
{
    // ...
    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::relation('labels', [FilterType::HAS])->includeRelationFields()
        );
    }
}

class Issue extends Model implements IsFilterable
{
    // ...
    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::relation('labels', [FilterType::HAS])->includeRelationFields()
        );
    }
}

class Label extends Model implements IsFilterable
{
    // ...
    public function allowedFilters(): AllowedFilterList
    {
        return Filter::only(
            Filter::field('labeled_by', [FilterType::EQUAL])
                ->pivot(Epic::class, Issue::class)
        );
    }
}
```
