Skip to content

Commit ee31d44

Browse files
committed
feat(taxonomy): add type-specific taxonomy relationship methods
1 parent c3178d4 commit ee31d44

File tree

1 file changed

+307
-0
lines changed

1 file changed

+307
-0
lines changed

src/Traits/HasTaxonomy.php

Lines changed: 307 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,137 @@ public function toggleTaxonomies($taxonomies, string $name = 'taxonomable'): sel
132132
return $this;
133133
}
134134

135+
/**
136+
* Detach taxonomies of a specific type from the model.
137+
*
138+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
139+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy>|null $taxonomies The taxonomies to detach (null to detach all of this type)
140+
* @param string $name The name of the relationship (default: 'taxonomable')
141+
* @return $this
142+
*/
143+
public function detachTaxonomiesOfType(string|TaxonomyType $type, $taxonomies = null, string $name = 'taxonomable'): self
144+
{
145+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
146+
147+
if (is_null($taxonomies)) {
148+
// Detach all taxonomies of this type
149+
$taxonomiesToDetach = $this->taxonomiesOfType($type, $name);
150+
$taxonomyIds = $taxonomiesToDetach->pluck('id')->toArray();
151+
} else {
152+
// Get taxonomy IDs and filter by type
153+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
154+
155+
// Verify that the taxonomies belong to the specified type
156+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
157+
$modelClass = config('taxonomy.model', Taxonomy::class);
158+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
159+
->where('type', $typeValue)
160+
->pluck('id')
161+
->toArray();
162+
163+
$taxonomyIds = $validTaxonomyIds;
164+
}
165+
166+
if (! empty($taxonomyIds)) {
167+
$this->taxonomies($name)->detach($taxonomyIds);
168+
}
169+
170+
return $this;
171+
}
172+
173+
/**
174+
* Sync taxonomies of a specific type with the model.
175+
*
176+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
177+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to sync
178+
* @param string $name The name of the relationship (default: 'taxonomable')
179+
* @return $this
180+
*/
181+
public function syncTaxonomiesOfType(string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): self
182+
{
183+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
184+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
185+
186+
// Verify that the taxonomies belong to the specified type
187+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
188+
$modelClass = config('taxonomy.model', Taxonomy::class);
189+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
190+
->where('type', $typeValue)
191+
->pluck('id')
192+
->toArray();
193+
194+
// Get current taxonomies of this type
195+
$currentTaxonomiesOfType = $this->taxonomiesOfType($type, $name)->pluck('id')->toArray();
196+
197+
// Detach current taxonomies of this type
198+
if (! empty($currentTaxonomiesOfType)) {
199+
$this->taxonomies($name)->detach($currentTaxonomiesOfType);
200+
}
201+
202+
// Attach new taxonomies of this type
203+
if (! empty($validTaxonomyIds)) {
204+
$this->taxonomies($name)->attach($validTaxonomyIds);
205+
}
206+
207+
return $this;
208+
}
209+
210+
/**
211+
* Attach taxonomies of a specific type to the model.
212+
*
213+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
214+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to attach
215+
* @param string $name The name of the relationship (default: 'taxonomable')
216+
* @return $this
217+
*/
218+
public function attachTaxonomiesOfType(string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): self
219+
{
220+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
221+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
222+
223+
// Verify that the taxonomies belong to the specified type
224+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
225+
$modelClass = config('taxonomy.model', Taxonomy::class);
226+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
227+
->where('type', $typeValue)
228+
->pluck('id')
229+
->toArray();
230+
231+
if (! empty($validTaxonomyIds)) {
232+
$this->taxonomies($name)->syncWithoutDetaching($validTaxonomyIds);
233+
}
234+
235+
return $this;
236+
}
237+
238+
/**
239+
* Toggle taxonomies of a specific type for the model.
240+
*
241+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
242+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to toggle
243+
* @param string $name The name of the relationship (default: 'taxonomable')
244+
* @return $this
245+
*/
246+
public function toggleTaxonomiesOfType(string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): self
247+
{
248+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
249+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
250+
251+
// Verify that the taxonomies belong to the specified type
252+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
253+
$modelClass = config('taxonomy.model', Taxonomy::class);
254+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
255+
->where('type', $typeValue)
256+
->pluck('id')
257+
->toArray();
258+
259+
if (! empty($validTaxonomyIds)) {
260+
$this->taxonomies($name)->toggle($validTaxonomyIds);
261+
}
262+
263+
return $this;
264+
}
265+
135266
/**
136267
* Determine if the model has any of the given taxonomies.
137268
*
@@ -169,6 +300,56 @@ public function hasTaxonomyType(string|TaxonomyType $type, string $name = 'taxon
169300
return $this->taxonomies($name)->where('type', $typeValue)->exists();
170301
}
171302

303+
/**
304+
* Determine if the model has any of the given taxonomies of a specific type.
305+
*
306+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
307+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to check
308+
* @param string $name The name of the relationship (default: 'taxonomable')
309+
* @return bool True if the model has any of the given taxonomies of the specified type
310+
*/
311+
public function hasTaxonomiesOfType(string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): bool
312+
{
313+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
314+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
315+
316+
return $this->taxonomies($name)
317+
->where('type', $typeValue)
318+
->whereIn('taxonomies.id', $taxonomyIds)
319+
->exists();
320+
}
321+
322+
/**
323+
* Determine if the model has all of the given taxonomies of a specific type.
324+
*
325+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
326+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to check
327+
* @param string $name The name of the relationship (default: 'taxonomable')
328+
* @return bool True if the model has all of the given taxonomies of the specified type
329+
*/
330+
public function hasAllTaxonomiesOfType(string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): bool
331+
{
332+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
333+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
334+
335+
// Verify that the taxonomies belong to the specified type
336+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
337+
$modelClass = config('taxonomy.model', Taxonomy::class);
338+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
339+
->where('type', $typeValue)
340+
->pluck('id')
341+
->toArray();
342+
343+
if (empty($validTaxonomyIds)) {
344+
return false;
345+
}
346+
347+
return $this->taxonomies($name)
348+
->where('type', $typeValue)
349+
->whereIn('taxonomies.id', $validTaxonomyIds)
350+
->count() === count($validTaxonomyIds);
351+
}
352+
172353
/**
173354
* Scope a query to include models that have any of the given taxonomies.
174355
*
@@ -257,6 +438,78 @@ public function scopeWithoutTaxonomies(Builder $query, $taxonomies, string $name
257438
});
258439
}
259440

441+
/**
442+
* Scope a query to include models that have any of the given taxonomies of a specific type.
443+
*
444+
* @param \Illuminate\Database\Eloquent\Builder<$this> $query
445+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
446+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to filter by
447+
* @param string $name The name of the relationship (default: 'taxonomable')
448+
* @return \Illuminate\Database\Eloquent\Builder<$this>
449+
*/
450+
public function scopeWithAnyTaxonomiesOfType(Builder $query, string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): Builder
451+
{
452+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
453+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
454+
455+
return $query->whereHas('taxonomies', function ($query) use ($typeValue, $taxonomyIds) {
456+
$query->where('type', $typeValue)
457+
->whereIn('taxonomies.id', $taxonomyIds);
458+
});
459+
}
460+
461+
/**
462+
* Scope a query to include models that have all of the given taxonomies of a specific type.
463+
*
464+
* @param \Illuminate\Database\Eloquent\Builder<$this> $query
465+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
466+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to filter by
467+
* @param string $name The name of the relationship (default: 'taxonomable')
468+
* @return \Illuminate\Database\Eloquent\Builder<$this>
469+
*/
470+
public function scopeWithAllTaxonomiesOfType(Builder $query, string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): Builder
471+
{
472+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
473+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
474+
475+
// Verify that the taxonomies belong to the specified type
476+
/** @var class-string<\Aliziodev\LaravelTaxonomy\Models\Taxonomy> $modelClass */
477+
$modelClass = config('taxonomy.model', Taxonomy::class);
478+
$validTaxonomyIds = $modelClass::whereIn('id', $taxonomyIds)
479+
->where('type', $typeValue)
480+
->pluck('id')
481+
->toArray();
482+
483+
foreach ($validTaxonomyIds as $taxonomyId) {
484+
$query->whereHas('taxonomies', function ($query) use ($typeValue, $taxonomyId) {
485+
$query->where('type', $typeValue)
486+
->where('taxonomies.id', $taxonomyId);
487+
});
488+
}
489+
490+
return $query;
491+
}
492+
493+
/**
494+
* Scope a query to exclude models that have any of the given taxonomies of a specific type.
495+
*
496+
* @param \Illuminate\Database\Eloquent\Builder<$this> $query
497+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
498+
* @param int|string|array<int, int|string|\Aliziodev\LaravelTaxonomy\Models\Taxonomy>|\Aliziodev\LaravelTaxonomy\Models\Taxonomy|\Illuminate\Database\Eloquent\Collection<int, \Aliziodev\LaravelTaxonomy\Models\Taxonomy> $taxonomies The taxonomies to exclude
499+
* @param string $name The name of the relationship (default: 'taxonomable')
500+
* @return \Illuminate\Database\Eloquent\Builder<$this>
501+
*/
502+
public function scopeWithoutTaxonomiesOfType(Builder $query, string|TaxonomyType $type, $taxonomies, string $name = 'taxonomable'): Builder
503+
{
504+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
505+
$taxonomyIds = $this->getTaxonomyIds($taxonomies);
506+
507+
return $query->whereDoesntHave('taxonomies', function ($query) use ($typeValue, $taxonomyIds) {
508+
$query->where('type', $typeValue)
509+
->whereIn('taxonomies.id', $taxonomyIds);
510+
});
511+
}
512+
260513
/**
261514
* Filter models by multiple taxonomy criteria.
262515
*
@@ -478,4 +731,58 @@ public function hasDescendantTaxonomy(int $taxonomyId): bool
478731

479732
return $modelTaxonomyIds->intersect($descendantIds)->isNotEmpty();
480733
}
734+
735+
/**
736+
* Get the count of taxonomies of a specific type for this model.
737+
*
738+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
739+
* @param string $name The name of the relationship (default: 'taxonomable')
740+
* @return int The count of taxonomies of the specified type
741+
*/
742+
public function getTaxonomyCountByType(string|TaxonomyType $type, string $name = 'taxonomable'): int
743+
{
744+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
745+
746+
return $this->taxonomies($name)->where('type', $typeValue)->count();
747+
}
748+
749+
/**
750+
* Get the first taxonomy of a specific type for this model.
751+
*
752+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type
753+
* @param string $name The name of the relationship (default: 'taxonomable')
754+
* @return \Aliziodev\LaravelTaxonomy\Models\Taxonomy|null The first taxonomy of the specified type or null if none found
755+
*/
756+
public function getFirstTaxonomyOfType(string|TaxonomyType $type, string $name = 'taxonomable'): ?Taxonomy
757+
{
758+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
759+
760+
return $this->taxonomies($name)->where('type', $typeValue)->first();
761+
}
762+
763+
/**
764+
* Scope a query to order models by taxonomy type and optionally by taxonomy name.
765+
*
766+
* @param \Illuminate\Database\Eloquent\Builder<$this> $query
767+
* @param string|\Aliziodev\LaravelTaxonomy\Enums\TaxonomyType $type The taxonomy type to order by
768+
* @param string $direction The sort direction ('asc' or 'desc')
769+
* @param string $orderBy The field to order by ('name' or 'slug')
770+
* @return \Illuminate\Database\Eloquent\Builder<$this>
771+
*/
772+
public function scopeOrderByTaxonomyType(Builder $query, string|TaxonomyType $type, string $direction = 'asc', string $orderBy = 'name'): Builder
773+
{
774+
$typeValue = $type instanceof TaxonomyType ? $type->value : $type;
775+
$direction = strtolower($direction) === 'desc' ? 'desc' : 'asc';
776+
$orderBy = in_array($orderBy, ['name', 'slug']) ? $orderBy : 'name';
777+
778+
return $query->join('taxonomables as tax_order', function ($join) {
779+
$join->on($this->getTable() . '.id', '=', 'tax_order.taxonomable_id')
780+
->where('tax_order.taxonomable_type', '=', get_class($this));
781+
})
782+
->join('taxonomies as tax_sort', 'tax_order.taxonomy_id', '=', 'tax_sort.id')
783+
->where('tax_sort.type', $typeValue)
784+
->orderBy('tax_sort.' . $orderBy, $direction)
785+
->select($this->getTable() . '.*')
786+
->distinct();
787+
}
481788
}

0 commit comments

Comments
 (0)