Pull to refresh

Создаем Конечный Автомат на PHP

Reading time6 min
Views13K
Original author: Jorge Castro

Что такое Конечный Автомат?

Конечный Автомат (State Machine), также называемый Automata (да, как и игра), - это концепция для разработки, организации рабочих и технологических процессов с учетом текущего «состояния» какой-то задачи, изменения её состояний и, по возможности, для автоматизации процесса.

Так же хочу заметить в разнице между английским и русским названием: State Machine - дословно машина состояний, то есть система которая, выполняя действия, переходит от одного состояния к другому. Конечный Автомат - это устройство что выполняет какие-то автоматизированные действия и число возможных внутренних состояний которого конечно.

Я объясню на примере. Предположим, что я хочу купить молоко, тогда такая задача будет иметь примерно следующие состояния:

  • Начальное состояние

  • Поездка в магазин

  • Взятие молока

  • Произведение оплаты за молоко

  • Поездка обратно домой

Где это используется ?

Везде. В большинстве (если не во всех) бизнес-процессах, требующих как минимум двух «состояний». Например: службы доставки, операции купли-продажи, процесс найма и т.д.

Внимание! если вы новичок или опыт с ООП мал тогда не воспринимайте статью как руководство к действию. Конечные Автоматы не популярны коммерческой разработке и могут быть полезны в проектах, которые направлены на транзакции, например платёжные шлюзы. Задумываются о них лишь когда запутанность транзакций превышает все мыслимые пределы.

Зачем мне нужен Конечный Автомат?

Вернемся назад к примеру с молоком. Зачем мне нужно создавать конечный автомат, в котором процесс идет от А до Я?. Загвоздка в том что всегда что-то случается, могут возникнуть ошибки, недоразумения или проблемы.

Пример, в случае покупки молока: что делать, если я забуду деньги, или магазин закрыт, или все молоко разобрали, или если возникнут проблемы с вождением (гололёд, снегопад) ?

Таким образом состояния для нас теперь следующие:

  • Начальное состояние

  • Поездка в магазин

  • Отмена поездки

  • Взятие молока

  • Произведение оплаты за молоко

  • Невозможность покупки

  • Поездка обратно домой

Можно даже добавить больше состояний, но для нашего исследования тут хватит основных. Также возможно сжатие состояний и использование меньшего их количества, например "состояние отмены поездки" и "состояние отмены покупки молока" можно объединить просто в «Отмена».

Как автоматизировать процесс?

А теперь, как автоматизировать процесс, а точнее, как я могу автоматизировать изменение состояний? Каждое изменение состояния называется ПЕРЕХОДОМ. Переход возможен при изменении значений переменных, результатов функции/методов или истечения/наступления времени - это все система использует для автоматизации. На примере молока я буду использовать следующие значения (поля):

  • milk = 0 нет молока, milk = 1 у меня есть молоко

  • money = кол-во денег в кармане

  • price = цена молока

  • stock_milk = количество молока в магазине

  • store_open = 0 магазин закрыт, = 1 открыт

  • gas = бензин моей машины

Так мы можем сформировать состояния и переходы:

Начальное состояние

Состояние после перехода

Когда возможен переход?

Исходное состояние

Поездка за молоком

Когда milk = 0 и gas > 0

Поездка в магазин

Отмена поездки (это конец процесса)

Когда gas = 0

Поездка в магазин

Взятие молока

store_open = 1 и stock_milk > 0

Поездка в магазин

Невозможность покупки

store_open = 0 или stock_milk = 0

Взятие молока

Произведение оплаты за молоко

(это также увеличивает milk +1)

money >= price

Взятие молока

Невозможность покупки

money < price

Невозможность покупки

Поездка обратно домой (конец процесса)

всегда

Плата за молоко

Поездка обратно домой (конец процесса)

всегда

Код PHP

В нашем примере (меньше 100 строк кода) мы опускаем некоторые ключевые части проекта. Например, если мы захотим использовать робота, который будет выполнять всю работу от и до, то ему потребуются специальные датчики и груда программного обеспечения с которым нужно будет интегрироваться - так вот этого всего делать мы не будем :)

Теперь давайте запрограммируем это.

Во-первых, используя Composer скачаем нужную для нашего примера библиотеку (она бесплатна для личных и коммерческих проектов):

composer require eftec/statemachineone

Библиотека поддерживает различные типы подключений к базам данных. В этом примере мы будем использовать MySQL и коннектор MySQLi.

Создадим новый проект: нам нужно запрограммировать только один файл.

Часть первая

Сначала мы инициализировали код, добавили библиотеку и создали новый экземпляр StateMachineOne();

<?php

use eftec\statemachineone\StateMachineOne;

require __DIR__ . "/vendor/autoload.php";

$sMachine = new StateMachineOne(null);
$sMachine->setDebug(true);

Вторая, наши переменные

Теперь мы определяем наши переменные, такие как состояния и начальные значения.

<?php
// состояния специфичны для данного проекта
const INITIAL_STATE = 1;
const DRIVING_TO_BUY_MILK = 2;
const CANCEL_DRIVING = 3;
const PICKING_THE_MILK = 4;
const PAYING_FOR_THE_MILK = 5;
const UNABLE_TO_PURCHASE = 6;
const DRIVE_BACK_HOME = 7;
$sMachine->setDefaultInitState(INITIAL_STATE);

$sMachine->setStates([
    INITIAL_STATE => 'Начальное состояние',
    DRIVING_TO_BUY_MILK => 'Поездка в магазин',
    CANCEL_DRIVING => 'Отмена поездки',
    PICKING_THE_MILK => 'Взятие молока',
    PAYING_FOR_THE_MILK => 'Произведение оплаты за молоко',
    UNABLE_TO_PURCHASE => 'Невозможность покупки',
    DRIVE_BACK_HOME => 'Поездка обратно домой',
]);

$sMachine->fieldDefault = [
    'milk' => 0,
    'money' => 9999,
    'price' => 10,
    'stock_milk' => 80,
    'store_open' => true,
    'gas' => 10,
];

Итак, используем ли мы те же состояния, определенные концептуально? Да и это важно. Очень легко пропустить шаг или важную операцию (такое может привести к порче всей цепочки переходов), поэтому код должен быть как можно более чистым.

Третья, подключение к базе данных

Теперь мы подключились к базе данных (MySQL). Мы должны установить базу данных (в примере база установлена на localhost), пользователя (root), пароль (root) и базу данных/схему для использования (statemachinedb). Библиотека использует базу данных как дополнительный компонент. Автоматически будут созданы две таблицы: buymilk_jobs и buymilk_logs (можно создать и вручную если хочется).

<?php
// Параметры базы данных
$sMachine->tableJobs = "buymilk_jobs";
$sMachine->tableJobLogs = "buymilk_logs"; // опционально
$sMachine->setDB('mysql', 'localhost', 'root', 'root', 'my_automata_php');
$sMachine->createDbTable(false); // true - создавать новую таблицу при каждом запуске.

$sMachine->loadDBAllJob(); // Загружаем все задачи, в том числе готовые.
//$sMachine->loadDBActiveJobs(); // для использования на проде. Загружает все задания из БД со всеми активными состояниями

Четвертая, определение переходов

Теперь мы определяем переходы между состояниями.

<?php
// Бизнес правила
$sMachine->addTransition(INITIAL_STATE, DRIVING_TO_BUY_MILK, 'when milk = 0 and gas > 0');
$sMachine->addTransition(INITIAL_STATE, CANCEL_DRIVING, 'when gas = 0', 'stop');
$sMachine->addTransition(DRIVING_TO_BUY_MILK, PICKING_THE_MILK, 'when store_open = 1 and stock_milk > 0');
$sMachine->addTransition(DRIVING_TO_BUY_MILK, UNABLE_TO_PURCHASE, 'when store_open = 0 or stock_milk = 0');
$sMachine->addTransition(PICKING_THE_MILK, PAYING_FOR_THE_MILK, 'when money >= price set milk = 1');
$sMachine->addTransition(PICKING_THE_MILK, UNABLE_TO_PURCHASE, 'when money < price');
$sMachine->addTransition(UNABLE_TO_PURCHASE, DRIVE_BACK_HOME, 'when timeout', 'stop');
$sMachine->addTransition(PAYING_FOR_THE_MILK, DRIVE_BACK_HOME, 'when timeout', 'stop');

Что означает строка «when…». ? Это язык конечного автомата, он базовый, но работу свою делает. Однако для каждой команды требуется место. Также обратите внимание на пример: money <price неверно, а money < price (видим пробелы) правильно.

Пятая, и, наконец, мы объединим все это вместе.

В чем заключается магия Автоматов - в методе checkAllJobs(). Кроме того, у библиотеки есть визуальный интерфейс, чтобы мы могли тестировать задания. Этот UI не является обязательным, т.к. каждое задание может быть запрограммировано с помощью кода.

<?php
$msg = $sMachine->fetchUI();// читает пользовательский ввод из UI и возвращает вспомогательное сообщение (это необязательно и нужно только для отладки).
$sMachine->checkAllJobs(); // проверяем все доступные задачи

$sMachine->viewUI(null, $msg); // null означает что будем отлаживать текущую задачу

Тестирование с использованием пользовательского интерфейса

Если конфигурация верна, то на странице должен отображаться новый экран.

Теперь давайте создадим новую задачу. Давайте начнем с начальными значениями:milk = 0, money = 999 и gas = 10 и нажмем на кнопку "Create a new job". Теперь задача была создана и перешла из состояния "Вождение автомобиля"(1) в состояние " Купить молоко"(2).

Job #11 2018–12–09 19:06:11.232900 [INFO]: state changed from 1 to 2 changed (it is a log message thanks to the debug option)

Давайте установим следующие значения, store_open=1 и stock_milk=1 и нажмем на кнопку Set field values (она установит новые значения и проверит все условия).

Таким образом, работа перескочила с Поездка в магазин (2) -> Взятие молока (4), и она устанавливает значение молока равным 1.

Кроме того, работа перескочила с Взятие молока (4) -> Оплата молока (5) на том же шаге. Почему? Это потому, что действие соответствует ее условиям перехода.

Job #11 2018–12–09 19:09:06.502700 [INFO]: state changed from 2 to 4 changed setting milk = 1
Job #11 2018–12–09 19:09:06.514600 [INFO]: state changed from 4 to 5 changed

Теперь давайте нажмем кнопку «Refresh» (она снова проверит все задания), и задача изменит состояние с «Произведение оплаты за молоко» (5) -> «Поездка обратно домой» (7), и задача остановится, завершив цикл.

Job #11 2018–12–09 19:12:41.435900 [INFO]: state changed from 5 to 7 stopped

Замечания и предложения

Мы ознакомились с теорией Конечных Автоматов и немного применили на практике. Используемая библиотека содержит ошибки (пришлось напрячься чтобы запустить её) но для академического интереса вполне подходит. Данная статья является переводом, так что если найдете ошибки - пишите.

Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
Total votes 15: ↑11 and ↓4+9
Comments11

Articles