Přeskočit obsah
V
Pro vývojáře
Architektura, konvence, jádro systému a bezpečnost
Začínáme / Instalace a spuštění

Instalace a spuštění#

Požadavky#

Co Verze / poznámka
PHP ≥ 8.3 (composer.json); ověřeno na 8.4 a 8.5, viz PHP 8.5
MySQL / MariaDB
Composer
Node.js a npm pro sestavení assetů
Redis doporučené, dá se vypnout
Elasticsearch volitelné — bez něj hledání spadne na SQL
ffmpeg / ffprobe pro video v galeriích — bez ffprobe se video odmítne, obrázky fungují dál
memory_limit PHP-FPM aspoň 256M (pro obrázky do 40 Mpx kolem 320M), CLI víc nebo -1; viz Provozní požadavky

Postup: rozjíždím projekt poprvé#

  1. Závislosti
composer install
npm install
  1. Konfigurace — zkopíruj vzor a vyplň skutečné hodnoty:
cp config/Shared/local.neon.example config/Shared/local.neon
openssl rand -hex 32          # JWT secret

Do local.neon patří přístup k databázi (mariadb.* — jediný zdroj pravdy, používá ho Doctrine i nette/database) a jwt.secret. Includuje se jako poslední v config/common.neon, takže přebíjí všechny placeholdery.

  1. Databáze — základní schéma je v docs/sql/. Změny schématu jsou ručně psané opakovatelné skripty v sql/; na nasazeném serveru je při nasazení pouští bin/migrate.php (viz Checklist nasazení). Na vlastní databázi, kde evidence ještě není, pusť první běh ručně jako php bin/migrate.php --baseline: založí evidenci a starší skripty zapíše jako hotové. Databázi je proto nutné před ním dorovnat ručně. Bez --baseline skript na databázi bez evidence skončí chybou.

  2. Redis — na hostingu bez Redisu odkomentuj v local.neon:

redis:
    storage: false
    sessions: false
jwt:
    useRedis: false          # deny-list půjde do DB (potřebuje cron cleanup)
ratelimit:
    useRedis: false
  1. Assety
npm run build:admin        # administrace

Front téma se sestavuje ze své složky — viz Témata frontendu.

  1. Práva k zápisu — temp/, log/, var/ a složky pro nahrané soubory.

  2. Cron token nastav v administraci (1/cron.token) dřív, než zapojíš crony — bez něj vrací všechny úlohy 403.

  3. Naplánované úlohy podle Checklistu nasazení a Naplánovaných úloh.

local.neon nepatří do gitu

Obsahuje hesla k databázi a JWT secret. V .gitignore je (/config/local.neon i /config/Shared/local.neon) — zkontroluj to dřív, než něco commitneš.

validateClass není migrace

Ověření entity proti schématu jen řekne, co nesedí. Změnu ve schématu musíš provést sám skriptem v docs/sql/.

Postup: zapínám ladicí režim na vývojovém stroji#

touch config/debug-mode.flag      # gitignored, existence = debug ON

Nebo přes proměnnou prostředí NETTE_DEBUG=1, která má přednost. Produkční výchozí stav je vypnuto; podrobně Checklist nasazení.

Ladicí režim se nenastavuje v local.neon

Rozhoduje se o něm v App\Bootstrap::resolveDebugMode(), tedy před načtením konfigurace. Cokoli napsaného do NEONu nemá na debug žádný vliv.

Konfigurace#

Soubor Obsah
config/common.neon kostra, includuje zbytek
config/Shared/*.neon sdílená konfigurace po oblastech (25 souborů)
config/Shared/local.neon 🔴 tajemství a lokální přepisy, mimo git
config/debug.neon co platí jen v ladicím režimu
config/Admin/, config/System/ konfigurace jednotlivých sekcí

Část nastavení je v databázi, ne v souborech — čte je SettingManager a v runtime SettingContext.

SettingContext má dvě vrstvy

setSettings() je pro managery no-op. Ve skriptech musíš zapsat i setServerSettings(), jinak nastavení nedorazí tam, kam čekáš.

Vrstva Plní ji Obsah Čte ji
klientská (setSettings()) presenter při startu na frontu jen veřejná podmnožina getPublicSettings() — jediné, co smí do šablony nebo po drátě
serverová (setServerSettings()) Api SettingManager ve svém konstruktoru plná mapa: neon params + publicParams + všechny řádky DB get() a getSettings(); serverová vrstva vyhrává, klientská je záloha

Proč dvě: settingContext je DI singleton sdílený frontovým presenterem a Api managery, které se v tomtéž požadavku volají přes ApiBridge::internalCall() (a jejich startup() neběží). Serverový kód z něj čte SMTP údaje, appTempDir, emailTemplatePath nebo feedsDir. Hranice, kterou se tajemství nesmí dostat ven, je HTTP odpověď (endpoint getPublicMerged a Latte proměnná $settings), ne paměť serverového procesu.

Kontext nezužuj na veřejnou podmnožinu

Kdyby serverová vrstva obsahovala jen veřejné klíče, přestane odcházet pošta a spadne nahrávání přes dropzone — chybějící SMTP údaje a cesty se projeví až v jiném modulu.

Na front (klientovi) jde jen veřejná podmnožina — SettingManager::getAllPublic(), endpoint :Api:System:Setting:getPublicMerged:

Zdroj Na front
neon publicParams (config/Shared/parameters.neon) ano — krátký whitelist
neon params (adresáře, absolutní cesty) nikdy
řádek cms_system_settings s is_public = 1 ano
řádek cms_system_settings s is_public = 0 ne, jen serverová vrstva

Příznak is_public je fail-closed: v PHP i v DB (DEFAULT 0) je výchozí false a upsert přes SettingManager u nového řádku zapíše 0, u existujícího příznak nemění. Nový klíč je tedy soukromý, dokud ho někdo vědomě nezveřejní. Výjimka: nový řádek jazykové varianty (<klíč>.<alias jazyka>, např. site.name.de) zdědí is_public základního klíče, jinak by front u nového jazyka tiše ukazoval výchozí jazyk.

Klíč, který potřebuje šablona nebo JS, zveřejni v migraci

Bez is_public = 1 front hodnotu nedostane a čte prázdno — bez chyby. Obráceně: zveřejnit klíč s heslem nebo tokenem (email.smtp.password) znamená poslat ho každému návštěvníkovi. getMerged/getAll jsou jen pro administraci a příznak nečtou.

Druhá vrstva proti úniku: SettingSecrecy. Latte proměnná $settings se plní i v Admin, Api a Cron kontextu, kde mapa tajemství legitimně obsahuje. SettingInitTrait ji proto pouští přes SettingSecrecy::stripSecrets(), který vyhodí každý klíč odpovídající vzoru secret|password|token|salt|credential. Není to autorizace — autoritou zůstává is_public; že jsou obě vrstvy nezávislé, hlídá tests/Unit/Security/SettingPublicExposureTest.php.

Vzor Kde Navíc
SECRET_KEY_PATTERN stripSecrets(), test —
CREDENTIAL_KEY_PATTERN jen migrační shim v SettingManager (rozhoduje bez is_public) smtp — email.smtp.host a email.smtp.username jsou části týchž přihlašovacích údajů

Užší vzor nerozšiřuj o smtp

SECRET_KEY_PATTERN je kontrakt testu i stripSecrets(). Se smtp by se nedalo odlišit hlídané jméno od vědomě širší heuristiky a ze šablon by zmizely i neškodné klíče SMTP.

Jazykové varianty klíče#

cms_system_settings (module, key, value) nemá sloupec jazyka. Jazykově závislá hodnota (identita webu: site.name, site.tagline) proto leží ve vlastním řádku s příponou aliasu jazyka (site.name.en); základní klíč bez přípony nese výchozí jazyk a je fallbackem pro ostatní. Klíč je v celém systému plochý řetězec ($map[$module][$key]), tečka nic nevnořuje.

LocalizedSettingKey::resolve() čte takto:

  1. neprázdná hodnota <klíč>.<alias> jazyka stránky,
  2. jinak základní <klíč>,
  3. jinak ''.

Prázdná jazyková hodnota = nepřeloženo

Vypnout text jen v jednom jazyce nejde. Je to záměr: prázdné pole v jazykovém tabu administrace nesmí na /en/ tiše smazat název webu. Jazykové varianty dostávají jen klíče, které vyjmenuje volající (identita webu), ne libovolný klíč.

Klíč s vlastní obrazovkou nastavení#

Obecná obrazovka Nastavení → Nastavení webu (SettingPresenter) vypíše každý řádek cms_system_settings modulů 1 a 2 jako pole formuláře; klíč, který nezná, dá do sekce „ostatní“ pod syrovým jménem. Klíče, které potřebují popisky, upload nebo validaci, mají vlastní záložku (provozovatel, identita webu, vzhled webu, nastavení administrace) a z obecné obrazovky jsou vyřazené. Každý klíč se tak mění právě na jednom místě.

Kde je klíč vyřazený Kdy
SettingPresenter::HIDDEN_KEYS výčet klíčů, které edituje jiná záložka
SettingPresenter::HIDDEN_LOCALIZED_BASE_KEYS základní klíč, jehož jazykové varianty (site.name.en) se skrývají podle předpony; jazyky přibývají v databázi, výčet by se tiše rozešel

Některé klíče tvoří dvojici, která musí sedět: šablona a její cesta (theme.frontend + theme.frontend.path, theme.admin + theme.admin.path). Cesta proto nemá vlastní pole, dopočítá se z vybrané šablony a ukládá se s ní najednou.

Téma administrace se načítá ještě před přihlášením (viz Admin presenter), takže na přihlašovací stránce není identita a admin-only get-merged odpoví zamítnutím. ThemeRepository proto čte theme.admin* z veřejné mapy (get-public-merged) a bere je natvrdo z modulu 1. Volitelný klíč (theme.admin.skin) chybět smí, do kontroly úplnosti se nepočítá.

Postup: přidávám klíče s vlastní záložkou nastavení

  1. Řádky založte migrací. Klíč, který čte front nebo bootstrap administrace, dostane is_public = 1 už v migraci; uložení formuláře příznak u existujícího řádku nezmění.
  2. Klíče přidejte do SettingPresenter::HIDDEN_KEYS (jazykové varianty přes HIDDEN_LOCALIZED_BASE_KEYS).
  3. Závislou hodnotu (cestu) nedávejte do formuláře, dopočítejte ji a uložte s hlavní hodnotou.
  4. Záložku přidejte do app/UI/Admin/System/Templates/@settingsTabs.latte.
  5. Čte-li klíč bootstrap administrace, nechte ho v modulu 1 a ověřte přihlašovací stránku v odhlášeném prohlížeči.

Překlep v dvojici theme.admin + theme.admin.path = 500 na celé administraci

ThemeMapper z ní skládá cestu k šablonám; při rozporu skončí DefaultThemeResolver::resolve() na variantExists() on null na každé admin stránce včetně přihlášení, takže chybu nejde opravit z administrace. Stejně dopadne klíč tématu přesunutý mimo modul 1 a klíč bez is_public = 1 na přihlašovací stránce, jakmile se RBAC vynucuje.

Klíč mimo HIDDEN_KEYS jde měnit ze dvou míst

Obecná obrazovka ho ukáže jako holé textové pole bez validace vlastní záložky; hodnota uložená odtamtud obejde kontrolu, na kterou se zbytek kódu spoléhá.

Obrázek v nastavení#

Logo, favicona, sdílecí obrázek i fotka provozovatele (site.*.assetId, web.operator.avatar.assetId) se v nastavení drží jen jako id assetu; obrázek je běžný záznam cms_system_images a ukládá ho Api ImageManager, takže je vidět i v administraci „Obrázky“ a dostane varianty podle svého použití.

Uložení záložky (SiteIdentityFormTrait, OperatorFormTrait) rozhoduje u každého obrázkového pole jednu ze tří cest a výsledek nese jako trojici id / failed / release:

Cesta Asset Hodnota nastavení
beze změny nic dosavadní id (<pole>CurrentId)
nový soubor zapíše se do téhož assetu, nový vznikne jen když žádný nebyl id assetu
„Odebrat“ smaže se až po uložení nastavení prázdná
zápis obrázku selhal nic failed = true: celé uložení se zruší, i texty

Všechny klíče záložky jdou jednou dávkou SettingManager::saveBatch() v jedné transakci. Selže-li dávka, smaže se asset, který v tomhle požadavku teprve vznikl.

null nesmí znamenat zároveň „odebrat“ i „selhalo“

Kdyby vracela cesta jen ?int, selhaný upload by zapsal prázdnou hodnotu a odpojil dosavadní logo, zatímco formulář hlásí úspěch. Proto trojice a při selhání původní id.

Výměna obrázku = tentýž asset

Nový asset při každé výměně by v cms_system_images nechával osiřelé řádky. Api starému záznamu přegeneruje varianty podle plánu použití.

Postup: změnil jsem konfiguraci a nic se nestalo#

  1. Smaž temp/cache. Nette drží zkompilovaný DI kontejner.
  2. U nového jazyka navíc temp/cache/translation/ — katalog překladů se sám neregeneruje.
  3. U nové třídy může být problém zastaralá mapa tříd.
  4. Teprve pak hledej chybu v tom, co jsi napsal.

Zastaralá mapa tříd shodí CELÝ web

Ne srozumitelnou hláškou — chybou 500 na všech stránkách naráz. Po přidání jazyka, nového modulu nebo změně konfigurace vždycky smaž temp/cache.

Ověření po instalaci#

  1. Titulní stránka.
  2. Přihlášení do administrace.
  3. Jeden výpis a jeden formulář v administraci.
  4. Odeslání jednoho formuláře na frontu.
  5. log/ — přibylo něco?

Živý proklik odhalí to, co harness ne

Testovací běh přes CLI obchází spoustu vrstev. Než něco prohlásíš za hotové, proklikej to v prohlížeči — podrobně Testy a nástroje.

Ověř si, ke které databázi jsi připojený

Než začneš cokoli zkoušet, podívej se do config/Shared/local.neon, kam míří připojení k databázi. Vývoj patří na vlastní kopii dat, ne na databázi běžícího webu.

Kam sáhnout#

Chci Kde
vzor lokální konfigurace config/Shared/local.neon.example
rozhodnutí o ladicím režimu app/Bootstrap.php → resolveDebugMode()
SQL skripty schématu docs/sql/
obecná obrazovka nastavení a skryté klíče app/UI/Admin/System/Presenters/SettingPresenter.php
čtení tématu administrace app/UI/Admin/System/Models/Repositories/Api/ThemeRepository.php
příkazy sestavení package.json, composer.json
checklist nasazení Checklist nasazení

Navazující kapitoly: Checklist nasazení · Naplánované úlohy · Struktura projektu · Testy a nástroje

Nastavení cookies