Octibiz
Demo

Durchsucht Website und Dokumentation gemeinsam. Enter zeigt alle Treffer, Esc schließt.

Richtlinien

Alle verbindlichen Regeln an einem Ort. Jede Zeile nennt die Regel und die Folge, wenn du sie brichst — denn fast alle diese Fehler sind still. Sie werfen keine Ausnahme, sie tun einfach nicht, was du erwartest.

Der vollständige Durchlauf zeigt jede dieser Regeln im Einsatz.

Wie diese Seite zu lesen ist

Drei Stufen:

  • Hart erzwungen — der Bau, ein Wächter oder das Tor bricht ab. Du kommst nicht daran vorbei.
  • Still — nichts bricht, aber das Ergebnis ist falsch. Das sind die teuren.
  • Konvention — nicht erzwungen, aber im Bestand einheitlich. Abweichen kostet Nachfragen.

Benennung und Identität

RegelStufeFolge bei Verstoß
Plugin-Schlüssel als <hersteller>.<name>, klein, mit PunktKonventionKollision mit einem anderen Plugin
getPluginKey() im Descriptor, name in plugin.json und extra.octibiz.pluginKey in composer.json sind identischHartDer Installer bricht ab
getModuleFlag() heißt module.<name-mit-bindestrich> und steht genauso in plugin.jsonHartDer Installer bricht ab
getProviderKey() in jedem Provider ist der Plugin-SchlüsselStillBeim Entfernen bleiben Katalog-Waisen stehen
Adress-Segmente tragen dein Präfix und beanspruchen kein Kern-SegmentHartAbbruch vor jedem Seiteneffekt
Rechte heißen <plugin>.<objekt>.<verb>, üblich sind view und manageKonvention
Tabellen tragen ein fachliches Präfix, kein Hersteller-Präfix: acme_shipment, nicht acme_plugin_shipmentKonvention
Die Version steht gleich in AcmeShippingDescriptor::VERSION, composer.json, plugin.json und im obersten CHANGELOG.md-AbschnittHartEin Prüfer schlägt an

Der Descriptor

RegelStufeFolge bei Verstoß
PluginDescriptorInterface ist die einzige Pflicht-ImplementierungHartOhne ihn existiert das Plugin nicht
Jedes neue API-Präfix steht in getRouteSegments()StillFür dieses Segment greift keine Modul-Sperre — die Route bleibt erreichbar, obwohl das Modul aus ist
Jede Kopplung an ein anderes Plugin steht in getDependencies()StillDer Trockenlauf vor dem Entfernen meldet fälschlich „keine Abhängigen"
spiVersion in plugin.json nennt die Vertragsfläche, gegen die du gebaut hastHartFehlt sie, Installation nur mit Warnung; falsche Hauptversion bricht ab

Das Bundle

RegelStufeFolge bei Verstoß
Kein services.yaml im PluginKonventionKein ausgeliefertes Plugin hat eines
Überschreibst du prependExtension(), ruf parent:: aufStillEntity-Anmeldung, Migrationspfad und Vorlagen fallen lautlos aus. Auf einer bestehenden Entwickler-Datenbank unsichtbar, weil die Tabellen schon da sind — erst eine Frischinstallation legt keine einzige mehr an
Ein Kern-Dienst wird über serviceAliases() ersetzt, nicht über #[AsAlias]StillDie Konfiguration des Kerns gewinnt beim Zusammenführen, der Ersatz bleibt wirkungslos
Ein ersetzter Kern-Dienst fragt zu Beginn, ob sein Plugin aktiv istStillEr wirkt weiter, obwohl das Modul aus ist. Das Umschalten baut den Container nicht neu

Datenmodell

RegelStufeFolge bei Verstoß
Ganzzahliger Primärschlüssel plus HasUlid; der API-Bezeichner ist immer die ULIDStillInterne Zählstände nach außen; ratbare Kennungen
Timestampable und, wo fachlich sinnvoll, SoftDeletableKonvention
BrandScoped für Belege und Vorgänge, nicht für StammdatenKonvention
Geld immer in Cent als GanzzahlKonventionRundungsfehler
Status sind Daten in einer Zeichenkettenspalte, kein Aufzählungstyp im CodeKonventionDer Status lässt sich nie konfigurieren
Keine Fremdschlüssel über Plugin-GrenzenStillDas Entfernen des einen Plugins zerlegt das andere
Nie roh in die Tabelle eines fremden Plugins schreibenStillReal passiert: Nach dem Entfernen mit Datenlöschung beantwortete die Projektliste jeden Aufruf mit einem Serverfehler

Migrationen

RegelStufeFolge bei Verstoß
Rein additiv, kein Rückwärtsschritt auf einer laufenden DatenbankKonventionDatenverlust beim Zurückrollen
Nur Schema, kein einziges INSERTStillEine zweite Wahrheit, die beim nächsten Abgleich auseinanderläuft
isTransactional(): bool { return false; }KonventionEntspricht dem Bestand
Eigener Namensraum Acme\Shipping\MigrationsHartWird sonst nicht als Migrationspfad erkannt

Ein Plugin, das nur migriert und keine Verträge liefert, hat nach der Installation leere Tabellen.

API und Rechte

RegelStufeFolge bei Verstoß
API-Ressourcen sind eigene Klassen in src/ApiResource/, nicht die EntityKonventionInterne Felder werden nach außen sichtbar
Sammel-Operation: is_granted('recht') ohne ObjektKonvention
Einzel-Operation: is_granted('recht', object) und ein eigener VoterStillNiemand prüft. Der PermissionVoter des Kerns enthält sich bei übergebenem Objekt. Jede solche Operation ist ein Kandidat für fremden Datenzugriff über eine geratene Kennung
In Diensten nie die allgemeine Prüfung mit einem markenbezogenen Objekt aufrufen, sondern den eigenen Voter direkt fragenStillDerselbe Fallstrick
Markenbezogene Listen schneiden auf das Recht, nicht auf die MitgliedschaftStillHäufigstes echtes Sicherheitsmuster im Bestand: Wer in einer zweiten Marke Mitglied ist, dort aber kein Recht hat, sah trotzdem deren Daten
Jeder Sammel-Filter ist als Abfrage-Parameter deklariertStillEr wirkt, steht aber weder in der OpenAPI-Beschreibung noch im Werkzeug-Katalog. Eine KI müsste ihn raten
Rechte kommen aus PermissionProviderInterface, nie aus einer MigrationStillSie werden beim Entfernen nicht aufgeräumt
getRoleGrants() erweitert Rollen additivKonventionEin Plugin definiert keine Systemrolle um
Öffentliche Endpunkte über publicApiPathPatterns() brauchen eine eigene EchtheitsprüfungStillEine offene Tür. Das schaltet die Anmeldepflicht ab, nicht die Prüfung
Rückruf-Empfänger sind einfache Controller, keine API-RessourcenKonventionSie erschienen sonst als Werkzeug im KI-Zugang

Verträge

Implementieren genügt. Es gibt keine Registrierung, kein Kennzeichen von Hand, keine Konfigurationsdatei.

RegelStufeFolge bei Verstoß
Ein Plugin liefert seinen Modul-Schalter selbst über getFeatureFlags()StillDie Zeile fehlt nach der Installation; die Sperre ist fail-closed, alle Routen antworten mit 404
Ein Plugin mit eigener Fachtabelle liefert einen Demo-BeitragStillEine frisch aufgesetzte Vorführung zeigt an dieser Stelle einen Leerzustand
Jede neue Spalte mit Personenbezug bekommt einen Beitrag zu Auskunft und LöschungStillDie Zeile bleibt nach einer vollzogenen Löschung stehen. Kein Fehler, keine Warnung, nur Daten, die weg sein müssten
Eigene Zeilen in Kern-Tabellen räumt UninstallDataParticipantInterface beim Entfernen wegStillWaisen im Kern
Wer auf ein fremdes Ereignis hört, steigt früh aus, wenn sein Plugin inaktiv istStillDas Plugin wirkt weiter, obwohl es ausgeschaltet ist
Ein Veto-Empfänger liest nur — kein Schreiben, kein SpeichernStillManche dieser Ereignisse laufen mitten im Speichervorgang

Die Namensregel bei Ereignissen ist verlässlich: Ein Ereignis auf -ing läuft vorher und ist ablehnbar, eines im Partizip lief bereits und ist eine Tatsache.

Automatisierung

RegelStufeFolge bei Verstoß
Ein Auslöser implementiert TriggerProviderInterface und TriggerCatalogProviderInterfaceStillEr funktioniert, ist aber im Baukasten nicht auswählbar
getType() gibt eine eigene Konstante der Klasse zurück, kein Fremdklasse::TYPEHartDer Katalog-Wächter der Oberfläche liest den Quelltext und kennt nur Literal oder self::…
execute() beachtet $context->isDryRunStillDer Trockenlauf ändert Daten
Aktionen mit Pflichtfeldern implementieren AutomationActionConfigValidatorInterfaceStillEine unvollständige Konfiguration fällt erst beim Lauf auf, und dann beim Empfänger
Beschriftungen folgen automation.trigger.<kleinCamel> bzw. automation.action.<kleinCamel>StillDie Oberfläche zeigt den rohen Schlüssel
Aktionen schreiben keinen eigenen Prüfspur-EintragKonventionDublette je Feldänderung; die Änderung wird ohnehin protokolliert

Oberfläche

RegelStufeFolge bei Verstoß
Kern-Bausteine ausschließlich über @/sdkHartTiefere Verweise werden beim Bau abgewiesen
Routen-Pfade ohne führenden SchrägstrichStillDie Route hängt an der falschen Stelle
Komponenten immer als Nachlader (() => import(...))KonventionAlles landet im Einstiegspaket
localeLoaders sind Nachlader, keine direkten VerweiseStillJede Sprache jedes Plugins landet im Einstiegspaket
In routeModules steht links das Routen-Präfix, rechts der Kurzname des Schalters ohne module.StillTrifft der rechte Wert keinen echten Schalter, gilt das Modul immer als eingeschaltet
Backend-Übersetzungen liegen zentral, nur Oberflächen-Übersetzungen im PluginHartWerden sonst nicht geladen

Tests

RegelStufeFolge bei Verstoß
PHP-Tests liegen zentral unter tests/, nie unter plugins/<Name>/tests/HartEin Architektur-Wächter hält das fest. Ein Test, den der Testläufer nicht findet, ist kein Test
Oberflächen-Tests liegen sehr wohl im Plugin, unter frontend/**/__tests__/Konvention
Vor dem Lauf make test-dbHartApiTestCase bricht mit Anleitung ab, statt still 404 zu liefern
Erbt ein Test von KernelTestCase, seedet er die Schalter selbst über SeedsPluginModuleFlagsTraitStillAlle Plugin-Routen 404en, und ein Test auf einen Fehlerfall wird trotzdem grün
Zu jeder markenbezogenen Liste gehört ein Test auf den Marken-Schnitt, der auch die Anzahl prüftStillEine Summe zu verbergen und die Anzahl mitzuliefern verrät den fremden Bestand trotzdem
make gate ist grün, bevor etwas „fertig" heißtHartDas Tor ist die maßgebliche Antwort

Die Abnahme-Liste

Vor dem Ausliefern, in dieser Reihenfolge:

Geprüft?Was
Version steht überall gleich: Descriptor, composer.json, plugin.json, CHANGELOG.md
Jedes API-Präfix steht in getRouteSegments()
Der Modul-Schalter kommt aus dem eigenen StandardDataProvider
Migrationen wandern nur additiv und enthalten kein INSERT
Rechte, Stammdaten und Nachschlagewerte kommen aus den Verträgen
Jede Einzel-Operation hat einen eigenen Voter
Jede markenbezogene Liste ist auf das Recht geschnitten
Jeder Sammel-Filter ist als Parameter deklariert
Ein Demo-Beitrag existiert, wenn es eine eigene Fachtabelle gibt
Jede Spalte mit Personenbezug hat einen Beitrag zu Auskunft und Löschung
Kopplungen an andere Plugins stehen in getDependencies()
prependExtension() ruft parent:: auf, falls überschrieben
Der Generator-Rest ist raus
make gate ist grün

Weiter

plugin.guidelines · Gilt ab Version 0.6.22