mirror of
https://github.com/filamentphp/filament.git
synced 2026-09-24 15:42:09 +08:00
Merge pull request #8873 from cheesegrits/3.x-relationship-joiner
Handle pivot clauses in relationship joiner
This commit is contained in:
@@ -5,13 +5,13 @@ namespace Filament\Forms\Components;
|
||||
use Closure;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Support\Enums\ActionSize;
|
||||
use Filament\Support\Services\RelationshipJoiner;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidationRules
|
||||
@@ -133,23 +133,7 @@ class CheckboxList extends Field implements Contracts\HasNestedRecursiveValidati
|
||||
$this->options(static function (CheckboxList $component) use ($modifyQueryUsing): array {
|
||||
$relationship = Relation::noConstraints(fn () => $component->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($modifyQueryUsing) {
|
||||
$relationshipQuery = $component->evaluate($modifyQueryUsing, [
|
||||
|
||||
@@ -8,6 +8,7 @@ use Filament\Forms\ComponentContainer;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Support\Concerns\HasExtraAlpineAttributes;
|
||||
use Filament\Support\Services\RelationshipJoiner;
|
||||
use Illuminate\Contracts\Support\Arrayable;
|
||||
use Illuminate\Contracts\Support\Htmlable;
|
||||
use Illuminate\Database\Connection;
|
||||
@@ -18,7 +19,6 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rule;
|
||||
use Illuminate\Validation\Rules\Exists;
|
||||
@@ -704,23 +704,7 @@ class Select extends Field implements Contracts\HasAffixActions, Contracts\HasNe
|
||||
$this->getSearchResultsUsing(static function (Select $component, ?string $search) use ($modifyQueryUsing): array {
|
||||
$relationship = Relation::noConstraints(fn () => $component->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($modifyQueryUsing) {
|
||||
$relationshipQuery = $component->evaluate($modifyQueryUsing, [
|
||||
@@ -786,23 +770,7 @@ class Select extends Field implements Contracts\HasAffixActions, Contracts\HasNe
|
||||
|
||||
$relationship = Relation::noConstraints(fn () => $component->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($modifyQueryUsing) {
|
||||
$relationshipQuery = $component->evaluate($modifyQueryUsing, [
|
||||
@@ -912,23 +880,7 @@ class Select extends Field implements Contracts\HasAffixActions, Contracts\HasNe
|
||||
$this->getSelectedRecordUsing(static function (Select $component, $state) use ($modifyQueryUsing): ?Model {
|
||||
$relationship = Relation::noConstraints(fn () => $component->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
$relatedKeyName = $relationship->getRelatedKeyName();
|
||||
@@ -952,23 +904,7 @@ class Select extends Field implements Contracts\HasAffixActions, Contracts\HasNe
|
||||
$this->getOptionLabelsUsing(static function (Select $component, array $values) use ($modifyQueryUsing): array {
|
||||
$relationship = Relation::noConstraints(fn () => $component->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
$relatedKeyName = $relationship->getQualifiedRelatedKeyName();
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Support\Services;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
use Kirschbaum\PowerJoins\JoinsHelper;
|
||||
use Kirschbaum\PowerJoins\PowerJoins;
|
||||
|
||||
class RelationshipJoiner
|
||||
{
|
||||
use PowerJoins;
|
||||
|
||||
public function leftJoinRelationship(Builder $query, string $relationship): Builder
|
||||
{
|
||||
if (str($relationship)->contains('.')) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->joinNestedRelationship(
|
||||
$relationship,
|
||||
callback: null,
|
||||
joinType: JoinsHelper::$joinMethodsMap['leftJoin'] ?? 'leftJoin',
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->joinRelationship(
|
||||
$relationship,
|
||||
callback: null,
|
||||
joinType: 'leftJoin',
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<JoinClause>
|
||||
*/
|
||||
public function getLeftJoinsForRelationship(Builder $query, string $relationship): array
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->leftJoinRelationship($relationship);
|
||||
|
||||
return $query->toBase()->joins;
|
||||
}
|
||||
|
||||
public function prepareQueryForNoConstraints(Relation $relationship): Builder
|
||||
{
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
|
||||
// Any "where" clauses that are scoped to the pivot table need to be moved to the join.
|
||||
// It's expected that any scopes that don't apply to the pivot table do not have
|
||||
// a `column` attribute set.
|
||||
$relationshipQueryPivotWheres = Arr::where(
|
||||
$relationshipQuery->getQuery()->wheres,
|
||||
function (array $where) use ($relationship): bool {
|
||||
if (! array_key_exists('column', $where)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return Str::startsWith($where['column'], "{$relationship->getTable()}.");
|
||||
},
|
||||
);
|
||||
|
||||
$firstRelationshipJoinClause->wheres = array_merge(
|
||||
$firstRelationshipJoinClause->wheres,
|
||||
$relationshipQueryPivotWheres,
|
||||
);
|
||||
|
||||
$relationshipQuery->getQuery()->wheres = Arr::except(
|
||||
$relationshipQuery->getQuery()->wheres,
|
||||
array_keys($relationshipQueryPivotWheres),
|
||||
);
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct()
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
|
||||
return $relationshipQuery;
|
||||
}
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use Closure;
|
||||
use Filament\Actions\Concerns\CanCustomizeProcess;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Support\Services\RelationshipJoiner;
|
||||
use Filament\Tables\Table;
|
||||
use Illuminate\Database\Connection;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
@@ -13,7 +14,6 @@ use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Str;
|
||||
|
||||
@@ -70,23 +70,7 @@ class AttachAction extends Action
|
||||
/** @var BelongsToMany $relationship */
|
||||
$relationship = Relation::noConstraints(fn () => $table->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching records to attach.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
$isMultiple = is_array($data['recordId']);
|
||||
|
||||
@@ -198,23 +182,7 @@ class AttachAction extends Action
|
||||
/** @var BelongsToMany $relationship */
|
||||
$relationship = Relation::noConstraints(fn () => $table->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($this->modifyRecordSelectOptionsQueryUsing) {
|
||||
$relationshipQuery = $this->evaluate($this->modifyRecordSelectOptionsQueryUsing, [
|
||||
@@ -295,23 +263,7 @@ class AttachAction extends Action
|
||||
->getOptionLabelUsing(function ($value) use ($table): string {
|
||||
$relationship = Relation::noConstraints(fn () => $table->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
return $this->getRecordTitle($relationshipQuery->find($value));
|
||||
})
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
namespace Filament\Tables\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Filament\Tables\Support\RelationshipJoiner;
|
||||
use Filament\Support\Services\RelationshipJoiner;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\Expression;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
namespace Filament\Tables\Filters\Concerns;
|
||||
|
||||
use Closure;
|
||||
use Filament\Support\Services\RelationshipJoiner;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||
use Illuminate\Database\Eloquent\Relations\Relation;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
|
||||
trait HasRelationship
|
||||
{
|
||||
@@ -79,23 +78,7 @@ trait HasRelationship
|
||||
{
|
||||
$relationship = Relation::noConstraints(fn () => $this->getRelationship());
|
||||
|
||||
$relationshipQuery = $relationship->getQuery();
|
||||
|
||||
// By default, `BelongsToMany` relationships use an inner join to scope the results to only
|
||||
// those that are attached in the pivot table. We need to change this to a left join so
|
||||
// that we can still get results when the relationship is not attached to the record.
|
||||
if ($relationship instanceof BelongsToMany) {
|
||||
/** @var ?JoinClause $firstRelationshipJoinClause */
|
||||
$firstRelationshipJoinClause = $relationshipQuery->getQuery()->joins[0] ?? null;
|
||||
|
||||
if ($firstRelationshipJoinClause) {
|
||||
$firstRelationshipJoinClause->type = 'left';
|
||||
}
|
||||
|
||||
$relationshipQuery
|
||||
->distinct() // Ensure that results are unique when fetching options and indicating.
|
||||
->select($relationshipQuery->getModel()->getTable() . '.*');
|
||||
}
|
||||
$relationshipQuery = (new RelationshipJoiner())->prepareQueryForNoConstraints($relationship);
|
||||
|
||||
if ($this->getModifyRelationshipQueryUsing()) {
|
||||
$relationshipQuery = $this->evaluate($this->modifyRelationshipQueryUsing, [
|
||||
|
||||
@@ -1,44 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Filament\Tables\Support;
|
||||
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Query\JoinClause;
|
||||
use Kirschbaum\PowerJoins\JoinsHelper;
|
||||
|
||||
class RelationshipJoiner
|
||||
{
|
||||
public function leftJoinRelationship(Builder $query, string $relationship): Builder
|
||||
{
|
||||
if (str($relationship)->contains('.')) {
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->joinNestedRelationship(
|
||||
$relationship,
|
||||
callback: null,
|
||||
joinType: JoinsHelper::$joinMethodsMap['leftJoin'] ?? 'leftJoin',
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->joinRelationship(
|
||||
$relationship,
|
||||
callback: null,
|
||||
joinType: 'leftJoin',
|
||||
);
|
||||
|
||||
return $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<JoinClause>
|
||||
*/
|
||||
public function getLeftJoinsForRelationship(Builder $query, string $relationship): array
|
||||
{
|
||||
/** @phpstan-ignore-next-line */
|
||||
$query->leftJoinRelationship($relationship);
|
||||
|
||||
return $query->toBase()->joins;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user