Wzorzec lokalnych nadpisań — szablony Symfony bez forkowania
architektura,symfony,statyczna-stronaCzęść 4 z 5
- 368 testów dla statycznego generatora stron — po co?
- 79 błędów w kodzie Symfony który przeszedł PHPStan
- XSS, open redirecty i path traversal na 'statycznej' stronie
- Wzorzec lokalnych nadpisań — szablony Symfony bez forkowania
- Open-source notACMS — pełna lista kontrolna
To część 4 serii o przygotowaniu notACMS do wydania jako open-source. Część 3 opisuje luki bezpieczeństwa. Oryginalna seria o migracji z WordPressa opisuje samą migrację.
Chcesz udostępnić szablon projektu Symfony. Każdy użytkownik musi go dostosować — inne kolory, inna strona główna, inna nawigacja. Forkowanie oznacza że nie może pobierać aktualizacji z upstreama. Pliki konfiguracyjne nie obsłużą zmian w szablonach. Motywy są zbyt sztywne.
Rozwiązanie które zbudowałem dla notACMS to katalog local/ który scala się na projekt bazowy w czasie buildu. Użytkownicy customizują local/, reszta zostaje nienaruszona, a gdy upstream się zmienia — pull i merge jak każda inna operacja gita.
Problem
Trzy scenariusze których pliki konfiguracyjne nie rozwiążą:
- Użytkownik A chce inne kolory CSS i customowy layout strony głównej
- Użytkownik B chce nadpisać tylko komponent nawigacji
- Użytkownik C chce dodać własne stringi tłumaczeń
Forkowanie to tradycyjna odpowiedź. Ale forkowanie oznacza że każda aktualizacja upstreama to ręczny merge. Dla projektu który dostaje regularne usprawnienia, to obciążenie utrzymaniowe które zabija adopcję.
Rozwiązanie: Warstwa scalania
local/ leży obok projektu bazowego. W czasie buildu ścieżki rozwiązują się z priorytetem: local/ pierwsze, baza druga. Trzy typy nadpisań:
Pełne nadpisanie — Zamiana całego pliku. local/templates/base.html.twig zastępuje templates/base.html.twig kompletnie.
Nadpisanie bloków — Rozszerzenie i nadpisanie konkretnych bloków Twig:
{% extends 'base.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('styles/custom.css') }}">
{% endblock %}
Nadpisanie tłumaczeń — local/translations/messages.en.yaml scala się z bazowymi tłumaczeniami, dodając lub zastępując klucze:
# local/translations/messages.en.yaml
site.title: "Moja Własna Strona"
nav.home: "Strona główna"
Kolejność CSS: Importmap z dwoma entrypointami
Najtrudniejsza część. Lokalne style muszą ładować się po stylach bazowych żeby je nadpisać przez kaskadę:
// importmap.php
->add('app', 'assets/app.js') // baza: importuje app.scss
->add('local', 'assets/local/app.js') // local: importuje local.scss
local.scss importowany po app.scss, więc kaskada CSS działa poprawnie. Bez !important. Importmap rejestruje oba entrypointy; przeglądarka ładuje je w kolejności.
Rozwiązywanie szablonów
Kernel::build() sprawdza pliki w local/templates/ i kopiuje je do outputu, nadpisując bazowe szablony. Katalog local/ jest gitignorowany w projekcie użytkownika, ale placeholdery .gitkeep zapewniają że świeże klony mają strukturę:
local/
├── assets/
│ └── styles/
│ └── .gitkeep
├── templates/
│ └── .gitkeep
└── translations/
└── .gitkeep
Boilplate'y w docs/examples/
Gotowe szablony startowe do różnych poziomów customizacji:
| Boilerplate | Poziom customizacji | Kiedy użyć |
|---|---|---|
starter-extend/ |
Najlżejszy | Chcesz rozszerzyć bazę drobnymi poprawkami |
block-override/ |
Średni | Chcesz zastąpić konkretne komponenty |
full-override/ |
Pełny | Chcesz pełną kontrolę nad layoutem |
material-cards/ |
Motyw | Kompletny przykład ciemnego motywu z local.scss |
translation-override/ |
Stringi | Tylko customowe stringi tłumaczeń |
Co jest śledzone w Gicie
Pliki .gitkeep w katalogach placeholder local/ żeby struktura istniała w świeżych klonach. Faktyczne pliki nadpisań są gitignorowane — to customizacje użytkownika. docs/examples/ zawiera szablony copy-paste od których użytkownicy zaczynają.
Dlaczego nie framework?
notACMS nie jest frameworkiem. Użytkownicy nie robią composer install notacms/core. Klonują repo, customizują local/ i deployują. Wzorzec jest na tyle prosty że da się go zrozumieć w 5 minut, i na tyle potężny że obsłuży każdą customizację. Cała powierzchnia customizacji to jeden katalog.
W następnym poście opisuję pełną listę kontrolną wydania open-source — wszystko z poprzednich czterech postów prowadzące do finałowego launchu.