NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky
Tags for Laravel

Validácia, filtre a resources

TagsRule

Validačné pravidlo, ktoré kontroluje vstup so štítkami rovnako ako uloženie — kurátorované zoznamy, limity, rozsahy a povolené typy — bez vedľajších účinkov: nikdy nevytvorí štítok:

use RoundlyConsulting\Tags\Rules\TagsRule;

public function rules(): array
{
    return [
        'category' => ['nullable', TagsRule::for('genre')],                          // single type: one value
        'labels' => ['array', TagsRule::for('label')->taggable($this->route('concert'))],
        'skills' => ['array', TagsRule::for('skill')->within($this->user()->team)->max(3)],
        'topic_ids' => ['array', TagsRule::for('topic')->byId()->taggable($this->route('article'))],
    ];
}
VolanieVýznam
TagsRule::for($type)Validuje vstup pre typ bez vedľajších účinkov — nikdy nevytvorí štítok. Zoznam alebo jedna hodnota pri type s jedným štítkom.
->taggable($model)Validuje tak, ako bude označovať tento model: jeho kandidátske rozsahy, globalOnly typu a povolené typy. Funguje aj neuložený model — číta sa len jeho trieda, tagScope() a tagTypes(). Overené kľúče premeníte na štítky cez Tags::for($model)->findMany($keys, $type).
->within(?Model $scope)Vyhodnocuje v rozsahu a globálnych štítkoch (null: len globálne); prepíše to, čo by dal taggable().
->byId()Položky sú primárne kľúče (prijímajú sa aj číselné reťazce), nie názvy.
->max(?int $max)Najviac toľko rôznych štítkov — nikdy viac, než povoľuje typ.

Každá položka musí byť platný názov alebo slug (pri byId() kľúč). Pri kurátorovanom type musí každá položka označovať živý štítok v kandidátskych rozsahoch; pri voľnom type neznáme názvy prejdú. Zlyhanie uvedie prvú neúspešnú položku; kľúč iného rozsahu a chýbajúci kľúč zlyhajú rovnako, takže hlásenie o iných rozsahoch nič neprezradí:

ZlyhanieHlásenie (anglicky)
Nie je zoznam pri type s viacerými štítkamiThe :attribute must be a list of tags.
Typ, ktorý model nesmie niesť (s taggable())The :attribute cannot be used on this record.
Prázdna, pridlhá či na slug nevhodná položka alebo chybný kľúčThe :attribute contains an invalid tag [:tag].
Neznámy názov kurátorovaného typu; kľúč chýbajúci, iného typu či rozsahu — rovnakoThe :attribute contains the unknown tag [:tag].
Viac rôznych štítkov, než dovoľuje limit (neznáme voľné názvy sa rátajú podľa slugu)The :attribute may hold at most :max tags.

Hlásenia pochádzajú z tags::validation v angličtine a slovenčine.

Od validovaného vstupu k zápisu

// Validated keys arrive as strings, and a string is a name to the tagging verbs:
// turn them into tags first, in the scopes the rule validated them in
$topics = Tags::for($article)->findMany($request->validated('topic_ids'), 'topic');   // a rule with taggable($article)
$article->syncTags($topics, 'topic');

$skills = Tags::type('skill')->within($team)->findMany($skillIds);                      // a rule with within($team)->byId()

// Names and slugs go straight through
$concert->syncTags($request->validated('labels'), 'label');

TagsFilter

Filter pre query-builder-for-laravel, ktorý aplikuje scopes štítkov modelu, takže indexový endpoint filtruje presne ako withAnyTags() a spol.:

use RoundlyConsulting\QueryBuilder\AllowedFilter;
use RoundlyConsulting\QueryBuilder\QueryBuilder;
use RoundlyConsulting\Tags\Http\Filters\TagsFilter;

// GET /concerts?filter[category]=music&filter[labels]=live,outdoor
$concerts = QueryBuilder::for(Concert::class, $request)
    ->allowedFilters(
        AllowedFilter::custom('category', TagsFilter::any('genre')->withDescendants()),
        AllowedFilter::custom('labels', TagsFilter::all('label')),
        AllowedFilter::custom('without', TagsFilter::none('label')),
        AllowedFilter::custom('team_labels', TagsFilter::any('label')->within(fn () => $request->user()?->team)),
        AllowedFilter::custom('topic', TagsFilter::any('topic')->byId()),
    )
    ->paginate();
VolanieVýznam
TagsFilter::any($type) / all($type) / none($type)Scope withAnyTags / withAllTags / withoutTags ako filter pre query-builder — PHP aj HTTP filtrujú rovnako.
->withDescendants()Štítok vyhovie aj každým štítkom pod sebou.
->within(Model|Closure|null $scope)Obmedzí reťazce na rozsah a globálne štítky; closure sa vyhodnotí až pri aplikovaní filtra — pre filtre deklarované skôr, než je používateľ známy.
->byId()Hodnoty sú primárne kľúče, nie slugy ani názvy.

Hodnoty sú slugy alebo názvy (pri byId() kľúče), oddelené čiarkou alebo ako pole. Prázdna hodnota — ?filter[category]= alebo vymazaný výber — v žiadnom režime nič neobmedzí. Pre modely bez štítku daného typu použite scope withoutTags([], $type) v AllowedFilter::callback.

TagSummaryResource

Tvar na vnorenie štítkov do vlastných JSON resources — kategórie a označení podujatia:

use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;
use RoundlyConsulting\Tags\Http\Resources\TagSummaryResource;

final class ConcertResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        $genre = $this->resource->tagOf('genre');

        return [
            'id' => $this->resource->getKey(),
            'title' => $this->resource->title,
            'genre' => $genre === null ? null : new TagSummaryResource($genre),
            'labels' => TagSummaryResource::collection($this->resource->tagsOf('label')),
        ];
    }
}

// The controller: tags in one query, category ancestors in one more
$concerts = Concert::with('tags')->paginate(20);
Tags::loadAncestors($concerts->getCollection()->map(fn (Concert $concert) => $concert->tagOf('genre')));

return ConcertResource::collection($concerts);
{
  "id": 7,
  "title": "Night at the club",
  "genre": {
    "id": 2,
    "type": "genre",
    "name": "Jazz",
    "slug": "jazz",
    "color": null,
    "icon": "sax",
    "depth": 2,
    "ancestors": [
      { "id": 1, "type": "genre", "name": "Music", "slug": "music", "color": "#f24e29", "icon": null, "depth": 1 }
    ]
  },
  "labels": [
    { "id": 9, "type": "label", "name": "Live", "slug": "live", "color": null, "icon": null, "depth": 1 }
  ]
}

id, type (názov typu), name a slug v aktuálnom jazyku, farba, ikona a hĺbka — plus ancestors (od koreňa, každý v rovnakom tvare), ak ich načítal loadAncestors(). Bez počtu použití, rozsahu, rodiča a časových pečiatok, takže vykreslenie nespustí žiadnu Gate kontrolu ani dotaz a ku klientovi sa nedostane žiadny názov PHP triedy ani rozsah.

Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

Ďalšie spôsoby podpory vrátane kryptomien

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.