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ácii | Prípad enumu | Vynucuje |
|---|---|---|
| none, false, prázdne alebo neuvedené | UniqueScope::None | Nič — predvolené. |
| owner alebo true | UniqueScope::Owner | Jedinečnosť medzi vlastníkmi rovnakého typu modelu — naprieč všetkými tímami či tenantmi. |
| scope | UniqueScope::Scope | Jedinečnosť medzi vlastníkmi rovnakého typu modelu, ktorí patria k rovnakému modelu rozsahu — tímu, tenantovi. |
| global | UniqueScope::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 DuplicateAttributeValueExceptionJeden 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 replacedAtribú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 kryptomienOdoslaní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.