Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Jádro / Volání API

Volání API#

Front ani administrace nesahají do databáze. Data si vyžádají od Api vrstvy přes most app/Core/Bridge/ApiBridge.php — a je jedno, jestli Api běží v témž procesu, nebo jako samostatná služba.

Celý tok#

Front / Admin presenter nebo komponenta
        │  $manager->findAll(…)->execute()
Manager → Service → Mapper → Repository
        │  $this->bridge->call($apiEndpoint . '/get-all', $params, null, 'POST')
ApiBridge::call()
        ├── internal ──► sestaví Api presenter a zavolá get*Data(), BEZ HTTP
        └── external ──► HTTP požadavek na /api/<sekce>/<presenter>/<akce>
                        Api presenter → pole → (JSON)
Mapper → EntityHydrator → Front / Admin entita

Hydrataci odpovědi popisují Hydrátory, vrstvy Pětivrstvý model.

Konvence endpointů#

Repository nese apiEndpoint (api/blog/news) a metody se na sub-akce mapují pevně:

Metoda repository Endpoint
find($id) <endpoint>/get
findBy($filters) <endpoint>/get-by
findAll($filters, …) <endpoint>/get-all
save($data) <endpoint>/save
updateColumns($ids, $values) <endpoint>/update-columns
delete($id) / deleteAll($filters) <endpoint>/delete / /delete-all
přesun ve stromu <endpoint>/move-subtree, /delete-node

Admin repository volá /get-all, ne /find-all

Je to častý překlep při psaní nového modulu — a projeví se prázdným výpisem, ne chybou.

Interní versus externí volání#

Interní (výchozí) Externí
Jak sestaví se presenter a zavolá get*Data() HTTP požadavek
Rychlost bez serializace a bez sítě s režií
Kdy admin, front, CLI Api jako samostatná služba, cizí klient
Co běží createPresenter()get*Data() run()startup()action*()get*Data()

Výchozí typ je internal (config/Shared/api.neonapiBridge.defaultCallType).

Na interní cestě NEBĚŽÍ startup()

A s ním ani nic, co je v něm — gate na externí cestě, CORS, kontrola JWT. Proto je autorizační guard v get*Data(), kudy prochází obojí. Kontrola napsaná do startup() nebo do action*() pro admin a front neplatí. Podrobně RBAC v API.

Interní volání dnes NEobchází autorizaci

Bývalo to tak. Dnes běží guard i na interní cestě (apiRbac.internalMode: enforce), takže admin a front dostanou Permission denied úplně stejně jako cizí klient — jen ho dostanou jako Result, ne jako HTTP 403.

Selhaná odpověď: kde se z ní stane „data nejsou“#

Tohle je nejdůležitější věc na celém mostu. Most obsluhuje dva neslučitelné kontrakty:

Kontrakt Volající Co znamená obálka selhání
„dej mi data“ find(), findBy(), findAll() vada → musí se z ní stát prázdno
„proveď a řekni, jestli to prošlo“ save(), delete(), updateColumns(), strom smysl volání — 83 míst v app/ čte ['success']

Proto most obálku nepřepisuje, jen ji přestane tajit: zapíše API_FAILSOFT source=bridge … do auth kanálu. Rozhodnutí „tohle měla být data“ dělá až FailSoftReadTrait ve čtecích metodách repository — a druhou pojistkou je hydrátor, který selhanou obálku odmítne prohlásit za entitu.

Bez toho by z obálky selhání vznikla nesmyslná entita

V hydrátoru TypeError (klíč code je číslo, setter čeká ?string), mimo něj tichá prázdná stránka bez jediného řádku v logu. Proto ta obrana ve dvou vrstvách.

Když stránka ukazuje prázdno, hledej API_FAILSOFT v auth logu

Řádek nese endpoint, cestu (internal/external) i volajícího. Je to rozdíl mezi „data opravdu nejsou“ a „nedostal jsi je“.

Postup: přidávám nové čtení#

  1. Api presenter dostane get<Akce>Data() s guardem oprávnění na prvním řádku (viz RBAC).
  2. Klíč oprávnění do databáze — bez něj je to fail-closed DENY.
  3. Front/Admin repository dostane metodu, která zavolá bridge->call() nad apiEndpoint.
  4. Mapper odpověď zhydratuje; service a manager ji jen propustí.
  5. Ověř obě cesty — přes stránku (interní) i přímým HTTP voláním, pokud endpoint má být veřejný.

Api manager volaný napřímo obchází zpracování v presenteru

Ořez polí podle oprávnění, doplnění vlastníka i normalizace payloadu jsou v get*Data() / actionSave(), ne v manageru. Volání manageru napřímo je proto v pořádku jen tam, kde to je záměr (cron), a nikdy jako náhrada mostu z frontu — projeví se to tím, že se prázdná asociace tiše neuloží.

Cache#

Volání se dá jednorázově vyjmout z cache:

$payload = $this->bridge->call('api/blog/article/get-all', [
    'filters' => $filters,
    ApiBridge::PARAM_USE_CACHE => false,
], null, 'POST');

Řídicí klíč se z parametrů odstraní na OBOU cestách

Kdyby zůstal, externě by se odeslal jako běžný parametr požadavku — a stal by se z něj vypínač cache ovladatelný zvenčí. Do Api metody se proto nedostane a nesmí být v její signatuře, jinak skončí Unknown named parameter.

Podrobně Cache.

CLI a harnessy#

Skript přes most nemá session, takže běží jako guest

Ve vynucujícím režimu nedostane data — a nevrátí chybu autorizace, vrátí prázdno, protože „Permission denied“ zůstane uvnitř mostu. Test nad tím projde zeleně a nic netestuje. Řešení: ApiBridge::setInternalAuthToken($jwtManager->generateToken(…)).

Cron tím dotčený není

Injektuje Api managery přímo, ne přes most.

Kam sáhnout#

Chci Kde
most a jeho konfiguraci app/Core/Bridge/ApiBridge.php, config/Shared/api.neon
mapování metod na endpointy app/Core/Base/Front/Models/Repositories/ApiRepository.php
zápisové metody repository app/Core/Traits/Shared/Models/Repositories/{Save,Delete,Tree}Trait.php
převod selhání na prázdno app/Core/Traits/Shared/Models/Repositories/FailSoftReadTrait.php
rozpoznání obálky selhání app/Core/Utils/Results/Result.phpisFailureEnvelope()

Navazující kapitoly: Pětivrstvý model · Hydrátory · RBAC v API · Cache