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

Jedinečnosť, povinnosť a predvolené hodnoty

Jedinečné hodnoty

Jedinečná definícia odmietne hodnotu, ktorú už má iný vlastník:

use RoundlyConsulting\Attributes\DataTransferObjects\AttributeDefinitionData;
use RoundlyConsulting\Attributes\Enums\AttributeType;
use RoundlyConsulting\Attributes\Enums\UniqueScope;
use RoundlyConsulting\Attributes\Facades\Attributes;

Attributes::define(new AttributeDefinitionData('sku', AttributeType::String_, unique: UniqueScope::Global_));

Attributes::for($a)->set('sku', 'ABC');
Attributes::for($b)->set('sku', 'ABC'); // throws DuplicateAttributeValueException
Attributes::for($a)->set('sku', 'ABC'); // re-saving the owner's own value is fine

Attributes::for($a)->forget('sku');
Attributes::for($b)->set('sku', 'ABC'); // fine — detaching freed the value
Hodnota v konfiguráciiPrípad enumuVynucuje
none, false, prázdne alebo neuvedenéUniqueScope::NoneNič — predvolené.
owner alebo trueUniqueScope::OwnerJedinečnosť medzi vlastníkmi rovnakého typu modelu — naprieč všetkými tímami či tenantmi.
scopeUniqueScope::ScopeJedinečnosť medzi vlastníkmi rovnakého typu modelu, ktorí patria k rovnakému modelu rozsahu — tímu, tenantovi.
globalUniqueScope::Global_Jedinečnosť naprieč všetkými vlastníkmi bez ohľadu na typ.

Opätovné uloženie vlastnej hodnoty toho istého vlastníka prejde bez chyby, odpojenie atribútu — alebo trvalé zmazanie jeho vlastníka — jeho hodnotu uvoľní a hodnoty null sa nekontrolujú. DuplicateAttributeValueException sprístupňuje attributeName a scope.

Jedinečnosť je zárukou databázy, nie len kontrolou pred zápisom: každá jedinečná hodnota nesie deterministický hash v stĺpci unique_hash s unikátnym indexom — dvaja súbežní zapisovatelia tej istej hodnoty nemôžu commitnúť obaja a ten pomalší dostane DuplicateAttributeValueException. Funguje to aj pre šifrované hodnoty: ich hash je kľúčovaný slepý index (HMAC pod kľúčom odvodeným z APP_KEY), takže databáza nikdy nedrží otvorený text. Rotácia APP_KEY tento kľúč zmení — šifrované jedinečné hodnoty potom uložte znova.

Jedinečnosť v rámci tímu či tenanta

owner robí hodnotu jedinečnou medzi všetkými vlastníkmi daného typu modelu — v aplikácii s viacerými tenantmi teda naprieč všetkými tenantmi. 'unique' => 'scope' to zúži na vlastníkov, ktorí patria k rovnakému modelu rozsahu, takže SKU ABC môžu používať dva tímy naraz a nikto sa nedozvie, čo má uložené iný tím. Rozsah určíte pre konkrétnu definíciu cez unique_scope, pre všetky definície s rozsahom naraz nastavením unique_scope alebo closure:

// config/attributes.php — one tenant key for every scoped definition…
'unique_scope' => 'team_id',
'definitions' => [
    'sku' => ['type' => 'string', 'unique' => 'scope'],
    // …or per definition: an attribute/relation name, or a ResolvesUniqueScope class-string
    'code' => ['type' => 'string', 'unique' => 'scope', 'unique_scope' => 'team'],
],

// At runtime (or in a model's attributeDefinitions()) a closure works too
Attributes::define(new AttributeDefinitionData('sku', AttributeType::String_,
    unique: UniqueScope::Scope, uniqueScope: fn (Model $owner) => $owner->team_id));

Attributes::for($productOfTeamA)->set('sku', 'ABC');
Attributes::for($productOfTeamB)->set('sku', 'ABC');  // fine — another team
Attributes::for($otherProductOfTeamA)->set('sku', 'ABC'); // throws DuplicateAttributeValueException

Jeden riadok na vlastníka a názov už zaručuje jedinečnosť v rámci jedného vlastníka, preto jedinečnosť pre konkrétnu inštanciu vlastníka vlastný rozsah nepotrebuje. Rozsah sa určuje v tomto poradí — unique_scope definície, inak nastavenie unique_scope, inak InvalidConfigurationException:

  • Closure dostane vlastníka.
  • Class-string musí implementovať RoundlyConsulting\Attributes\Contracts\ResolvesUniqueScope — __invoke(Model $owner): Model|int|string|null, získava sa z kontajnera; akákoľvek iná trieda vyhodí InvalidConfigurationException.
  • Akýkoľvek iný reťazec sa z vlastníka prečíta ako atribút alebo relácia. Nenačítaná relácia sa dopytuje priamo, takže funguje aj pri Model::preventLazyLoading(); ak uvediete cudzí kľúč (team_id), dopyt sa vynechá.
  • Uložený model sa počíta ako jeho morph trieda a kľúč, neprázdny int či reťazec ako on sám.
  • Null, prázdny reťazec, neuložený model či čokoľvek iné zlyhá bezpečne: AttributesException („… is unique per scope, but its owner resolved no scope“) a nič sa nezapíše — vlastník bez tenanta sa tak nikdy nedostane do spoločného priestoru.
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Attributes\Contracts\ResolvesUniqueScope;

final class OwnerTenant implements ResolvesUniqueScope
{
    public function __invoke(Model $owner): Model|int|string|null
    {
        return $owner->tenant_id; // a saved model or a non-empty key; null is refused
    }
}

// config/attributes.php
'unique_scope' => App\Attributes\OwnerTenant::class,

Kľúč rozsahu je súčasťou unique_hash, takže zárukou zostáva existujúci unikátny index: súbežní zapisovatelia v jednom rozsahu dostanú DuplicateAttributeValueException a migrácia nie je potrebná. Kontrola pred zápisom započíta aj nešifrované riadky uložené ešte predtým, než sa definícia stala jedinečnou — určí rozsah ich vlastníkov. Zmena rozsahu definície, napríklad z owner na scope, uložené riadky neprepočíta; ich hodnoty uložte znova, aby platil nový hash.

DuplicateAttributeValueException uvádza len atribút a rozsah (Attribute [sku] must be unique (scope); the given value already exists.) — nikdy iného vlastníka, jeho model rozsahu ani hodnotu. Pri owner či global volajúci vidí už samotný fakt, že hodnota inde existuje; scope to obmedzí na vlastníkov s rovnakým modelom rozsahu.

Povinné atribúty

Povinnosť sa vynucuje na požiadanie, nie pri každom pripojení — zavolajte validateAttributes() vo chvíli, keď má byť model úplný, napríklad na konci importu alebo kroku objednávky:

use RoundlyConsulting\Attributes\Exceptions\MissingRequiredAttributeException;

Attributes::define(new AttributeDefinitionData('rating', AttributeType::Integer, required: true));

try {
    $product->validateAttributes();
} catch (MissingRequiredAttributeException $e) {
    $e->attributeName; // 'rating'
}

validateAttributes() prejde všetky definície platné pre model, globálne aj deklarované na modeli. Pri prvom chýbajúcom povinnom atribúte vyhodí MissingRequiredAttributeException a každú prítomnú hodnotu znova overí voči typu a pravidlám — pri zapnutom validation.whole_set celú uloženú množinu naraz, až po kontrole povinných atribútov.

Predvolené hodnoty

Predvolenú hodnotu z definície vracajú čítania, keď atribút nie je pripojený. Uplatňuje sa len pri čítaní — nikdy sa neukladá a hromadné getAttachedAttributes() obsahuje len uložené riadky:

Attributes::define(new AttributeDefinitionData('retries', AttributeType::Integer, default: 3));

Attributes::for($product)->get('retries'); // 3 when not attached, the stored value when set
$product->attributeInt('retries');         // 3
$product->attr('retries')->int();          // 3

Attributes::for($product)->set('retries', null);
Attributes::for($product)->get('retries'); // null — an explicitly stored null is not replaced

Atribút výslovne uložený s hodnotou null sa prečíta ako null — predvolená hodnota nastupuje len za atribút, ktorý nie je pripojený.

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.