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.neon → apiBridge.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í#
- Api presenter dostane
get<Akce>Data()s guardem oprávnění na prvním řádku (viz RBAC). - Klíč oprávnění do databáze — bez něj je to fail-closed DENY.
- Front/Admin repository dostane metodu, která zavolá
bridge->call()nadapiEndpoint. - Mapper odpověď zhydratuje; service a manager ji jen propustí.
- 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.php → isFailureEnvelope() |
Navazující kapitoly: Pětivrstvý model · Hydrátory · RBAC v API · Cache