---
title: "Architektonické presety — Testing for Laravel | Roundly"
description: "Jedenásť jednoriadkových Pest arch presetov — strict types, final ako predvolba, seamy modelov aj factories, zákazy závislostí, modely cez fasádu a ďalšie."
url: https://roundly-consulting.com/sk/open-source/navody/testing-for-laravel/architektonicke-presety
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Testing for Laravel](https://roundly-consulting.com/sk/open-source/navody/testing-for-laravel.md)

# Architektonické presety

Jedenásť skladateľných presetov, každý postavený na chybe, ktorá sa reálne dostala do vydania. Zavolajte ho na najvyššej úrovni Pest arch súboru; test si zaregistruje sám. Arch súbor naviažte na svoj TestCase postavený na PackageTestCase — uses(TestCase::class)->in('Arch', …) v tests/Pest.php, ako v Inštalácii: swappableModelsAreNotFinal číta predvolenú hodnotu konfigurácie zo spustenej aplikácie a bez nej zlyhá s návodom.

```php
use RoundlyConsulting\Testing\Arch\ArchPresets;

ArchPresets::strictTypes(string $namespace, array $ignoring = []);
ArchPresets::finalByDefault(string $namespace, array $ignoring = []);
ArchPresets::swappableModelsAreNotFinal(array $map);               // [Shop::class => 'shops.shop_model']
ArchPresets::noLocalCryptoPrimitives(string $namespace, array $ignoring = []);
ArchPresets::modelsResolveThroughSeam(string $srcDir, string $seamDir = 'Support', array $modelKeys = []);
ArchPresets::factoriesResolveThroughSeam(string $factoriesDir, string $srcDir, string $seamDir = 'Support'); // opt-in
ArchPresets::morphColumnsUseTheSeam(string $migrationsDir);        // no raw $table->morphs()
ArchPresets::runtimeRequireIsWhitelisted(string $composerJson, array $alsoAllow = []);
ArchPresets::noDebuggingLeftovers(array $ignoring = [], ?string $srcDir = null); // dd/dump/ray/var_dump/print_r, ->dd()
ArchPresets::noVendorNamespace(string|array $prefixes, ?string $srcDir = null, array $ignoring = []); // no GuzzleHttp\…, App\…
ArchPresets::modelsGoThroughTheFacade(string $namespace, array $ignoring = []); // models/traits never call actions

// Helpers the presets build on
ArchPresets::shadowedClassesAreFinal(string $namespace, array $exemptions); // only if you use ->ignoring()
ArchPresets::exemptionsExist(array $exemptions, string $for, ?string $within = null); // the pin the presets register for you
```

## Čo ktorý stráži

| Preset | Pravidlo — a chyba, ktorej predchádza |
| --- | --- |
| `strictTypes` | Každý súbor deklaruje strict\_types=1, takže sa nevkradne tichá konverzia typov. |
| `finalByDefault` | Triedy sú predvolene final; abstraktné triedy sa vylúčia automaticky (abstract final je v PHP fatálna chyba). Predchádza nechceným bodom rozšírenia. |
| `swappableModelsAreNotFinal` | Každý namapovaný model nie je final a jeho konfiguračný kľúč predvolene ukazuje práve naň. final na vymeniteľnom modeli spôsobil fatálnu chybu PHP hneď pri výmene — a vyšiel sedemkrát. |
| `noLocalCryptoPrimitives` | Žiadne lokálne kryptografické primitíva (ArchPresets::CRYPTO\_PRIMITIVES — hash, hash\_hmac, hash\_pbkdf2, hash\_hkdf, openssl\_\*, sodium\_\*, random\_bytes, random\_int, base64\_\* a ďalšie). hash\_equals je zámerne povolené: je to porovnanie v konštantnom čase priamo v PHP. hash\_hkdf je zakázané od verzie 1.4.0 — presuňte ho na Crypto::hkdf() z crypto-for-laravel, ktoré algoritmus aj dĺžku preberá explicitne. |
| `modelsResolveThroughSeam` | V statickom kontexte modelu žiadne volanie static:: ani self::, ktoré nad danou triedou zostaví dopyt — query(), ďalšie vstupné body modelu (all, with, on, onWriteConnection, destroy) a akákoľvek metóda buildera, ktorú preposiela \_\_callStatic, napríklad where(), create() či firstOrCreate() —, ani new static; new self nikde v modeli; konfiguračné literály výmeny sa smú vyskytnúť len v adresári seamu. Takéto obídenie raz rozbilo autorizáciu. Od verzie 1.5.0 mimo seamu navyše žiadna relácia nesmie pomenovať vymeniteľný model jeho triedou z balíka (belongsTo(Comment::class)) a žiadne statické volanie nad ním nesmie začať dopyt (Comment::query(), Comment::where(), Comment::withTrashed()). |
| `factoriesResolveThroughSeam` | Voliteľný. Každá factory v $factoriesDir, ktorej model je vymeniteľný, prepíše modelName() tak, aby vracala seam daného modelu (return LikeModel::class();). Bez toho Like::factory() aj po výmene u hostiteľa vytvorí triedu z balíka — v tabuľke balíka a bez castov, scopes či udalostí hostiteľa. |
| `morphColumnsUseTheSeam` | Žiadne surové morphs(), nullableMorphs(), numericMorphs(), nullableNumericMorphs() ani ich uuid/ulid varianty v adresári migrácií — polymorfné stĺpce musia ísť cez makro morphKey() z package toolkitu. Chýbajúci alebo prázdny adresár zlyhá. |
| `runtimeRequireIsWhitelisted` | require v composer.json obsahuje len php a ďalšie platformové balíky Composera (ext-\*, lib-\*, composer-runtime-api, composer-plugin-api, php-64bit, …), illuminate/\*, laravel/\*, symfony/\* a roundly-consulting/\*, plus presné názvy v $alsoAllow. Každý nepovolený vendor sa uvedie menom, rovnako ako každá položka $alsoAllow, ktorá nič neodpúšťa — pozrite Výnimky z presetov. |
| `noDebuggingLeftovers` | Žiadne dd, dump, ray, var\_dump ani print\_r — ani pomocníci, ktorých Laravel vešia na vlastné objekty: ->dd(), ->ddRawSql(), ->dumpRawSql() — a to v akejkoľvek veľkosti písmen (DD() spustí dd()). Hľadá sa v tokenoch zdrojového kódu, predvolene v <cwd>/src, takže ray() zachytí aj bez nainštalovaného balíka debuggera Ray. Reťazené ->dump() je zámerne povolené, keďže $yaml->dump() je bežné API, rovnako ako new Dump(…) a Ray::make(). |
| `noVendorNamespace` | Nič v $srcDir (predvolene <cwd>/src) nepomenúva menný priestor rovný niektorému z $prefixes ani pod ním — importy, plne kvalifikované aj kvalifikované názvy, preložené cez menný priestor a importy súboru. Zachytí, čo ->not->toUse() prehliadne: súrodenecké balíky vendora, nenainštalovaných vendorov a mená hostiteľa ako App\\. |
| `modelsGoThroughTheFacade` | Žiadna metóda ani trait modelu neodkazuje na menný priestor Actions balíka, takže každá zmena ide cez manažér a fake() fasády ju vidí — pozrite Kontrakt fasády. |

strictTypes, finalByDefault a noLocalCryptoPrimitives stoja na arch vrstve Pestu a vracajú arch expectation. Zvyšných osem vyjadruje to, čo arch vrstva Pestu nedokáže, a namiesto toho registruje test it() založený na tokenoch či reflexii.

Vlastný arch test Pestu prejde aj nad prázdnou množinou, preto každý z týchto troch presetov viazaných na menný priestor registruje aj sprievodný test („preset: … has something to check“), ktorý zlyhá, keď sa menný priestor cez PSR-4 mapu Composera nepreloží na nič (jedno preklepnuté písmeno) alebo keď $ignoring vylúči všetko, čo obsahuje.

## Typický arch súbor

```php
<?php

declare(strict_types=1);

use RoundlyConsulting\Shops\Exceptions\ShopsException;
use RoundlyConsulting\Shops\Shops\Shop;
use RoundlyConsulting\Testing\Arch\ArchPresets;

ArchPresets::strictTypes('RoundlyConsulting\Shops');

// Exempt the intentional extension points through the checked parameter…
ArchPresets::finalByDefault('RoundlyConsulting\Shops', [Shop::class, ShopsException::class]);

// …and pin the swappable model with the counter-weight preset.
ArchPresets::swappableModelsAreNotFinal([Shop::class => 'shops.shop_model']);

ArchPresets::noLocalCryptoPrimitives('RoundlyConsulting\Shops');
ArchPresets::modelsResolveThroughSeam(__DIR__.'/../src', 'Support');
ArchPresets::morphColumnsUseTheSeam(__DIR__.'/../database/migrations');
ArchPresets::runtimeRequireIsWhitelisted(__DIR__.'/../composer.json');
ArchPresets::noDebuggingLeftovers();
ArchPresets::noVendorNamespace(['GuzzleHttp', 'App']);
ArchPresets::modelsGoThroughTheFacade('RoundlyConsulting\Shops');
```

## Zámerné napätie

finalByDefault chce mať každú triedu final; swappableModelsAreNotFinal zakazuje final na modeli vymeniteľnom cez konfiguráciu. Toto napätie je zámerné — spúšťajte oba: vymeniteľné modely z prvého vylúčte cez $ignoring a druhým ich pripnite. Prázdna mapa zlyhá, namiesto toho aby prešla naprázdno. Rovnaká kontrola existuje aj pre jednotlivý model ako expectation a ako statické zrkadlo:

```php
expect(Shop::class)->toBeSwappableVia('shops.shop_model');

// The same check for plain PHPUnit
Assert::modelIsSwappableVia(Shop::class, 'shops.shop_model');
```

Všetky tri čítajú predvolenú hodnotu konfigurácie, preto potrebujú spustenú aplikáciu — naviažte súbor na svoj TestCase postavený na PackageTestCase.

## Deklarácia kľúčov pre modelsResolveThroughSeam

Bez deklarácie odvodzuje polovica pre zatúlané literály kľúče výmeny z ich tvaru — model, models alebo \*\_model — a stráži len tie. Ak má niektorý váš kľúč výmeny iný tvar, zoznam deklarujte. Deklarované kľúče sa zjednotia s odvodenými a deklarovaný kľúč, ktorý nezodpovedá žiadnemu literálu vo vašom kóde, zlyhá:

```php
ArchPresets::modelsResolveThroughSeam(__DIR__.'/../src', 'Support', [
    'alerts.alert', 'alerts.health-check', 'alerts.silence-model', 'alerts.history.model',
]);
```

Polovica pre neskoré statické viazanie sa uplatní len v statickom kontexte, s jednou výnimkou: new self vždy vytvorí triedu balíka, preto sa nahlási kdekoľvek v modeli. V inštančnej metóde self::query() už pracuje s triedou $this za behu — teda s nakonfigurovanou — takže je správne a nenahlási sa. Nenahlási sa ani statické volanie vlastných statických metód modelu či pomocníkov triedy — static::creating(), static::addGlobalScope(), self::saving(), teda tvar každého hooku booted(). Zákaz sa navyše týka len tried, ktoré rozširujú Model.

## Trieda z balíka zostáva v seame

Vymeniteľný model je taký, ktorý môže hostiteľ cez konfiguráciu (comments.model) nahradiť vlastnou podtriedou. Mimo seamu musí kód používať triedu, ktorú seam vráti — pomenovanie triedy z balíka vytvorí, prepojí alebo dopytuje nesprávnu triedu. comments 1.1.0 vyšiel s dvoma takými obídeniami pod zeleným modelsResolveThroughSeam: CommentMention::comment() bolo belongsTo(Comment::class) a AssertsComments dopytoval Comment::query(). Od verzie 1.5.0 preset zachytí oboje a factoriesResolveThroughSeam uzatvára rovnakú dieru v model factories:

```php
ArchPresets::modelsResolveThroughSeam(__DIR__.'/../src', 'Support', ['comments.model']);
ArchPresets::factoriesResolveThroughSeam(__DIR__.'/../database/factories', __DIR__.'/../src'); // opt-in
```

## Ktoré modely sú vymeniteľné

Oba presety čítajú vymeniteľné modely z vlastného seamu balíka, nie z konfigurácie: preloženie config('comments.model') potrebuje spustenú aplikáciu a konvencia pomenovania by niektoré seamy pokryla a iné nie. Model je vymeniteľný, keď je metóda v $seamDir statická, nemá parametre, deklaruje návratový typ string a jej telo pomenúva Eloquent model cez X::class popri literáli konfiguračného kľúča — ModelResolver::for('comments.model', Comment::class), config('x.model', X::class) alebo ručne napísané načítanie s kontrolou is\_a. Metóda sa môže volať akokoľvek (CommentModel::class(), TicketModels::ticket(), SluggableConfig::historyModel()) a konštanta self::KEY či self::DEFAULT sa počíta ako hodnota, ktorú obsahuje. Rozhranie, samotný Model z Eloquentu, inštančná metóda, metóda s parametrami a metóda bez kľúča seamom nie sú. Pri meraní 17. 10. 2026 pravidlo našlo presne tie modely, ktoré každý z 31 balíkov, ktoré ho používajú, uvádza vo svojej mape pre swappableModelsAreNotFinal() — 66 modelov.

Čo nevidí: seam, ktorý kľúč alebo predvolený model preberá ako argument, číta ich z inej triedy alebo ich skladá za behu. Deklarované položky $modelKeys z toho robia zlyhanie — každú musí čítať metóda seamu popri jej predvolenom modeli. Nedeklarovaný kľúč odvodený z tvaru sa tomu nepodriaďuje, pretože odvodenie zachytí aj kľúče, ktoré nevymieňajú nič z balíka, napríklad auth.providers.users.model. Ak chcete, aby sa pravidlo uplatnilo, kľúče deklarujte.

## Relácie a statické volania nad triedou z balíka

Mimo $seamDir modelsResolveThroughSeam pri každom vymeniteľnom modeli zlyhá na:

- Relácii, ktorej argumentom je trieda z balíka — belongsTo(Comment::class), hasMany(Comment::class, 'parent\_id'), prechodný model v hasManyThrough(Reply::class, Comment::class), belongsToMany(related: Comment::class), self::class v Comment či parent::class v podtriede z balíka, a to aj cez alias alebo plne kvalifikovaný názov. Pokrýva belongsTo, hasOne, hasMany, belongsToMany, morphOne, morphMany, morphToMany, morphedByMany, hasOneThrough a hasManyThrough a počíta sa akýkoľvek argument na najvyššej úrovni.
- Statickom volaní, ktoré nad triedou z balíka začne dopyt — Comment::query(), all(), with(), destroy(), where(), find(), create(), firstOrCreate(), updateOrCreate(), count(), withTrashed(), onlyTrashed() alebo lokálny scope ako Comment::approved(). Volanie začne dopyt, keď ho \_\_callStatic modelu prepošle — teda pri každej metóde, ktorú model nedeklaruje —, a počítajú sa aj vlastné vstupné body modelu query, all, with, on, onWriteConnection a destroy. Ak sa model nedá autoloadnúť, preset použije verejné metódy Eloquent a query buildera spolu s makrami soft delete, bez toho, čo deklaruje Model.

Povolené: samotné Comment::class (instanceof, is\_a(), morph mapa, mapa policies), statická metóda, ktorú model deklaruje (Comment::creating(), Comment::observe(), jeho vlastný pomocník), static::class, čokoľvek v $seamDir a každý nevymeniteľný model. Docblocky a reťazce sa nikdy nepočítajú. Namiesto toho použite seam:

```php
return $this->belongsTo(CommentModel::class(), 'comment_id');
$query = CommentModel::class()::query()->where('commentable_id', $id);
```

Každé zistenie uvedie súbor, riadok a volanie — „These relations name a swappable model by its packaged class outside the 'Support' seam“ a potom - <path>:<line> belongsTo(Comment::class). Zlyhá aj deklarovaný kľúč, ktorý sa v seame číta, ale žiadnou metódou, ktorá by pomenovala jeho predvolený model. Balík bez vymeniteľného modelu týmito dvoma polovicami prejde; polovice pre neskoré statické viazanie a zatúlané literály bežia naďalej.

## factoriesResolveThroughSeam: factories vytvárajú nakonfigurovaný model

Eloquent factory vytvára svoj $model — triedu z balíka —, pokiaľ modelName() neurčí inak, takže Comment::factory() aj po výmene u hostiteľa vytvorí Comment z balíka, v tabuľke balíka a bez castov, scopes či udalostí hostiteľa. Oprava je jedna metóda:

```php
/** @extends Factory<Like> */
final class LikeFactory extends Factory
{
    protected $model = Like::class;

    public function modelName(): string
    {
        return LikeModel::class();
    }
}
```

Factory je každá konkrétna trieda v $factoriesDir, ktorá rozširuje Illuminate\\Database\\Eloquent\\Factories\\Factory, priamo alebo cez základnú factory v tom istom adresári. Jej model určuje predvolená hodnota protected $model = X::class; a factory bez $model, ktorej modelName() vracia seam, sa číta ako factory modelu tohto seamu. Factory vymeniteľného modelu musí deklarovať modelName(), ktorej celé telo je return <Seam>::<method>(); pre daný model — chýbajúce prepísanie, vrátenie $this->model či X::class alebo seamu iného modelu zlyhá a uvedie seam, ktorý treba vrátiť. modelName() len v docblocku sa nepočíta a factories modelov, ktoré nikto nevymieňa, sa nekontrolujú.

Preset je voliteľný, nie súčasť modelsResolveThroughSeam, ktorý si každý balík, ktorý ho používa, zoberie pri najbližšom behu: keď vznikol, prepísanie chýbalo 58 factories v 24 balíkoch. Zapnite ho v balíku, ktorého factories vytvárajú vymeniteľný model, spolu s opravou. Test sa volá „preset: factories build swappable models through the Support seam“ a naprázdno prejsť nemôže: chýbajúci adresár, seam bez vymeniteľného modelu, adresár bez triedy factory, factories, z ktorých žiadna nevytvára vymeniteľný model, aj factory, ktorej model sa nedá určiť, zlyhajú.

## noVendorNamespace: zákaz, ktorý toUse() nevyjadrí

\->not->toUse('GuzzleHttp') zostane zelené v troch prípadoch, pretože arch vrstva Pestu rozvinie názov len na triedy pod nainštalovaným PSR-4 koreňom na rovnakej alebo vyššej úrovni a závislosť si ponechá, len ak jej symbol existuje:

- Súrodenecký balík pod prefixom vendora — GuzzleHttp\\Psr7 a GuzzleHttp\\Promise sú samostatné balíky s vlastnými koreňmi, takže holé pravidlo zostane zelené aj s use GuzzleHttp\\Psr7\\Utils; v src/.
- Vendor, ktorý nie je nainštalovaný — nie je do čoho rozvinúť.
- Menný priestor hostiteľa — balík nemá koreň App\\, takže ->not->toUse('App') nekontroluje nič.

noVendorNamespace namiesto toho číta názvy z tokenov zdrojového kódu, takže zachytí všetky tri:

```php
ArchPresets::noVendorNamespace(['GuzzleHttp', 'App'], __DIR__.'/../../src');
ArchPresets::noVendorNamespace('Mockery', __DIR__.'/../../src', [LegacyBridge::class]);
```

Názov rovný prefixu alebo pod Prefix\\ zlyhá kdekoľvek v kóde: v importe use (obyčajnom, s aliasom, zoskupenom, use function, use const), v plne kvalifikovanom názve (new \\GuzzleHttp\\Client, #\[\\Vendor\\Attribute\], extends, catch) aj v kvalifikovanom názve preloženom cez import (Psr7\\Utils po use GuzzleHttp\\Psr7;). Kvalifikovaný názov bez zodpovedajúceho importu sa preloží do vlastného menného priestoru súboru, use traitu v tele triedy je odkaz a čítajú sa aj menné priestory v zložených zátvorkách a globálny menný priestor. Prefixy sa porovnávajú bez ohľadu na veľkosť písmen a končia na oddeľovači menného priestoru, takže Guzzle nezodpovedá GuzzleHttp\\…. Komentáre, docblocky, reťazcové literály ako class\_exists('GuzzleHttp\\Client') a názvy členov sa nepočítajú.

$prefixes prijme jeden menný priestor alebo zoznam a okrajové spätné lomky sa orežú; $srcDir je predvolene <cwd>/src. Položka $ignoring preskočí súbor, ktorého prvá deklarovaná trieda, trait, rozhranie či enum jej zodpovedá presne alebo ako prefix menného priestoru, a kontroluje sa proti zastaraniu ako každá iná výnimka. Test sa volá „preset: never references <prefixes>“ a každé zistenie uvedie súbor, riadok a názov:

```
These files reference a banned namespace (GuzzleHttp\):
  - Http/Client.php:12 GuzzleHttp\Psr7\Utils
Remove the dependency, or reach it through the API the package is allowed to depend on.
```

Naprázdno prejsť nemôže: chýbajúci zdrojový adresár, adresár bez PHP súboru aj prefix, ktorý pokrýva vlastný menný priestor prehľadávaného kódu, zlyhajú a prázdny zoznam prefixov či prázdny prefix vyhodí InvalidArgumentException.

## 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](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## 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.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/testing-for-laravel.md)
