Это руководство охватывает настройку диаграммы Ганта с использованием PHP версий 5.6x-7.x с RESTful API на сервере.

Это руководство основано на старой версии Slim Framework v3.x. Для последней версии ознакомьтесь с документацией Slim Framework v4.x.

Если вас интересует интеграция на стороне сервера с другими платформами или фреймворками, ознакомьтесь с этими руководствами:

Для этой настройки фреймворк Slim 3 будет обрабатывать маршрутизацию, а MySQL будет служить в качестве базы данных. Операции CRUD будут реализованы с использованием PDO, что обеспечит совместимость с другими фреймворками.

Полный исходный код можно найти на GitHub.

Шаг 1. Инициализация проекта

Создание проекта

Для начала будет использовано скелетное приложение для фреймворка Slim 3.

Сначала проект необходимо импортировать и установить. Composer может помочь в этом:

php composer.phar create-project slim/slim-skeleton gantt-rest-php

Если Composer установлен глобально, можно использовать:

composer create-project slim/slim-skeleton gantt-rest-php

После этого убедитесь, что все работает правильно. Перейдите в папку приложения и запустите веб-сервер:

cd gantt-rest-php
php -S 0.0.0.0:8080 -t public public/index.php

Теперь откройте http://127.0.0.1:8080 в браузере, чтобы увидеть страницу по умолчанию Slim.


Шаг 2: Добавление Ганта на страницу

Далее необходимо создать страницу с диаграммой Ганта. Найдите страницу по умолчанию в templates/index.phtml. Здесь будет добавлена диаграмма Ганта, а также настройка для загрузки данных.

Вот полный код страницы:

/templates/index.phtml

<!DOCTYPE html>
<head>
  <meta http-equiv="Content-type" content="text/html; charset=utf-8">
 
  <script src="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.js"></script>
  <link href="https://cdn.dhtmlx.com/gantt/edge/dhtmlxgantt.css" rel="stylesheet">
 
  <style type="text/css">
    html, body{
      height:100%;
      padding:0px;
      margin:0px;
      overflow: hidden;
    }
</style> </head> <body> <div id="gantt_here" style='width:100%; height:100%;'></div> <script type="text/javascript">
    gantt.init("gantt_here");
</script> </body>

Этот код отобразит пустую диаграмму Ганта на странице. Пользователи смогут создавать и изменять задачи и связи, но изменения не сохранятся после перезагрузки страницы.

Для тестирования перезапустите приложение:

command line

php -S 0.0.0.0:8080 -t public public/index.php

Затем откройте http://127.0.0.1:8080/ в браузере. Диаграмма Ганта должна появиться на странице.


Шаг 3: Настройка базы данных

Следующим шагом является настройка базы данных. Простая база данных с двумя таблицами будет достаточной.

CREATE TABLE `gantt_links` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `source` int(11) NOT NULL,
  `target` int(11) NOT NULL,
  `type` varchar(1) NOT NULL,
  PRIMARY KEY (`id`)
);
 
CREATE TABLE `gantt_tasks` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `text` varchar(255) NOT NULL,
  `start_date` datetime NOT NULL,
  `duration` int(11) NOT NULL,
  `progress` float NOT NULL,
  `parent` int(11) NOT NULL,
  PRIMARY KEY (`id`)
);

После подготовки базы данных заполните таблицу gantt_tasks тестовыми данными, используя следующий SQL:

INSERT INTO `gantt_tasks` VALUES ('1', 'Project #1', '2017-04-01 00:00:00', '5', '0.8', '0');
INSERT INTO `gantt_tasks` VALUES ('2', 'Task #1', '2017-04-06 00:00:00', '4', '0.5', '1');
INSERT INTO `gantt_tasks` VALUES ('3', 'Task #2', '2017-04-05 00:00:00', '6', '0.7', '1');
INSERT INTO `gantt_tasks` VALUES ('4', 'Task #3', '2017-04-07 00:00:00', '2', '0', '1');
INSERT INTO `gantt_tasks` VALUES ('5', 'Task #1.1', '2017-04-05 00:00:00', '5', '0.34', '2');
INSERT INTO `gantt_tasks` VALUES ('6', 'Task #1.2', '2017-04-11 13:22:17', '4', '0.5', '2');
INSERT INTO `gantt_tasks` VALUES ('7', 'Task #2.1', '2017-04-07 00:00:00', '5', '0.2', '3');
INSERT INTO `gantt_tasks` VALUES ('8', 'Task #2.2', '2017-04-06 00:00:00', '4', '0.9', '3');

Более подробную информацию можно найти здесь.

С завершением настройки проекта пришло время переходить к загрузке данных.


Шаг 4: Загрузка данных

Теперь будет реализована загрузка данных из базы данных. На стороне клиента данные запрашиваются с помощью метода gantt.load:

/templates/index.phtml

gantt.config.date_format = "%Y-%m-%d %H:%i:%s"; 
gantt.init("gantt_here");
gantt.load("/data");

Это отправляет AJAX-запрос на указанный URL, ожидая ответ с данными Ганта в формате JSON.

Значение date_format также указывается здесь, чтобы на стороне клиента знали, как парсить формат даты источника данных.

Следующим шагом будет добавление обработчика для этого запроса на серверной стороне. В src/routes.php добавляется новый маршрут:

src/routes.php

<?php
// Routes
 
$app->get('/', function ($request, $response, $args) {
  // Render index view
  return $this->renderer->render($response, 'index.phtml', $args);
});
 
$app->get('/data',  'getGanttData');

Логика для getGanttData будет реализована следующим образом. Чтобы все было организовано, вся функциональность, связанная с Гантом, будет помещена в отдельный файл.

Создайте новый файл, src/gantt.php, и добавьте следующий код:

src/gantt.php

function getConnection()
{
    return new PDO("mysql:host=localhost;dbname=gantt", "root", "root", [
      PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
      PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
    ]);
}
 
function getGanttData($request, $response, $args) {
  $db = getConnection();
  $result = [
    "data"=> [],
    "links"=> []
  ];
 
  foreach($db->query("SELECT * FROM gantt_tasks") as $row){
    $row["open"] = true;
    array_push($result["data"], $row);
  }
 
  foreach ($db->query("SELECT * FROM gantt_links") as $link){
    array_push($result["links"], $link);
  }
 
  return $response->withJson($result);
};

Включите src/gantt.php в public/index.php:

public/index.php

<?php
// Existing code...
 
// Add dhtmlxGantt CRUD
require __DIR__ . '/../src/gantt.php';  
// Run app
$app->run();

Вот что происходит в коде:

  • В src/routes.php определяется маршрут для действия данных.
  • Обработчик читает все задачи и связи из базы данных и отправляет их в формате JSON клиенту.
  • Свойство open добавляется к объектам задач, чтобы отображать дерево задач в развернутом состоянии по умолчанию.

С реализованной загрузкой данных, открытие http://127.0.0.1:8080/ теперь должно показать диаграмму Ганта, заполненную тестовыми данными.


Шаг 5: Сохранение изменений

Следующим шагом будет сохранение изменений, внесенных на клиентской стороне, обратно на сервер. Для этого используется библиотека dataProcessor, встроенная в Гант.

В index.phtml добавьте следующие строки:

templates/index.phtml

gantt.config.date_format = "%Y-%m-%d %H:%i:%s";
 
gantt.init("gantt_here");
gantt.load("/data");
 
var dp = new gantt.dataProcessor("/data");dp.init(gantt);dp.setTransactionMode("REST");

dataProcessor реагирует на действия, такие как добавление, изменение или удаление данных в диаграмме, отправляя AJAX-запросы на сервер. Он работает в режиме REST, используя разные HTTP-методы для различных действий. Вот полный список маршрутов.

Далее эти маршруты будут добавлены в приложение, и их логика будет реализована. Перейдите в src/routes.php и обновите его:

src/routes.php

<?php
// Routes
 
$app->get('/', function ($request, $response, $args) {
  // Render index view
  return $this->renderer->render($response, 'index.phtml', $args);
});
 
$app->get('/data',  'getGanttData');
 
$app->post("/data/task", 'addTask');
$app->put("/data/task/{id}", 'updateTask');
$app->delete("/data/task/{id}", 'deleteTask');
 
$app->post("/data/link", 'addLink');
$app->put("/data/link/{id}", 'updateLink');
$app->delete("/data/link/{id}", 'deleteLink');

Теперь определите методы, связанные с этими маршрутами, в src/gantt.php:

src/gantt.php

// Existing code...
 
function getTask($data) {
  return [
    ':text' => $data["text"],
    ':start_date' => $data["start_date"],
    ':duration' => $data["duration"],
    ':progress' => isset($data["progress"]) ? $data["progress"] : 0,
    ':parent' => $data["parent"]
  ];
}
 
function getLink($data) {
  return [
    ":source" => $data["source"],
    ":target" => $data["target"],
    ":type" => $data["type"]
  ];
}
 
// Task and link CRUD functions...

Каждый метод обрабатывает базовые операции создания, обновления или удаления задач и связей. При добавлении новых элементов идентификатор базы данных возвращается клиенту.

Обратите внимание, что здесь не обрабатываются отношения базы данных. Например, вложенные задачи или связанные связи не удаляются автоматически при удалении задачи. Клиентская сторона обрабатывает это по умолчанию, отправляя отдельные запросы для каждой дочерней задачи или связи. Если предпочтительно обрабатывать это на стороне сервера, включите конфигурацию cascade_delete.

Теперь все настроено. Запустите приложение и посетите http://127.0.0.1:8080, чтобы увидеть полностью функциональную диаграмму Ганта.


Хранение порядка задач

Диаграмма Ганта позволяет перемещать задачи с помощью перетаскивания. Если эта функция используется, порядок необходимо сохранять в базе данных. Подробности об этом можно найти здесь.

Давайте добавим эту функцию в приложение.

Включение переупорядочивания задач на клиенте

Чтобы пользователи могли изменять порядок задач в интерфейсе, нужно внести несколько изменений. Начните с открытия представления Index и изменения конфигурации Ганта следующим образом:

/templates/index.phtml

gantt.config.order_branch = true;gantt.config.order_branch_free = true; 
gantt.init("gantt_here");

Затем убедитесь, что эти изменения отражены на серверной стороне. Порядок задач будет храниться в столбце "sortorder". Вот пример того, как может выглядеть обновленная таблица gantt_tasks:

CREATE TABLE `gantt_tasks` (
  `id` int(11) NOT NULL  AUTO_INCREMENT PRIMARY KEY,
  `text` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
  `start_date` datetime NOT NULL,
  `duration` int(11) NOT NULL,
  `progress` float NOT NULL DEFAULT 0,
  `parent` int(11) NOT NULL,
  `sortorder` int(11) NOT NULL );

Или, если таблица уже существует, вы можете просто добавить новый столбец:

ALTER TABLE `gantt_tasks` ADD COLUMN `sortorder` int(11) NOT NULL;

После обновления базы данных настройте операции CRUD в src/gantt.php.

  1. GET /data теперь должен возвращать задачи, упорядоченные по столбцу sortorder:

src/gantt.php

function getGanttData($request, $response, $args) {
 $db = getConnection();
 $result = [
   "data" => [],
   "links" => []
 ];
 
 foreach($db->query("SELECT * FROM gantt_tasks ORDER BY sortorder ASC") as $row){
   $row["open"] = true;
   array_push($result["data"], $row);
 }
 
 foreach ($db->query("SELECT * FROM gantt_links") as $link){
   array_push($result["links"], $link);
 }
 
 return $response->withJson($result);
}
  1. При добавлении новых задач убедитесь, что они получают начальное значение sortorder:

src/gantt.php

// create a new task
function addTask($request, $response, $args) {
 $task = getTask($request->getParsedBody());
 $db = getConnection();
 
 $maxOrderQuery = "SELECT MAX(sortorder) AS maxOrder FROM gantt_tasks";
 $statement = $db->prepare($maxOrderQuery);
 $statement->execute();
 
 $maxOrder = $statement->fetchColumn();
 if(!$maxOrder)
   $maxOrder = 0;
 
 $task[":sortorder"] = $maxOrder + 1;
 
 $query="INSERT INTO gantt_tasks(text,start_date,duration,progress,parent,sortorder)".
   "VALUES (:text,:start_date,:duration,:progress,:parent, :sortorder)";
 $db->prepare($query)->execute($task);
 
 return $response->withJson([
   "action"=>"inserted",
   "tid"=> $db->lastInsertId()
 ]);
}
  1. Когда задачи переупорядочиваются, порядок задач необходимо обновлять соответствующим образом. Вот как это можно сделать:

src/gantt.php

// update a task
function updateTask($request, $response, $args) {
  $sid = $request->getAttribute("id");
  $params = $request->getParsedBody();  $task = getTask($params);
  $db = getConnection();
  $query = "UPDATE gantt_tasks ".
    "SET text = :text, start_date = :start_date, duration = :duration, ".
      "progress = :progress, parent = :parent ".
    "WHERE id = :sid";
 
  $db->prepare($query)->execute(array_merge($task, [":sid"=>$sid]));
 
  if(isset($params["target"]) && $params["target"])    updateOrder($sid, $params["target"], $db);
 
  return $response->withJson([
    "action"=>"updated"
  ]);
}
 
function updateOrder($taskId, $target, $db){
  $nextTask = false;
  $targetId = $target;
 
  if(strpos($target, "next:") === 0){
    $targetId = substr($target, strlen("next:"));
    $nextTask = true;
  }
 
  if($targetId == "null")
    return;
 
  $sql = "SELECT sortorder FROM gantt_tasks WHERE id = :id";
  $statement = $db->prepare($sql);
  $statement->execute([":id"=>$targetId]);
 
  $targetOrder = $statement->fetchColumn();
  if($nextTask)
    $targetOrder++;
 
  $sql = "UPDATE gantt_tasks SET sortorder = sortorder + 1 ".
    "WHERE sortorder >= :targetOrder";
  $statement = $db->prepare($sql);
  $statement->execute([":targetOrder"=>$targetOrder]);
 
  $sql = "UPDATE gantt_tasks SET sortorder = :targetOrder WHERE id = :taskId";
  $statement = $db->prepare($sql);
  $statement->execute([
    ":targetOrder"=>$targetOrder,
    ":taskId"=>$taskId
  ]);
}

Вы можете найти готовый к использованию пример на GitHub.

Использование dhtmlxConnector

Другой способ настройки бэкенда — использование библиотеки dhtmlxConnector. Пошаговое руководство доступно здесь.

Безопасность приложения

Сам Гант не обрабатывает безопасность приложения, поэтому разработчики должны защититься от угроз, таких как SQL-инъекции, XSS и CSRF-атаки. Подробнее об этом можно прочитать в этой статье.

Устранение неполадок

Если задачи и связи не отображаются должным образом после выполнения шагов, вы можете обратиться к руководству по устранению неполадок в статье Устранение проблем с интеграцией бэкенда для возможных решений.

Что дальше

На данном этапе у вас должна быть полностью функциональная настройка Ганта. Полный код доступен на GitHub, где вы можете клонировать или скачать его для своих проектов.

Для получения дополнительных функций и интеграций ознакомьтесь с руководствами по функциям Ганта или изучите руководства по интеграции Ганта с другими фреймворками на стороне сервера.

К началу