Laravel Manual
Headless manual-as-code for Laravel: a Markdown user manual in your repo, rendered and searched server-side, served over a JSON API.
composer require intragile/laravel-manual
A repóban élő, Markdown formátumú felhasználói kézikönyv, amelyet a szerver renderel és keres benne, és beállítható hozzáférésű JSON API-n ad ki. Egy konzolparancs CI-ban ellenőrzi.
- Headless. A csomag adatot ad, a súgóoldalt az SPA-d vagy a Blade nézeted rajzolja.
- Kézikönyv kódként. A fejezetek Markdown-fájlok a repódban, ugyanabban a pull requestben kerülnek átnézésre, mint a funkció. Nincs adatbázis, nincs CMS.
- Fordítás külön rendszer nélkül. Nyelvenként egy könyvtár. Ha egy fejezet hiányzik a fordításból, a visszaesési lánc következő nyelvén jön, és ezt jelzi.
- Szerveroldali keresés. Nyelvenkénti szótövezés, súlyozott rangsor, egyszerű szöveges kivonatok. A teljes tartalom nem hagyja el a szervert.
- Kézikönyv-jelölések. Hivatkozhatsz másik fejezetre, az alkalmazásod útvonalaira és külső
URL-ekre kulccsal, tehetsz be ikonokat és kiemelő dobozokat (
tip,warning, ...). Mindez szerveroldalon oldódik fel. - Biztonságos HTML. A nyers HTML kikerül, a veszélyes linkek kiesnek, így a
htmlmezőv-html-be való. - Te döntöd el, ki éri el. Nyilvános, csak bejelentkezettek, gate, vagy kikapcsolva.
- A hibás kézikönyv nem megy ki. A
manual:checkCI-ban jelzi a hiányzó fordítást, az eltérő szerkezetet, a törött linket és a nyers HTML-t.
Telepítés
composer require intragile/laravel-manual
PHP 8.3+ és Laravel 12 vagy 13 kell hozzá. A service provider automatikusan betöltődik. A configot csak akkor publikáld, ha módosítani akarod:
php artisan vendor:publish --tag=manual-config
Gyors kezdés
Előbb olvasd el a hozzáférésről szóló részt. Az alapbeállítással (az access értéke
authenticated, az automatikus route-ok pedig csak az api csoportban vannak) a kérés csak akkor
megy át, ha az alapértelmezett guard már felismeri a felhasználót. Egy szokásos tokenes API-nál
(Sanctum) ez nem így van, ezért a végpontok a bejelentkezett felhasználónak is 401-et adnak.
Ajánlott: kapcsold ki az automatikus route-okat, és a saját, már hitelesítő route-csoportodban regisztráld őket. Előbb publikáld a configot:
php artisan vendor:publish --tag=manual-config
Utána a publikált fájlban kapcsold ki az automatikus route-okat, és állítsd be a nyelveket. A
locales az API által kiszolgálható összes nyelv, a source_locale pedig az a nyelv, amelyen
elsőként írsz:
// config/manual.php
'path' => resource_path('manual'),
'locales' => ['hu', 'en'],
'source_locale' => 'hu',
'fallback' => ['en'],
'routes' => [
'enabled' => false,
'prefix' => 'manual',
'middleware' => ['api'],
'name' => 'manual.',
],
// routes/api.php
use IntrAgile\Manual\Facades\Manual;
Route::prefix('v1')->middleware('auth:sanctum')->group(function () {
Manual::routes(); // GET /api/v1/manual, /manual/chapter, /manual/search
});
A Manual::routes() a configból maga teszi hozzá a saját előtagját (manual) és a route-név
előtagot (manual.), így a fenti csoport csak a külső előtagot és a hitelesítést adja. A
routes.middleware-t nem alkalmazza: az a tömb csak az automatikus route-csoporté.
A publikált configban a teljes routes tömböt tartsd meg. A Laravel a csomag configját csak a
legfelső szinten fésüli össze, így az általad megadott routes tömb lecseréli a csomagét; a
kihagyott kulcsok az alábbi táblázat alapértékeit kapják.
Ezután írd meg az első fejezetet (lásd a következő részt), és ellenőrizd:
php artisan manual:check
A kézikönyv megírása
resources/manual/
_meta.php a fa: szakaszok, sorrendjük és fejezeteik (nyelvfüggetlen)
_links.php opcionális: a [[link:kulcs]] célpontjai (lásd lent)
hu/
_sections.php a szakaszcímek ezen a nyelven
getting-started/
intro.md
first-steps.md
en/
_sections.php
getting-started/
intro.md
first-steps.md
A _meta.php a fát adja vissza. A szakaszazonosító a kulcs; az icon szabad szöveg, amelyet a
csomag változtatás nélkül ad tovább a kliensnek, az order a szakaszok sorrendjét adja, a
chapters pedig a fejezet-slugokat olvasási sorrendben. A fejezet címe {szakasz}/{fejezet},
itt getting-started/intro.
// resources/manual/_meta.php
return [
'getting-started' => ['icon' => 'rocket', 'order' => 1, 'chapters' => ['intro', 'first-steps']],
'admin' => ['icon' => 'settings', 'order' => 2, 'chapters' => ['users']],
];
A {locale}/_sections.php minden szakaszazonosítóhoz megadja a címét az adott nyelven:
// resources/manual/hu/_sections.php
return ['getting-started' => 'Első lépések', 'admin' => 'Adminisztráció'];
A számozás a _meta.php-beli helyzetből jön: az első szakasz második fejezete az 1.2. Mivel a
számok nem függenek a nyelvtől, minden fejezetnek kell fájl a forrásnyelven, különben a
számozás nyelvenként eltolódna. A manual:check ezt számon kéri.
A szakaszazonosítók és a fejezet-slugok csak [A-Za-z0-9_-]+ alakúak lehetnek.
A fejezetfájlok
Egy fejezet a {path}/{locale}/{szakasz}/{fejezet}.md fájl.
- Az első
# Címa fejezet címe. ATX alakot használj (# Cím): a setext cím (Címalatt=====) amanual:checkszerint hiba, és a renderelő sem olvassa. Egy fejezetben pontosan egy H1 van. - A cím utáni első bekezdés az összefoglaló, a fában és a keresésben ez látszik.
- Szakaszokhoz
##és###való. Kapnakid-t, és bekerülnek aheadings-be. A####és a mélyebb amanual:checkszerint hiba. - A táblázat, lista, idézet és a kerített kódblokk szabványos CommonMark (a GFM-táblázat is).
- Csak a kerített kódblokk számít kódnak. A
manual:checka 4 szóközzel behúzott kódblokkot nem ismeri fel: a sorait szövegként vizsgálja. A kódot mindig három visszafelé aposztróffal vagy~~~-vel kerítsd. - Nyers HTML nem megengedett (a renderelő kiveszi, a
manual:checkjelzi). AList<String>és hasonló szöveget tedd kódjelek közé.
Kiemelő dobozok
:::tip
Mentsen gyakran.
:::
A megengedett típusok a callouts config értékei (alapból tip, warning, note és steps).
Szabályok:
- A
:::típusés a záró:::a 0. oszlopban áll, külön sorban, előtte és utána üres sorral. Listába vagy idézetbe nem kerülhet; amanual:checkjelzi. - A dobozok egymásba ágyazhatók. Az ismeretlen típus és a lezáratlan doboz hiba a
manual:checkszerint.
A példa magázó, mert a kézikönyv a végfelhasználónak szól; a fejlesztői leírások tegeznek.
Így renderelődnek: <div class="manual-callout" data-type="tip">...</div>.
Tokenek
| Token | Eredmény |
|---|---|
[[manual:szakasz/fejezet]], címkével: [[manual:szakasz/fejezet|címke]] |
<a class="manual-link" data-slug="szakasz/fejezet">, hivatkozás másik fejezetre |
[[route:név]], címkével: [[route:név|címke]] |
<a class="manual-route" data-route="név">, hivatkozás az alkalmazásod egy képernyőjére |
[[link:kulcs]], címkével: [[link:kulcs|címke]] |
<a class="manual-external" href="..." target="_blank" rel="noopener noreferrer"> |
[[icon:név]] |
<i class="manual-icon" data-icon="név" aria-hidden="true"></i> (címke nélkül) |
Címke nélkül a név jelenik meg. A táblázatban a \| az, ahogy a Markdown-táblázat cellájában a
függőleges vonalat írni kell; a saját fejezeteidre ugyanez a szabály érvényes:
- A nevek csak
[A-Za-z0-9._/-]karaktereket tartalmazhatnak. - A táblázatcellában álló szó szerinti
|jelet\|alakban kell írni, a tokenen belül is. A cellában álló[[manual:a/b\|címke]]token, a címkéjecímke. - A token címkéje nem tartalmazhat soron belüli jelölést (
**félkövér**,`kód`stb.); az ilyen token szó szerinti szöveg marad. Tartsd egyszerűnek a címkét. - A tokent csak szövegben oldja fel, kódrészletben és kódblokkban soha. Ha egy tokent szó szerint
akarsz megmutatni, tedd kódrészletbe:
`[[manual:a/b]]`. A zárójelek kódolása ([[) kódon kívül nem segít: az is tokenként értelmeződik. - A
manual:checkjelzi a hibás alakú tokent, az ismeretlen fajtát, a_meta.php-ban nem szereplőmanualcélt, az ismeretlenlinkkulcsot, és ha aknown_routesmeg van adva, az ismeretlen route-ot.
A külső linkek a _links.php-ból jönnek (vagy a links configból: tömb, vagy ilyen fájl
útvonala). Az alak kulcs => nyelv => URL:
// resources/manual/_links.php
return [
'status' => ['hu' => 'https://example.com/hu/status', 'en' => 'https://example.com/en/status'],
];
Az URL-t a fejezet nyelvére választja, aztán en-re, aztán az elsőre. Csak http, https és
mailto URL lesz link; minden más, és az ismeretlen kulcs is, egyszerű címkeszövegként jelenik meg.
Beállítások
| Kulcs | Alap | Jelentés |
|---|---|---|
path |
resource_path('manual') |
A kézikönyv könyvtára. |
locales |
['en'] |
Az API által elfogadott nyelvek. Sorold fel az összes fordítást. A forrásnyelv mindig engedélyezett. |
source_locale |
en |
Az a nyelv, amelyen minden fejezetnek léteznie kell; a visszaesési lánc utolsó lépése. |
fallback |
['en'] |
További nyelvek, sorrendben, amelyeket akkor próbál, ha a kért nyelven hiányzik a fejezet, vagy a kért nyelv nincs beállítva. |
access |
authenticated |
public, authenticated, gate neve vagy disabled. Lásd lent. |
routes.enabled |
true |
A route-ok automatikus regisztrálása. Kapcsold ki, ha a Manual::routes()-t magad hívod. |
routes.prefix |
manual |
URL-előtag. |
routes.middleware |
['api'] |
Az automatikus route-csoport middleware-je. |
routes.name |
manual. |
Route-név előtag (manual.index, manual.chapter, manual.search). |
callouts |
['tip', 'warning', 'note', 'steps'] |
A megengedett :::típus dobozok nevei. |
links |
null |
A [[link:]] térképe: tömb, fájlútvonal, vagy null, ekkor a {path}/_links.php, ha létezik. |
known_routes |
null |
A [[route:]] által használható route-nevek, a manual:check ellenőrzi. null kihagyja az ellenőrzést. Lista, vagy iterálható értéket adó callable, pl. 'App\Support\Routes::names'. A callable szöveges callable vagy Closure lehet: a tömb-callable-t a rendszer névlistaként olvassa. A config:cache miatt a szöveges callable az ajánlott. |
stemmers |
hu-ra magyar, en-re angol |
Nyelv => Stemmer-t megvalósító osztály. Lásd: Keresés. |
search.min_term_length |
3 |
Az ennél rövidebb keresőszavakat figyelmen kívül hagyja. |
search.limit |
10 |
A találatok alapszáma. |
cache.store |
null |
Cache store; null az alapértelmezettet használja. |
cache.prefix |
manual |
A cache-kulcsok előtagja. |
Hozzáférés
Az access dönti el, ki hívhatja az API-t. Az ellenőrzés a csomag minden route-jának részét képező
middleware-ben fut, tehát az automatikus route-okra és a Manual::routes()-ra is vonatkozik.
| Érték | Viselkedés |
|---|---|
public |
Bárki. |
authenticated |
Be kell jelentkezni, különben 401. |
| gate neve | A Gate::allows(név) dönt. Az elutasított, nem bejelentkezett látogató 401-et, az elutasított bejelentkezett felhasználó 403-at kap. |
disabled |
Minden route 404. |
Gate neve nem lehet public, authenticated vagy disabled: ezek a szavak mindig a táblázatban
szereplő viselkedést jelentik.
Az authenticated a $request->user()-t nézi az alapértelmezett guardon. A csomag saját
hitelesítést nem ad, az alap route-csoport pedig csak ['api'], ezért további beállítás nélkül sem
a Sanctum token, sem a munkamenet nem ismerhető fel, és 401 jön. Két megoldás:
-
Ajánlott:
routes.enabled = false, és aManual::routes()hívása a saját hitelesített csoportodban, mint a gyors kezdésben; vagy -
az automatikus route-ok megtartása és a hitelesítő middleware hozzáadása:
'routes' => [ 'enabled' => true, 'prefix' => 'manual', 'middleware' => ['api', 'auth:sanctum'], 'name' => 'manual.', ],
Ha a Sanctum-guardod nem az alapértelmezett, biztonságosabb a gate vagy az egyedi csoport.
public-ot csak olyan tartalomra használj, amit nyilvános weboldalra is kitennél.
A körülvevő csoport middleware-jei (például az auth:sanctum) a csomag saját middleware-jei
előtt futnak, így az Accept: application/json-t kényszerítő előtt is. Az Accept fejléc
nélkül érkező nem bejelentkezett látogatót ezért az alkalmazásod saját, hitelesítetlen kérésekre
vonatkozó kezelése utasítja el, amely alapból a bejelentkező route-ra irányít át. Állítsd be, hogy
az alkalmazásod az API-ra JSON-t adjon, például a bootstrap/app.php-ban:
$exceptions->shouldRenderJsonWhen(fn ($request) => $request->is('api/*')).
A gate a nem bejelentkezett látogatóra is lefut, de a Laravel csak akkor hívja meg nála a gate
callbackjét, ha annak első, user paramétere nullable, vagy null az alapértéke. Ha a gate-en a nem
bejelentkezett látogatót is át akarod engedni, így deklaráld:
use App\Models\User;
Gate::define('view-manual', fn (?User $user): bool => $user === null || $user->can_see_help);
Az a callback, amely kötelezően User-t vár, vagy egyáltalán nincs paramétere, minden nem
bejelentkezett látogatót 401-gyel utasít el.
API
Minden válasz JSON. A csomag a route-jain Accept: application/json-t kényszerít, és hagyományos
kivételeket dob (AuthenticationException, ValidationException, NotFoundHttpException), így a
hibákat az alkalmazásod kivételkezelője formázza, például application/problem+json-ra.
| Végpont | Leírás |
|---|---|
GET {prefix} |
A fa: a szakaszok a fejezeteikkel (slug, szám, cím, összefoglaló). |
GET {prefix}/chapter?slug=szakasz/fejezet |
Egy renderelt fejezet. 404, ha a slug ismeretlen, vagy a láncban egyetlen nyelven sincs hozzá fájl; 422, ha a slug hiányzik vagy 200 karakternél hosszabb. |
GET {prefix}/search?q=... |
Keresési találatok. 422, ha a q hiányzik vagy 200 karakternél hosszabb. |
Minden végpont elfogadja a ?locale= paramétert is (legfeljebb 20 karakteres szöveg).
{
"data": [
{
"id": "getting-started",
"icon": "rocket",
"title": "Első lépések",
"chapters": [
{ "slug": "getting-started/intro", "number": "1.1", "title": "Bevezetés", "summary": "Mi ez, és kinek szól." }
]
}
],
"locale": "hu",
"fallback": false
}
{
"data": {
"slug": "getting-started/intro",
"number": "1.1",
"title": "Bevezetés",
"summary": "Mi ez, és kinek szól.",
"section": "getting-started",
"position": 1,
"total": 3,
"readingMinutes": 2,
"html": "<p>...</p>",
"headings": [ { "anchor": "elso-lepesek", "text": "Első lépések", "level": 2 } ],
"prev": null,
"next": { "slug": "getting-started/first-steps", "title": "Első lépések" },
"locale": "hu",
"fallback": false
}
}
{
"data": [
{ "slug": "admin/users", "section": "admin", "title": "Felhasználók", "snippet": "... egyszerű szöveges részlet ...", "score": 12 }
],
"locale": "hu",
"fallback": false
}
- A
numberszöveg ("2.1"); aprevés anext{slug, title}, vagynullaz első, illetve az utolsó fejezetnél; a láncban fájl nélküli fejezeteket átugorja. - Az a fejezet, amelynek a láncban egyetlen nyelven sincs fájlja, kimarad a fából, és a
number,position,total,prevésnextcsak a nyelvhez ténylegesen létező fejezeteket számolja. - A fejezet
localemezője a ténylegesen beolvasott fájl nyelve, afallbackpedig akkortrue, ha ez eltér a kért nyelvtől. A fejezetválasznak nincs legfelsőlocale-ja. - A fa és a keresés legfelső
localemezője a kérésből meghatározott nyelv: a kért nyelv, ha be van állítva, különben afallbackelső beállított nyelve, végül asource_locale. Afallbackakkortrue, ha a kérést a saját nyelvén nem lehetett teljesíteni. Ez nyelvszintű: ha egyetlen fejezet hiányzik egy beállított fordításból, és a visszaesésből jön, az nem kapcsolja be a fa vagy a keresésfallback-jét. Ahhoz a fejezet sajátfallbackmezőjét olvasd. - A
htmlmegtisztított (lásd Biztonság), ésv-html-lel való beillesztésre szánt. - Minden más szöveges mező (
title,summary,headings[].text, a szakaszok címe, aprevés anextcíme, valamint a keresésisnippet) egyszerű szöveg: szövegként kell megjeleníteni, soha nem HTML-ként.
Nyelvfeloldás
A nyelvet a kérésből ebben a sorrendben oldja fel:
?locale=;- az
Accept-Languageminden címkéje, a kliens minőségi sorrendjében. Aq=0címkét („nem elfogadható”) figyelmen kívül hagyja. Minden címke előbb a teljes alakjával, aztán az elsődleges címkéjével próbálkozik: apt-BRa beállítottpt-BR-re (vagypt_BR-re), ennek hiányában apt-re illik; ahu-HUahu-ra; - az alkalmazás nyelve (
app()->getLocale()), csak ha a kliens egyáltalán nem küldött használható nyelvet; source_locale.
Az 1. és 2. lépés első olyan jelöltje nyer, amely szerepel a locales-ben vagy a source_locale,
így az Accept-Language: de, en;q=0.9 az en-t szolgálja ki, ha a de nincs beállítva. Ha a
kliens kért nyelvet, de egyik sincs beállítva (mondjuk csak ?locale=de), a kézikönyv a
visszaesési láncból (lásd lent) jön, a fa és a keresés pedig fallback: true-t jelez: a kliens így megtudja,
hogy a saját nyelve nem elérhető. A hibás alakú érték (?locale=../x) figyelmen kívül marad.
Visszaesési lánc
A kért nyelvhez a fejezetet ebben a sorrendben keresi: a kért nyelv, aztán a fallback-ben szereplő
nyelvek (csak a beállítottak), végül a source_locale. Az első megtalált fájl jön. A nem beállított
kért nyelvet (nincs a locales-ben, és nem a source_locale) kihagyja, és soha nem jut el a
fájlrendszerig: a lánc ilyenkor csak a beállított fallback nyelvekből, majd a source_locale-ból
áll. Ha a locales ['hu', 'en'], a source_locale hu, a fallback pedig ['en'], a de
kérés en nyelven jön fallback: true-val, az olyan fejezet pedig, amelynek nincs en fájlja,
hu nyelven. Üres ([]) fallback esetén a source_locale-ból jön. A config fallback kulcsa a
lánc (mely nyelveket próbálja); a válasz fallback jelzője csak azt mondja meg, hogy a
kiszolgált nyelv nem a kért nyelv.
A kézikönyv ellenőrzése
php artisan manual:check
php artisan manual:check --locale=en # a forrásnyelv és ez a nyelv; ismételhető
Minden beállított nyelvet ellenőriz (a forrásnyelvet és a locales-t), és a fejezet és a nyelv
megjelölésével jelzi:
_meta.php: hiányzik, nem tölthető be, hibás szakaszok vagy fejezetek, érvénytelen azonosító vagy slug, ismétlődő fejezet, fejezet nélküli kézikönyv;_sections.php: hiányzó szakaszcímek, ismeretlen szakaszkulcsok;- fájlok: a
_meta.php-ban nem szereplő Markdown-fájl (árva), a forrásnyelvről hiányzó fejezet, a fordításból hiányzó fejezet; - szerkezet: nincs cím, több H1, setext cím, nincs összefoglaló,
####vagy mélyebb cím, ismeretlen, lezáratlan, behúzott vagy idézetben álló:::jelölő, üres sorok nélküli:::; - tartalom: nyers HTML, hibás vagy fel nem oldott token, törött
manualhivatkozás, ismeretlenlinkkulcs, ismeretlen route (csakknown_routesmellett), olyan link-URL, amelyből nem lenne link; - paritás: az a fordítás, amelynek a blokkszerkezete (címek, listák, táblázatok, dobozok, kódblokkok, bekezdések) eltér a forrásnyelvétől. A lefordított kódkommentár vagy a más sortörés nem eltérés; a hiányzó bekezdés vagy szakasz az.
Bármilyen hibánál kiírja az üzeneteket, és nem nulla kilépési kóddal tér vissza, így pull request ellenőrző lépés lehet:
# .github/workflows/tests.yml
- name: Check the manual
run: php artisan manual:check
A --locale a locales-ben nem szereplő nyelvvel hiba. A kerített kódblokkot minden sorszabály
figyelmen kívül hagyja, a nyers HTML és a token ellenőrzése a soron belüli kódrészleteket is.
A manual:clear a cache-t érvényteleníti (lásd lent).
Keresés
A keresés a szerveren fut, és az indexét lustán építi fel. A keresőkifejezést (legfeljebb 200
karakter) szavakra bontja, az ékezeteket lehántja (az ertesites megtalálja az Értesítési-t),
és minden szót a feloldott nyelv szótövezőjével vág szótőre. Egy fejezet akkor találat, ha
minden szó előfordul benne részszövegként. A találatokat súlyozott előfordulás rangsorolja:
cím 6, alcímek 3, összefoglaló 2, törzs 1. A search.min_term_length-nél rövidebb szavakat
figyelmen kívül hagyja.
Beépítve: a HungarianStemmer és az EnglishStemmer, könnyű toldalékvágók, amelyek legalább négy
karaktert meghagynak. A szótövező nélküli nyelven úgy keres, ahogy beírták. A szótövezés csak a
keresőkifejezésre vonatkozik; a tartalomban úgy keres, ahogy írva van.
A visszaeső tartalomban a kért nyelv szótövezőjével keres: az angol kérésre megjelenő magyar fejezetben az angol szótövezővel.
Saját szótövező regisztrálásához valósítsd meg az IntrAgile\Manual\Search\Stemmer
interfészt, és nyelv szerint add meg. Az osztálynak argumentum nélküli konstruktor kell, és
kisbetűs ASCII szót kap (az ékezetek már le vannak hántva). A nyelvnek szerepelnie kell a
locales-ben is; a stemmers bejegyzés önmagában nem engedélyezi:
namespace App\Search;
use IntrAgile\Manual\Search\Stemmer;
final class GermanStemmer implements Stemmer
{
public function stem(string $normalizedWord): string
{
if (strlen($normalizedWord) < 5) {
return $normalizedWord;
}
return preg_replace('/(en|er|e|n|s)$/', '', $normalizedWord) ?? $normalizedWord;
}
}
// config/manual.php
'stemmers' => [
'hu' => \IntrAgile\Manual\Search\HungarianStemmer::class,
'en' => \IntrAgile\Manual\Search\EnglishStemmer::class,
'de' => \App\Search\GermanStemmer::class,
],
Az a bejegyzés, amelynek osztálya nem létezik, vagy nem valósítja meg a Stemmer-t, kimarad, vagyis
az adott nyelven nincs szótövezés.
A találat snippet mezője egyszerű szöveg, a fejezet első találat körüli részéből vágva. Nem
megtisztított HTML: szövegként jelenítsd meg ({{ hit.snippet }}), ne v-html-lel, vagy escape-eld.
Programozott elérés
use IntrAgile\Manual\Facades\Manual;
Manual::tree('hu'); // szakaszok a fejezeteikkel
Manual::chapter('admin/users', 'hu'); // tömb vagy null
Manual::markdown('admin/users', 'hu'); // ['markdown' => ..., 'locale' => ...] vagy null (cache nélkül)
Manual::search('számla', 'hu', 5); // találatok listája, opcionális limit
A Manual::spy() és a Manual::swap() működik, mert a facade gyökere a ManualManager szolgáltatás.
A visszatérési alakok a fenti JSON-szerkezetek (a data burkoló nélkül). A _meta.php-ban nem
szereplő slug null-t ad. A kívánt nyelvet add át; a HTTP-réteggel ellentétben a facade nem
olvassa a kérést.
Frontend-tippek
- A fejezet
htmlmezője biztonsággal beilleszthetőv-html-lel. A kattintásokat egyetlen delegált figyelővel kezeld a konténeren, a linkek egyenkénti bekötése helyett: keresd a legközelebbi[data-slug]-ot, és nyisd meg azt a fejezetet, vagy a legközelebbi[data-route]-ot, és told be a route-nevet a routeredben. A linkeknektabindex="0"ésrole="link"jár, ezért az Entert is kezeld. - Az
[[icon:név]]<i class="manual-icon" data-icon="név">lesz. Adata-icon-t képezd le a saját ikonkészletedre (CSS-sel, vagy cseréld ki az elemet); a fában a szakasziconmezője ugyanilyen szabad szöveg. - A dobozokat a
.manual-callout[data-type="tip"]választóval stílusozd (éswarning,note,steps, vagy amit beállítottál). A táblázatok.manual-table-wrapburkolóban vannak, hogy görgethetők legyenek. - A
h2ésh3id-ja megegyezik aheadingsanchormezőjével, így tartalomjegyzéket építhetsz, és a#anchor-ra görgethetsz. - Az SPA küldjön
?locale=vagyAccept-Languageértéket, hiszen a felhasználó nyelvét általában kliensoldalon tartja. A fejezetfallbackmezőjéből jeleníthetsz meg „még nincs lefordítva” megjegyzést. - A keresési
snippet-et szövegként jelenítsd meg, soha ne HTML-ként.
Nyilvános API és szemantikus verziózás
A csomag a szemantikus verziózást követi. Az 1.0.0-tól kezdve az alábbiak bármelyikét érintő, visszafelé nem kompatibilis változás a következő főverzióig vár:
- a
Manualfacade metódusai:tree(),chapter(),markdown(),search()ésroutes(); - az
IntrAgile\Manual\Search\Stemmerinterfész (hogy a saját szótövezők tovább működjenek), valamint a beépített szótövező osztályok:HungarianStemmer,EnglishStemmerésNullStemmer; - a
ManualManagerpublikus metódusai (tree(),chapter(),markdown()éssearch(), a konstruktora nem); - a config kulcsai és jelentésük;
- a route-ok, a nevük és a lekérdezési paramétereik, valamint a fent leírt JSON-szerkezetek;
- a tartalom formátuma:
_meta.php,_sections.php,_links.php, a token- és dobozszintaxis és a renderelt jelölés (manual-link,manual-route,manual-external,manual-icon,manual-callout, valamint adata-slug,data-route,data-icon,data-typeattribútumok); - a
manual:checkés amanual:clearparancs, a kapcsolóik és a kilépési kódjuk. Az egyes üzenetek szövege nem tartozik ide, és új ellenőrzések alverzióban is jöhetnek.
Nem tartozik ide: minden @internal jelölésű osztály és metódus (a controller, a middleware-ek, a
resource-ok, a parancsosztályok, a nyelvfeloldó), valamint a facade mögötti osztályok: a
ManualRepository, a ContentTree, a ManualSearch, a ManualRenderer és a ManualValidator.
A facade-ot érdemes használni.
Cache
A fa, az egyes fejezetek (a renderelt HTML-lel együtt) és a keresési index a beállított store-ban
vannak gyorsítótárazva. A bejegyzések csak tömböket és szövegeket tartalmaznak, így az objektumok
visszaállítását tiltó store-okkal (cache.serializable_classes = false) is működnek. A kulcs
ezekből áll össze:
- a
cache.prefix; CACHE_VERSION, a csomag egy konstansa, amelyet a tárolt alak vagy tartalom változásakor emelnek;- a cache generáció;
- a bejegyzés fajtája (
tree,chaptervagysearch) és a tényleges nyelv; - a kimenetet befolyásoló config hash-e (
locales,source_locale,fallback,callouts, a feloldott linkek, és egy külső linkfájl módosítási ideje és mérete); - a tartalom ujjlenyomata: a kézikönyv könyvtárának minden
.mdés.phpfájljának útvonala, módosítási ideje és mérete; - a fejezet slugja, a fejezet-bejegyzéseknél.
Ha egy deploy módosít egy fájlt vagy a configot, a változás anélkül jelenik meg, hogy bármit törölni kellene. Minden bejegyzés egy nap után lejár, hogy a régi kulcsok ne halmozódjanak fel.
A hiányzó tartalmi útvonal nem hiba, hanem üres kézikönyvet eredményez. A php artisan manual:check
parancsot ezért a telepített buildre vagy image-re is érdemes lefuttatni, nem csak a repositoryban.
A php artisan manual:clear növeli a generációt: minden régi bejegyzés egyszerre elérhetetlenné
válik, és magától lejár. Bármilyen cache store-on működik (nincs címke, nincs kulcslistázás). A
futó workerek a következő kérésnél már az új generációt olvassák, mert a repository, a keresés és a manager
kötései scoped hatókörűek: kérésenként vagy jobonként új példány készül, ezért az Octane és a queue
workerek biztonságosak.
Korlát: az előző szerkesztéssel azonos másodpercben végzett módosítás, ha a fájl mérete nem
változik, nem észlelhető. Ilyenkor futtasd a manual:clear-t. A gyakorlatban ez csak a fájl
kézi, helyben szerkesztésekor számít; a deploy lecseréli a fájlokat.
Biztonság
A markdown nyers HTML eltávolításával készül (html_input: strip), a veszélyes linksémákat,
például a javascript:-et pedig kiveszi (allow_unsafe_links: false), így a html mező biztonsággal
beilleszthető egy oldalba. A [[link:]] URL-jének http, https vagy mailto kell legyen. A
nyelv a fájlnévbe kerülés előtt csak a beállított listából származhat, a fejezet-slugnak pedig
szerepelnie kell a _meta.php-ban, mielőtt útvonal készül belőle, ezért kéréssel nem lehet
tetszőleges fájlt olvastatni a csomaggal. Lásd: SECURITY.md. A keresési snippet
egyszerű szöveg: escape-eld.
Tesztelés és minőség
composer test
composer analyse
vendor/bin/pint --test
A composer analyse a PHPStan-t --memory-limit=1G-vel futtatja. A saját CI-odban is adj meg
--memory-limit-et (vendor/bin/phpstan analyse --memory-limit=1G); a legmagasabb szinten a PHP
alap memóriakorlátja kevés lehet.
Licenc
MIT. Lásd: LICENSE.
Fejlesztő és karbantartó: IntrAgile Kft..
Ez az oldal a csomag v1.0.1 verziójának README-jéből készült. Forrás a GitHubonúj lapon nyílik meg