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

Kontrakt fasády

Každý balík Roundly vystavuje jedno verejné API v troch vrstvách — akcie (správanie), manažér (injektovateľný koreň fasády) a final fasáda — takže fanúšikovia fasád, používatelia DI aj tí, ktorí chcú holú akciu, spúšťajú ten istý kód. Tento kontrakt strážia tri expectations a jeden arch preset; štvrtá expectation pripína, že fasáda nad koreňom, ktorý prijíma tajomstvá, ich nenechá v rámcoch zásobníka volaní:

// tests/Feature/FacadeTest.php — bound to your PackageTestCase-based TestCase
expect(Teams::class)
    ->toDocumentItsRoot()                                  // docblock == root, class-string accessor, final
    ->toBeFakeable()                                       // real fake(), subtype of the root, DI gets it
    ->toReachEveryAction(__DIR__.'/../../src/Actions')     // every non-@internal action is reachable
    ->toRedactSensitiveArguments(methods: 3);              // only when the root marks #[SensitiveParameter]

// tests/Arch/ArchTest.php
ArchPresets::modelsGoThroughTheFacade('RoundlyConsulting\Teams');
use RoundlyConsulting\Testing\Arch\ArchPresets;
use RoundlyConsulting\Testing\Assert;

expect($facade)->toDocumentItsRoot(array $except = []);
expect($facade)->toBeFakeable();
expect($facade)->toReachEveryAction(string $actionsDir, array $except = [], array $via = []);
expect($facade)->toRedactSensitiveArguments(int $methods);
ArchPresets::modelsGoThroughTheFacade(string $namespace, array $ignoring = []);

// Static mirrors for plain PHPUnit
Assert::facadeDocumentsItsRoot(string $facade, array $except = []);
Assert::facadeIsFakeable(string $facade);
Assert::facadeReachesEveryAction(string $facade, string $actionsDir, array $except = [], array $via = []);
Assert::facadeRedactsSensitiveArguments(string $facade, int $methods);

toDocumentItsRoot()

Fasáda je final, getFacadeAccessor() vracia class-string manažéra alebo kontraktu — textový kľúč ako 'teams' zlyhá — a jej riadky @method static presne zodpovedajú koreňu: každá verejná metóda je zdokumentovaná, žiadna neexistujúca, každý počet parametrov sedí. Konštruktor, magické metódy, @internal a metódy zdedené z vendora (Manager::driver(), Macroable::macro()) sa nevyžadujú a zdokumentovaný názov môže žiť aj priamo na fasáde (fake()) alebo na fake (assert*()). $except uvádza metódy koreňa, ktoré zámerne zostávajú nezdokumentované.

Parametre sa počítajú s ohľadom na vnorenie, takže array<string, int>, array{a: int}, Closure(int, string): bool ani array $x = ['a' => 1] sa nikdy nezrátajú zle. Každé zlyhanie vráti riadok @method, ktorý stačí vložiť.

toBeFakeable()

Fasáda deklaruje skutočnú public static function fake(): XFake a XFake je vlastný podtyp typu accessora — inak by každý manažér injektovaný cez konštruktor pod fake vyhodil TypeError a samotný typ accessora fake nie je: fake(), ktorý namiesto seba nasadí skutočný manažér, nič nezaznamená. Jej zavolanie musí vytvoriť novú inštanciu a nasadiť ju ako koreň fasády aj ako app(<accessor>). Metódu fake() naozaj volá, preto potrebuje spustenú aplikáciu: naviažte test na svoj TestCase postavený na PackageTestCase. Skutočné naviazanie sa potom obnoví.

toReachEveryAction()

Na každú konkrétnu triedu bez @internal v $actionsDir musí odkazovať povrch fasády: koreň, trieda, na ktorú ho kontajner viaže, a každý sub-accessor či handle dosiahnutý cez verejné návratové typy — Teams::for($team)->members()->add() sú dva skoky. Modely, DTO, udalosti, enumy, výnimky, fake ani iné akcie nikdy nie sú povrchom.

  • Z kontroly akciu vyjme len skutočný docblock tag @internal. Akcia, ktorú používajú len iné akcie, sa stále počíta ako nedosiahnuteľná — označte ju @internal.
  • $via pridá ako ďalší koreň pomocníka, ktorého manažér drží, no nikdy nevracia. Každá položka musí existovať a sprístupniť aspoň jednu akciu, ktorá by bez nej dosiahnuteľná nebola, inak zlyhá ako zastaraná.
  • $except toleruje akciu, ktorá je naozaj nedosiahnuteľná; položka, ktorá nie je nedosiahnuteľnou akciou pre hostiteľa v $actionsDir, zlyhá.

toRedactSensitiveArguments()

PHP skryje argument s #[SensitiveParameter] len v rámci funkcie, ktorá atribút deklaruje. Volanie cez bežnú fasádu najprv prejde cez Facade::__callStatic($method, $args) a tento rámec nesie všetky argumenty v otvorenej podobe — Crypto::constantTimeEquals($known, $input) tak nechá $known tam, kde ho nájde error tracker zbierajúci argumenty rámcov, print_r($e), var_dump($e) či vlastný logger stack trace. getTraceAsString() vypíše tento rámec ako __callStatic('…', Array), takže test nad textom stack trace prejde aj pri fasáde, ktorá tajomstvo prezrádza. Táto expectation preto číta priamo argumenty rámcov:

// tests/Feature/FacadeTest.php — written before `use RedactsSensitiveArguments;`, it is the
// fail-first test: a stock facade fails it.
expect(Crypto::class)
    ->toDocumentItsRoot()
    ->toRedactSensitiveArguments(methods: 15);

Nápravou je trait Concerns\RedactsSensitiveArguments z balíka package-toolkit-for-laravel použitý na fasáde; prejdú aj skutočné statické metódy fasády s vlastnými atribútmi:

use Illuminate\Support\Facades\Facade;
use RoundlyConsulting\PackageToolkit\Concerns\RedactsSensitiveArguments;

final class Crypto extends Facade
{
    use RedactsSensitiveArguments;

    protected static function getFacadeAccessor(): string
    {
        return CryptoManager::class;
    }
}

$methods pripína, koľko verejných metód typu accessora — vrátane statických, bez magických __* — prijíma aspoň jeden argument s #[SensitiveParameter]. Musí byť aspoň 1: 0 alebo menej vyhodí InvalidArgumentException, pretože pripnutie na 0 prejde aj nad koreňom, ktorý nič neoznačuje. Fasáda, ktorej koreň neprijíma žiadne tajomstvo, nemá čo skrývať — tam expectation nevolajte. Všetky problémy sa vypíšu v jednej hláške:

  • Počet — $methods sa musí rovnať tomuto číslu a zlyhanie vypíše nájdené metódy. Koreň, ktorý neoznačuje žiadny parameter, zlyhá s hláškou „nothing to redact“.
  • Rozchod kontraktu — fasáda vidí len atribúty typu accessora. Ak spustená aplikácia pod accessorom viaže inú triedu (kontrakt alebo podtriedu), obe musia označovať rovnaké parametre na rovnakých pozíciách a verejná metóda, ktorú deklaruje len implementácia, nesmie označovať nič. Pri nasadenom fake() sa kontrola pozrie na implementáciu, ktorú rozširuje.
  • Skutočný stack trace — so zend.exception_ignore_args vypnutým počas kontroly (produkčné ini súbory ho zapínajú) sa za fasádu nasadí skúšobný koreň, ktorý pri každom volaní vyhodí výnimku. Každá citlivá metóda sa cez fasádu zavolá dvakrát, pozične aj s pomenovanými argumentmi, s jedinečnou skúšobnou hodnotou pre každý argument. Vlastný rámec fasády musí byť v stack trace aj s argumentmi, žiadny rámec nesmie obsahovať tajomstvo v otvorenej podobe — zachytí sa teda aj pomocník, cez ktorý fasáda volanie preposiela — a každá neškodná hodnota musí v rámci fasády zostať viditeľná.
  • Nič skryté navyše — každá ďalšia verejná metóda koreňa s argumentmi sa zavolá raz a všetky argumenty musia zostať viditeľné: fasáda, ktorá skryje každý argument, prejde kontrolou úniku, no touto zlyhá.

Kontrola nemá vedľajšie účinky: koreň fasády, záznam accessora v kontajneri aj hodnota ini sa potom obnovia, aj keď kontrola zlyhá, a nasadený fake sa vráti na miesto a naďalej zaznamenáva skutočné hodnoty. Spúšťajte ju ešte pred nasadením fake — fake bez implementácie za sebou zlyhá. Metóda koreňa, ktorú deklaruje už samotná Facade, napríklad swap() či spy(), sa cez fasádu ku koreňu nikdy nedostane, preto zlyhá s uvedením svojho názvu.

ArchPresets::modelsGoThroughTheFacade()

Nič v {ns}\Models, {ns}\Concerns ani {ns}\Traits, žiadny Eloquent model kdekoľvek v {ns} — vrátane rozloženia podľa oblastí ako Shops\Cart\Cart — ani žiadny trait balíka, ktorý taký model používa (rekurzívne, nech je kdekoľvek), nesmie odkazovať na {ns}\Actions. $user->like() tak ide cez manažér a fake() fasády ho vidí. {ns}\Actions a {ns}\Testing sa nikdy neprehľadávajú; výnimky idú cez kontrolovaný parameter $ignoring.

Žiadne prázdne zelené

Spolu zachytia fasády bez jediného riadku @method nad skutočným manažérom, docblocky s premenovanými metódami, fake, ktorý rozbije dependency injection, fake obídený traitmi modelov, akcie pre hostiteľa, ku ktorým sa žiadna fasáda nedostane, a tajomstvá, ktoré bežná fasáda nechá v otvorenej podobe v rámci __callStatic, hoci ich koreň označuje #[SensitiveParameter]. Žiadna z nich nemôže prejsť naprázdno: prázdny či chýbajúci adresár akcií, docblock bez riadku @method, koreň bez čoho dokumentovať či bez tajomstva na skrytie aj menný priestor bez modelov zlyhajú — a každá položka $except, $via a $ignoring musí stále niečo umlčať.

Testing for Laravel sám žiadnu fasádu nemá: je to testovacia výbava len na vývoj bez stavového správania pre hostiteľa, čo je presne prípad, ktorý konvencia vyníma.

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.