dhtmlxGantt mit PHP: Slim

Diese Anleitung beschreibt, wie ein Gantt-Diagramm auf PHP-Basis mit dem Slim 4 Framework und einer RESTful API auf dem Server eingerichtet wird.

Diese Anleitung gilt für Slim Framework v4.x. Wenn Sie mit einer älteren Version arbeiten, schauen Sie sich die Slim Framework v3.x Anleitung an.

Es gibt auch Tutorials zur Integration mit anderen Plattformen und Frameworks:

Dieses Beispiel verwendet Slim 4 für das Routing und MySQL für die Datenspeicherung. Die CRUD-Logik ist mit PDO aufgebaut und soll an andere Frameworks anpassbar sein.

Der vollständige Quellcode ist auf GitHub verfügbar.

Schritt 1. Projekt einrichten

Projekt erstellen

Wir verwenden die Skeleton-Anwendung für das Slim 4 Framework.

Beginnen Sie mit dem Import des Projekts und der Installation mit Composer:

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

Falls Composer global installiert ist, können Sie stattdessen Folgendes ausführen:

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

Überprüfen Sie als Nächstes, ob alles funktioniert. Navigieren Sie zum Anwendungsverzeichnis und starten Sie einen Webserver:

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

Öffnen Sie anschließend http://127.0.0.1:8080 in Ihrem Browser. Sie sollten die Standard-Slim-Seite sehen.

Schritt 2. Gantt zur Seite hinzufügen

Erstellen wir nun eine Seite, die das Gantt-Diagramm enthält. Dieser Prozess umfasst zwei einfache Schritte.

Ansicht erstellen

Erstellen Sie eine Datei namens basic.html im Ordner app/templates. Diese Datei enthält das Gantt-Diagramm und dessen notwendige Einrichtung zum Laden von Daten.

Hier ist der vollständige Code:

app/templates/basic.html

<!DOCTYPE html>
<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> </html>

Dieser Code erstellt ein leeres Gantt-Diagramm auf der Seite. Benutzer können Aufgaben und Verknüpfungen hinzufügen und bearbeiten, aber Änderungen bleiben nach dem Neuladen der Seite nicht bestehen.

Routen einrichten

Um die neue Seite im Browser zugänglich zu machen, fügen Sie app/routes.php eine Route hinzu:

app/routes.php

$app->get('/', function (Request $request, Response $response) {
$payload = file_get_contents(__DIR__.'/templates/basic.html');
$response->getBody()->write($payload);
return $response;
});

Starten Sie die App neu:

command line

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

Öffnen Sie nun http://127.0.0.1:8080/ in Ihrem Browser, um das Gantt-Diagramm auf der Seite zu sehen.

Schritt 3. Datenbank einrichten

Zu diesem Zeitpunkt wird das Gantt-Diagramm angezeigt, speichert jedoch keine Daten. Der nächste Schritt besteht darin, eine Datenbank einzurichten und sie mit der Anwendung zu verbinden.

Datenbank erstellen

Sie können eine Datenbank mit Ihrem bevorzugten MySQL-Client wie phpMyAdmin oder direkt über die Konsole einrichten. Unten ist ein Beispiel für SQL-Code, um eine grundlegende Datenbank mit zwei Tabellen zu erstellen.

CREATE DATABASE  IF NOT EXISTS `gantt`;
USE `gantt`;
 
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`)
);

Sobald die Datenbank eingerichtet ist, können Sie einige Beispieldaten in die Tabelle gantt_tasks zur Testzwecken hinzufügen. Hier ist ein einfaches Beispiel:

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

Für ein detaillierteres Beispiel schauen Sie sich diese Anleitung an.

Mit der Datenbank und den Beispieldaten können Sie mit dem Laden der Daten fortfahren.


Schritt 4: Daten laden

Arbeiten wir nun am Laden von Daten aus der Datenbank. Auf der Client-Seite können Daten mit der Methode gantt.load abgerufen werden:

app/templates/basic.html

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

Dies sendet eine AJAX-Anfrage an die angegebene URL und erwartet, dass die Antwort Gantt-Daten im JSON-Format enthält.

Der Parameter gantt.config.date_format wird hier ebenfalls gesetzt, um das vom Datenquellen verwendete Datumsformat festzulegen, was das Parsen der Daten für den Client erleichtert.

Auf der Serverseite müssen Sie einen Handler für diese Anfrage erstellen. Öffnen Sie die Datei app/routes.php und fügen Sie eine neue Route hinzu:

app/routes.php

$app->get('/data',  'getGanttData');

Implementieren Sie als Nächstes die Logik für die Funktion getGanttData. Um die Dinge organisiert zu halten, können Sie alle Gantt-bezogenen Logiken in eine separate Datei legen. Erstellen Sie eine neue Datei app/gantt.php und fügen Sie den folgenden Code hinzu:

app/gantt.php

<?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);
  }
 
  $payload = json_encode($result);
 
  $response->getBody()->write($payload);
  return $response->withHeader("Content-Type", "application/json");
};

Schließlich fügen Sie app/gantt.php in app/routes.php ein:

app/routes.php

<?php
declare(strict_types=1);
 
use App\Application\Actions\User\ListUsersAction;
use App\Application\Actions\User\ViewUserAction;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
 
// Fügen Sie dhtmlxGantt CRUD hinzu
require __DIR__ . "/gantt.php";
 
return function (App $app) {
    $app->get("/", function (Request $request, Response $response) {
        $payload = file_get_contents(__DIR__."/templates/basic.html");
        $response->getBody()->write($payload);
        return $response;
    });
 
    $app->get("/data",  "getGanttData");
 
    $app->group("/users", function (Group $group) {
        $group->get("", ListUsersAction::class);
        $group->get("/{id}", ViewUserAction::class);
    });
};

Hier ist eine kurze Zusammenfassung des Codes:

  • Eine Route wird in app/routes.php definiert, um Datenanfragen zu bearbeiten.
  • Der Handler liest alle Aufgaben und Verknüpfungen aus der Datenbank und sendet sie an den Client im JSON-Format.
  • Die Eigenschaft open wird den Aufgabenobjekten hinzugefügt, um sicherzustellen, dass der Aufgabenbaum standardmäßig erweitert ist.

Sobald dies erledigt ist, können Sie http://127.0.0.1:8080/ öffnen, um das Gantt-Diagramm mit den Testdaten zu sehen.


Schritt 5: Änderungen speichern

Der nächste Schritt besteht darin, die auf der Client-Seite vorgenommenen Änderungen zurück an den Server zu speichern. Dies wird typischerweise mit der eingebauten dataProcessor Bibliothek von Gantt erledigt. Fügen Sie den folgenden Code zu basic.html hinzu:

app/templates/basic.html

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");

Hierbei hört der DataProcessor auf Client-seitige Aktionen wie Hinzufügen, Aktualisieren oder Löschen von Daten und sendet entsprechende AJAX-Anfragen an den Server. Der REST-Modus stellt sicher, dass unterschiedliche HTTP-Methoden für unterschiedliche Aktionen verwendet werden. Weitere Details zu den Routen finden Sie hier.

Um diese Routen in der App zu behandeln, aktualisieren Sie app/routes.php:

app/routes.php

<?php
 
declare(strict_types=1);
 
use App\Application\Actions\User\ListUsersAction;
use App\Application\Actions\User\ViewUserAction;
use Psr\Http\Message\ResponseInterface as Response;
use Psr\Http\Message\ServerRequestInterface as Request;
use Slim\App;
use Slim\Interfaces\RouteCollectorProxyInterface as Group;
 
// Fügen Sie dhtmlxGantt CRUD hinzu
require __DIR__ . "/gantt.php";
 
return function (App $app) {
    $app->get("/", function (Request $request, Response $response) {
        $payload = file_get_contents(__DIR__."/templates/basic.html");
        $response->getBody()->write($payload);
        return $response;
    });
 
    $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");
};

Implementieren Sie nun die mit diesen Routen verknüpften Methoden in app/gantt.php. Der Code für diese Methoden ist unkompliziert – sie behandeln das Erstellen, Aktualisieren und Löschen von Aufgaben und Verknüpfungen. Beim Einfügen wird die Datenbank-ID des neuen Elements an den Client zurückgesendet.

Der clientseitige Gantt behandelt standardmäßig verwandte Aufgaben und Verknüpfungen, sodass hier keine Datenbankbeziehungen verwaltet werden müssen. Wenn Sie diese jedoch auf der Serverseite verwalten möchten, können Sie die cascade_delete Konfiguration aktivieren.

Wenn alles bereit ist, führen Sie die Anwendung aus und öffnen Sie http://127.0.0.1:8080, um das voll funktionsfähige Gantt-Diagramm zu sehen.


Speichern der Aufgabenreihenfolge

Gantt ermöglicht das Umsortieren von Aufgaben mit Drag-and-Drop-Funktionalität. Um diese Funktion zu unterstützen, müssen Sie die Aufgabenreihenfolge in der Datenbank speichern. Weitere Details finden Sie hier.

Aufgaben-Umsortierung auf dem Client aktivieren

Um Benutzern das Umsortieren von Aufgaben in der Benutzeroberfläche zu ermöglichen, beginnen Sie mit der Änderung der Konfiguration in der Datei basic.html. Aktualisieren Sie sie wie unten gezeigt:

app/templates/basic.html

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

Nehmen Sie als Nächstes Änderungen auf der Serverseite vor, um die Aufgabenreihenfolge in einer Spalte namens sortorder zu speichern. Hier ist ein Beispiel, wie die aktualisierte Tabelle gantt_tasks aussehen könnte:

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 );

Wenn Sie bereits eine Tabelle haben, können Sie einfach die Spalte sortorder hinzufügen:

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

Aktualisieren Sie nun die CRUD-Operationen in app/gantt.php.

  1. GET /data sollte die Aufgaben nach der Spalte sortorder sortiert zurückgeben:

app/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);
  }
  $payload = json_encode($result);
 
  $response->getBody()->write($payload);
  return $response->withHeader("Content-Type", "application/json");
};
  1. Beim Hinzufügen einer neuen Aufgabe muss sichergestellt werden, dass sie einen initialen sortorder-Wert erhält:

app/gantt.php

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);
 
  $result = [
    "action" => "inserted",
    "tid" => $db->lastInsertId()
  ];
  $payload = json_encode($result);
 
  $response->getBody()->write($payload);
  return $response->withHeader("Content-Type", "application/json");
}
  1. Wenn Aufgaben umsortiert werden, sollte ihre neue Reihenfolge gespeichert werden. Dies kann wie folgt gehandhabt werden:

app/gantt.php

// update a task
function updateTask($request, $response, $args) {
  $sid = $request->getAttribute("id");
  parse_str(file_get_contents("php://input"), $body);
  $task = [
      "text" => $body["text"],
      "start_date" => $body["start_date"],
      "duration" => $body["duration"],
      "progress" => $body["progress"],
      "parent" => $body["parent"],
      "sortorder" => $body["sortorder"]
  ];
  $db = getConnection();
  $query = "UPDATE gantt_tasks ".
    "SET text = :text, start_date = :start_date, duration = :duration,". 
      "progress = :progress, parent = :parent, sortorder = :sortorder ".
    "WHERE id = :sid";
  $db->prepare($query)->execute(array_merge($task, [":sid" => $sid]));
 
  if (isset($body["target"]) && $body["target"]) {
    updateOrder($sid, $body["target"], $db);
  }
 
  $result = [
    "action" => "updated"
  ];
  $payload = json_encode($result);
 
  $response->getBody()->write($payload);
  return $response->withHeader("Content-Type", "application/json");
}
 
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
  ]);
}

Ein einsatzbereites Demo ist auf GitHub verfügbar.

Verwendung von dhtmlxConnector

Alternativ können Sie ein PHP-Backend mit der Bibliothek dhtmlxConnector einrichten. Ein Schritt-für-Schritt-Tutorial ist hier verfügbar.

Anwendungssicherheit

Beachten Sie, dass Gantt selbst nicht gegen Bedrohungen wie SQL-Injections, XSS oder CSRF-Angriffe schützt. Die Gewährleistung der Sicherheit der Anwendung liegt in der Verantwortung des Entwicklers. Weitere Details finden Sie hier.

Fehlerbehebung

Wenn Aufgaben und Verknüpfungen nach den Einrichtungsschritten nicht angezeigt werden, schauen Sie sich die Fehlerbehebungsanleitung in diesem Artikel an.

Was kommt als Nächstes

Sie haben nun ein voll funktionsfähiges Gantt-Diagramm. Der vollständige Code ist auf GitHub verfügbar, wo Sie ihn für Ihre Projekte klonen oder herunterladen können.

Für weitere Funktionen und Integrationen erkunden Sie die Anleitungen oder Tutorials zur Integration von Gantt mit anderen Backend-Frameworks.

Zurück nach oben