Entwicklung einer RESTful PHP-API unter Ubuntu 20.04
Was ist eine API und wofür verwendet man sie?
Eine API (Application Programming Interface) fungiert als Vermittler zwischen Softwareanwendungen und ermöglicht die Kommunikation über das REST-Protokoll (Representational State Transfer). Ein typisches Beispiel: Eine mobile App greift über eine PHP-API auf eine MySQL-Datenbank in der Cloud zu.
Ein weiteres Einsatzgebiet ist das Bereitstellen von Schnittstellen für Dritte. Bekannte Unternehmen wie Google, Twitter und Facebook stellen APIs bereit, über die Nutzer direkt Daten anfragen oder übermitteln können – ganz ohne Benutzeroberfläche, sondern direkt über URL-Endpunkte.
Warum eine API für mehr Integration sorgt
Eine eigene API sorgt für mehr Flexibilität bei der Anbindung externer Anwendungen. Geben Sie die API-Antwort im JSON-Format zurück, können mobile Apps, Desktops, Tablets oder eingebettete Systeme sie direkt nutzen – ganz ohne Änderungen am Backend-Code.
In dieser Anleitung lernst du, wie du eine REST-API mit PHP unter Ubuntu 20.04 für einen fiktiven Online-Shop erstellst. Am Ende kannst du Produktdaten direkt aus der Datenbank abrufen, ohne auf ein klassisches Interface angewiesen zu sein.
Voraussetzungen
- Ein Ubuntu 20.04 System
- Ein Benutzerkonto mit sudo-Rechten
- Ein installierter LAMP-Stack (MySQL oder MariaDB)
Beispieldatenbank einrichten
Verbinde dich per SSH mit deinem Server und starte die MySQL-Konsole:
$ sudo mysql -u root -p
Nachdem du dein Root-Passwort eingegeben hast und die mysql>
-Eingabeaufforderung erscheint, erstelle mit folgendem Befehl die Datenbank store_api
:
mysql> CREATE DATABASE store_api;
Erstelle jetzt einen eigenen MySQL-Nutzer, den deine API später für den Datenbankzugriff verwendet:
mysql> CREATE USER 'api_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'EXAMPLE_PASSWORD';
mysql> GRANT ALL PRIVILEGES ON store_api.* TO 'api_user'@'localhost';
mysql> FLUSH PRIVILEGES;
Falls du MariaDB statt MySQL verwendest, nutze diesen Befehl für die Rechtevergabe:
MariaDB> GRANT ALL PRIVILEGES on store_api.* TO 'api_user'@'localhost' identified by 'EXAMPLE_PASSWORD';
Wechsle danach in die neue Datenbank:
mysql> USE store_api;
Produkte-Tabelle definieren
Erstelle nun eine Tabelle namens products
, in der die Artikel deines Onlineshops gespeichert werden. Damit kannst du später die Produktinformationen per API abrufen – ganz ohne grafische Oberfläche.
mysql> CREATE TABLE products (
product_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
product_name VARCHAR(50),
cost_price DOUBLE,
retail_price DOUBLE
) ENGINE = InnoDB;
Füge jetzt einige Beispielprodukte in die Tabelle ein, um ein realistisches Inventar zu simulieren:
mysql> INSERT INTO products (product_name, cost_price, retail_price) VALUES ('LEATHER JACKET', '89.23', '99.95');
mysql> INSERT INTO products (product_name, cost_price, retail_price) VALUES ('SILVER COAT', '44.00', '60.00');
mysql> INSERT INTO products (product_name, cost_price, retail_price) VALUES ('REXI BELT', '14.49', '18.85');
mysql> INSERT INTO products (product_name, cost_price, retail_price) VALUES ('SUEDE SHOE', '24.00', '36.00');
mysql> INSERT INTO products (product_name, cost_price, retail_price) VALUES ('WOOLEN SWEATER', '14.45', '18.00');
Führe eine SELECT-Abfrage aus, um zu überprüfen, ob die Einträge korrekt gespeichert wurden:
mysql> SELECT
product_id,
product_name,
cost_price,
retail_price
FROM products;
Die Ausgabe sollte wie folgt aussehen:
+------------+----------------+------------+--------------+ | product_id | product_name | cost_price | retail_price | +------------+----------------+------------+--------------+ | 1 | LEATHER JACKET | 89.23 | 99.95 | | 2 | SILVER COAT | 44 | 60 | | 3 | REXI BELT | 14.49 | 18.85 | | 4 | SUEDE SHOE | 24 | 36 | | 5 | WOOLEN SWEATER | 14.45 | 18 | +------------+----------------+------------+--------------+ 5 rows in set (0.00 sec)
Beende die MySQL-Sitzung mit folgendem Befehl:
mysql> QUIT;
Apache ModRewrite zur API-Weiterleitung einrichten
Damit deine API-Endpunkte wie gewünscht funktionieren, musst du das URL-Rewriting in Apache aktivieren. Starte mit dem Laden des Rewrite-Moduls:
$ sudo a2enmod rewrite
Bearbeite nun die zentrale Apache-Konfigurationsdatei:
$ sudo nano /etc/apache2/apache2.conf
Suche in der Datei nach folgendem Abschnitt und passe die Einstellung AllowOverride
wie folgt an:
... <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride None Require all granted </Directory> ...
Ändere AllowOverride None
zu AllowOverride All
, damit Apache deine .htaccess-Regeln berücksichtigt:
... <Directory /var/www/> Options Indexes FollowSymLinks AllowOverride All Require all granted </Directory> ...
Speichere die Änderung und starte Apache neu:
$ sudo systemctl restart apache2
.htaccess-Datei für URL-Umschreibungen erstellen
Erstelle nun einen Basisordner für deine API. In diesem Beispiel beginnt die Versionierung mit „v1“:
$ sudo mkdir -p /var/www/html/api/v1
Lege in diesem Verzeichnis eine neue .htaccess
-Datei an:
$ sudo nano /var/www/html/api/v1/.htaccess
Füge diesen Inhalt in die Datei ein:
RewriteEngine On
RewriteBase /api/v1
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*)$ index.php?request=$1 [QSA,NC,L]
Was bedeuten die .htaccess-Anweisungen?
- RewriteEngine On: Aktiviert die Umschreibefunktion von Apache.
- RewriteBase /api/v1: Legt den Basispfad für die Rewrite-Regeln fest.
- RewriteCond %{REQUEST_FILENAME} !-f: Regel greift nur, wenn die angeforderte Datei nicht existiert.
- RewriteCond %{REQUEST_FILENAME} !-d: Regel greift nur, wenn kein Ordner mit dem Namen vorhanden ist.
- RewriteRule …: Leitet alle Anfragen intern an
index.php
weiter und übergibt den Pfad als Parameter.
Beispiel: Ein Aufruf wie http://localhost/api/v1/products/1
wird automatisch zu http://localhost/api/v1/index.php?request=products/1
umgeschrieben.
Die zentrale index.php-Routingdatei erstellen
Als Nächstes legst du die Datei index.php
an, die alle eingehenden API-Anfragen verarbeitet. Sie übernimmt die Routingfunktion und leitet basierend auf URL und HTTP-Methode zur passenden Verarbeitung weiter.
Erstelle und öffne die Datei mit folgendem Befehl:
$ sudo nano /var/www/html/api/v1/index.php
Füge folgenden PHP-Code ein:
<?php
header("Content-type:application/json");
function load_class($class) {
require_once $class . '.php';
}
spl_autoload_register('load_class');
$http_verb = $_SERVER['REQUEST_METHOD'];
if ($_SERVER['REQUEST_METHOD'] == 'GET') {
foreach($_GET as $key => $value) {
$params[$key] = $value;
}
}
$request = explode('/', $_REQUEST['request']);
$resource = $request[0];
if (isset($request[1])) {
$resource_id = $request[1];
} else {
$resource_id = '';
}
if ($resource == 'products') {
$request = new Products;
}
if ($http_verb == 'GET') {
if (!empty($resource_id)) {
$response = $request->read($resource_id);
} else {
$response = $request->read($resource_id, $params);
}
echo $response;
}
Funktionsweise der index.php-Datei
- header(“Content-type:application/json”): Setzt den Content-Type-Header, damit Clients die Antwort als JSON interpretieren.
- load_class-Funktion: Diese Funktion lädt benötigte PHP-Klassen automatisch, was manuelles
require
vermeidet. - spl_autoload_register: Registriert die Autoload-Funktion für dynamisches Nachladen von Klassendateien.
- GET-Verarbeitung: Bei GET-Anfragen werden die übergebenen Parameter in einem Array
$params
gespeichert. - Request-Auflösung: Die URL wird in Ressourcen und IDs zerlegt.
products
wird beispielsweise als Ressourcentyp erkannt. - Ressourcenzuweisung: Wird „products“ erkannt, wird eine Instanz der Klasse
Products
erstellt. - Ausführung von GET-Requests: Falls eine ID vorhanden ist, wird nur ein Produkt abgerufen. Ohne ID erfolgt eine gefilterte Abfrage über GET-Parameter.
Beispiel: http://localhost/api/v1/products/1
ruft das Produkt mit ID 1 ab. Ein Aufruf wie http://localhost/api/v1/products?type=shoes
ermöglicht eine Filterung über Parameter.
Die Products-Klasse zur Datenverarbeitung erstellen
Für Anfragen an /products
oder /products/1
wird eine Klasse Products
benötigt. Diese übernimmt das Lesen der Datenbankeinträge.
Lege die Datei mit folgendem Befehl an:
$ sudo nano /var/www/html/api/v1/Products.php
Füge nun den folgenden PHP-Code für die Klasse ein:
<?php
class Products
{
public function read($resource_id, $params = '')
{
try {
$db_name = 'store_api';
$db_user = 'api_user';
$db_password = 'EXAMPLE_PASSWORD';
$db_host = 'localhost';
$pdo = new PDO('mysql:host=' . $db_host . '; dbname=' . $db_name, $db_user, $db_password);
$pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
$pdo->setAttribute(PDO::ATTR_EMULATE_PREPARES, false);
$data = [];
$sql = "select * from products";
if (!empty($resource_id)) {
$sql .= " where product_id = :product_id";
$data['product_id'] = $resource_id;
} else {
$filter = '';
if (isset($params['product_name'])) {
$filter .= " and product_name = :product_name";
$data['product_name'] = $params['product_name'];
}
$sql .= " where product_id > 0 $filter";
}
$stmt = $pdo->prepare($sql);
$stmt->execute($data);
$products = [];
while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
$products[] = $row;
}
$response = [];
$response['data'] = $products;
if (!empty($resource_id)) {
$response['data'] = array_shift($response['data']);
}
return json_encode($response, JSON_PRETTY_PRINT);
} catch (PDOException $ex) {
$error = [];
$error['message'] = $ex->getMessage();
return $error;
}
}
}
Aufbau und Funktion der Products-Klasse
- Klassenstruktur: Die
Products
-Klasse wird über dieindex.php
aufgerufen, sobald der Endpunkt/products
angesprochen wird. - read()-Methode: Die zentrale Methode
read
übernimmt das Verarbeiten eingehender GET-Anfragen. Sie akzeptiert optional eine ID und zusätzliche Parameter. - Datenbankverbindung: Der Zugriff erfolgt sicher über PDO mit den zuvor eingerichteten Zugangsdaten.
- Abfragelogik: Gibst du eine bestimmte Produkt-ID an, fragt die API nur dieses eine Produkt ab. Andernfalls kann mit Parametern wie
product_name
gefiltert werden. - Sichere Ausführung: Benannte Parameter im SQL verhindern SQL-Injection. Die Abfrage wird vorbereitet und mit
execute()
ausgeführt. - JSON-Ausgabe: Die Ergebnisse werden als formatierter JSON-String ausgegeben. Bei Einzeltreffern wird statt eines Arrays direkt das Objekt zurückgegeben.
- Fehlerbehandlung: Tritt ein Fehler auf, wird die Fehlermeldung im JSON-Format zurückgeliefert.
Die PHP-API mit curl testen
Wenn alle Dateien korrekt eingerichtet sind, kannst du die API mit curl
testen.
Alle Produkte abrufen
$ curl localhost/api/v1/products
Beispielausgabe:
{ "data": [ { "product_id": 1, "product_name": "LEATHER JACKET", "cost_price": 89.23, "retail_price": 99.95 }, { "product_id": 2, "product_name": "SILVER COAT", "cost_price": 44, "retail_price": 60 }, { "product_id": 3, "product_name": "REXI BELT", "cost_price": 14.49, "retail_price": 18.85 }, { "product_id": 4, "product_name": "SUEDE SHOE", "cost_price": 24, "retail_price": 36 }, { "product_id": 5, "product_name": "WOOLEN SWEATER", "cost_price": 14.45, "retail_price": 18 } ] }
Einzelnes Produkt über ID abrufen
Produkt mit ID 1:
$ curl localhost/api/v1/products/1
{ "data": { "product_id": 1, "product_name": "LEATHER JACKET", "cost_price": 89.23, "retail_price": 99.95 } }
Produkt mit ID 2:
$ curl localhost/api/v1/products/2
{ "data": { "product_id": 2, "product_name": "SILVER COAT", "cost_price": 44, "retail_price": 60 } }
Nach Produktname filtern
Beispiel: Nur den Artikel „SUEDE SHOE“ anzeigen lassen:
$ curl localhost/api/v1/products?product_name=SUEDE%20SHOE
{ "data": [ { "product_id": 4, "product_name": "SUEDE SHOE", "cost_price": 24, "retail_price": 36 } ] }
Fazit
Du hast nun erfolgreich eine REST-API mit PHP und MySQL unter Ubuntu 20.04 umgesetzt. Die API liefert Daten im JSON-Format zurück und unterstützt aktuell GET-Anfragen. Bei Bedarf lässt sich die Funktionalität auf weitere HTTP-Methoden wie POST, PUT oder DELETE erweitern, um vollständige CRUD-Funktionalität zu ermöglichen.