Einführung: Warum brauchen wir eine Zustandsmaschine?
In Anwendungen ist es häufig erforderlich, den Zugriff auf bestimmte Aktionen für ein Objekt zu beschränken. Hierzu werden RBAC-Module verwendet, die das Problem der Einschränkung des Zugriffs in Abhängigkeit von Benutzerrechten lösen. Die Aufgabe, Aktionen abhängig vom Status des Objekts zu verwalten, bleibt ungelöst. Dieses Problem ist mit einer Zustandsmaschine oder Zustandsmaschine gut gelöst. Mit einer praktischen Zustandsmaschine können Sie nicht nur alle Regeln für Übergänge zwischen den Zuständen des Objekts an einem Ort sammeln, sondern auch eine gewisse Reihenfolge im Code festlegen, indem Sie die Regeln für Übergänge trennen, Bedingungen überprüfen und behandeln und sie allgemeinen Regeln unterwerfen.
Ich möchte die Implementierung der Zustandsmaschine für Zend Framework 3 mit Doctrine 2 teilen
mit der Datenbank arbeiten. Das Projekt selbst finden Sie hier .
Und hier möchte ich die festgelegten Grundprinzipien teilen.
Fangen wir an
Wir werden die Beschreibung des Übergangsgraphen in der Datenbanktabelle speichern, weil:
- Das ist klar.
- Ermöglicht die Verwendung des gleichen Statuswörterbuchs, das auch für das gewünschte verwendet wird
uns ein Objekt mit einem Zustand. - Ermöglicht die Gewährleistung der Integrität der Datenbank mithilfe von Fremdschlüsseln.
Die Verwendung einer nicht deterministischen Finite-State-Maschine erhöht die Flexibilität unserer Lösung.
Der Übergangsgraph wird unter Verwendung eines Paares von Tabellen A und B beschrieben, die durch eine Eins-zu-Viele-Beziehung verbunden sind.
Tabelle a:
CREATE TABLE `tr_a` ( `id` int(11) NOT NULL AUTO_INCREMENT, `src_id` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `action_id` varchar(64) COLLATE utf8_unicode_ci NOT NULL COMMENT ' ', `condition` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' ', PRIMARY KEY (`id`), KEY `IDX_96B84B3BFF529AC` (`src_id`), KEY `IDX_96B84B3B9D32F035` (`action_id`), CONSTRAINT `FK_96B84B3B9D32F035` FOREIGN KEY (`action_id`) REFERENCES `action` (`id`), CONSTRAINT `FK_96B84B3BFF529AC` FOREIGN KEY (`src_id`) REFERENCES `state` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Tabelle B:
CREATE TABLE `tr_b` ( `id` int(11) NOT NULL AUTO_INCREMENT, `transition_a_id` int(11) NOT NULL, `dst_id` varchar(32) COLLATE utf8_unicode_ci NOT NULL, `weight` int(11) DEFAULT NULL COMMENT ' ,- , null- ', `condition` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' ', `pre_functor` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' , , ', `post_functor` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL COMMENT ' , , ', PRIMARY KEY (`id`), KEY `IDX_E12699CB85F4C374` (`transition_a_id`), KEY `IDX_E12699CBE1885D19` (`dst_id`), CONSTRAINT `FK_E12699CB85F4C374` FOREIGN KEY (`transition_a_id`) REFERENCES `tr_a` (`id`), CONSTRAINT `FK_E12699CBE1885D19` FOREIGN KEY (`dst_id`) REFERENCES `state` (`id`) ) ENGINE=InnoDB AUTO_INCREMENT=16 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci
Ich werde die Definition von Entitätsklassen weglassen, Sie können hier ein Beispiel sehen:
- Tabelle a
- Tabelle B.
Hier ist alles Standard, einschließlich einer Beschreibung der Beziehung zwischen Entitäten.
Um die Zustandsmaschine zu verwenden, benötigen wir nur wenige öffentliche Methoden
public function doAction($objE, $action, array $data = []) /** * * @param object $objE * @param string $action * @param array $data * @return bool */ public function hasAction($objE, $action, $data=[])
Es gibt mehrere weitere öffentliche Methoden für eine komfortable Verwendung, aber hier möchte ich auf den Algorithmus der Hauptmethode doAction()
.
Aus dem Objekt erhalten wir seinen Zustand.
Wenn Entity-A diese und die Aktionskennung kennt, ist sie in der Übergangstabelle A leicht zu finden.
Mit der durch die Bedingungskennung erhaltenen Bedingung, die in der condition
von Entität A liegt, können Sie überprüfen, ob die Aktion ausgeführt werden kann. Insbesondere kann die zu Beginn erwähnte RBAC-Klausel im Bedingungsvalidator verwendet werden.
Der Validator wird durch den Bezeichner aus dem condition
über den ValidatorPluginManager
und gefunden
sollte \Zend\Validator\ValidatorInterface
implementieren. Ich bevorzuge die Erben von ValidatorChain
. Dies macht es einfach, die Zusammensetzung kontrollierter Bedingungen zu ändern und einfache Validatoren als Teil von Testketten wiederzuverwenden.
Wir haben den Übergang bestimmt, den Zustand überprüft. Da haben wir eine nicht deterministische
Zustandsmaschine kann das Ergebnis der Aktion einer von mehreren Zuständen sein.
Solche Fälle sind nicht sehr häufig, aber das vorgeschlagene Projekt ist einfach umzusetzen.
Durch die Verbindung A - <B erhalten wir eine Sammlung möglicher neuer Zustände des Objekts (Entität-B).
Um einen einzelnen Status auszuwählen, überprüfen wir nacheinander die Bedingungen von Entity-B aus der resultierenden Sammlung und sortieren sie im weight
nach weight
von größer nach kleiner. Die erste erfolgreiche Überprüfung der Bedingung ergibt Entität B, in der sich ein neuer Status des Objekts befindet (siehe Feld dst_id
).
Der neue Zustand wird bestimmt. Bevor Sie den Status ändern, wird die Statusmaschine ausgeführt
Aktionen, die im Prefunctor definiert sind, ändert dann den Status und führt Aktionen aus.
in der Nachfunktion definiert. Die Zustandsmaschine erhält Funktoren basierend auf dem Namen aus dem Feld pre_functor
für die Präfunktion und post_functor
für die post_functor
Verwendung des Plug-in-Managers und ruft die Methode __invoke () für die empfangenen Objekte auf.
Es ist nicht erforderlich, den Status mithilfe von Funktoren zu ändern. Dies ist die Aufgabe der Zustandsmaschine. Wenn beim Ändern des Status keine zusätzlichen Aktionen ausgeführt werden müssen, setzen Sie die obigen Felder auf null.
Andere Chips:
- In den Feldern der Übergangstabellenbedingung
pre_funtor
, post_functor
ich Aliase, meiner Meinung nach ist dies praktisch. - Erstellen Sie zur besseren Übersichtlichkeit eine Ansicht aus den Tabellen A und B.
- Ich verwende Zeichenfolgenbezeichner als Primärschlüssel in Status- und Aktionswörterbüchern. Dies ist nicht notwendig, aber praktisch. Wörterbücher mit numerischen Bezeichnern können ebenfalls verwendet werden.
- Da eine nichtdeterministische endliche Zustandsmaschine verwendet wird, muss die Aktion nicht zu einer Statusänderung führen. Auf diese Weise können Sie Aktionen wie beispielsweise das Anzeigen beschreiben.
- Neben Methoden zum Überprüfen einer Aktion und Ausführen einer Aktion gibt es eine Reihe öffentlicher Methoden, mit denen beispielsweise eine Liste von Aktionen für einen bestimmten Status eines Objekts oder eine Liste verfügbarer Aktionen für einen bestimmten Status eines Objekts unter Berücksichtigung von Überprüfungen abgerufen werden kann . Oft müssen Sie in der Benutzeroberfläche in Rastern für jeden Datensatz eine Reihe von Aktionen anzeigen. Diese Methoden von Zustandsautomaten helfen dabei, die erforderliche Liste zu erhalten.
- Natürlich können andere Zustandsautomaten innerhalb von Funktoren aufgerufen werden. Darüber hinaus können Sie sich selbst anrufen, jedoch mit einem anderen Objekt oder mit demselben Objekt, jedoch nach einer Zustandsänderung (d. H. In einem Post-Funktor). Dies ist manchmal nützlich, um kaskadierende Übergänge unter sich ändernden "plötzlichen" Bedingungen des Kunden zu organisieren;)
Fazit
Trotz der vielen Aufgaben, die sich ideal für den Einsatz von Zustandsautomaten eignen, verwenden Webprogrammierer diese relativ selten. Die gleichen Entscheidungen, die ich sah, schienen mir ungeheuerlich.
Ich hoffe, dass die vorgeschlagene Lösung jemandem hilft, Zeit bei der Implementierung zu sparen.