Du brauchst vielleicht kein Framework für deine PHP Web API
Wenn du schon mal mit ASP.NET Web API, bzw. MVC gearbeitet hast und du dir ein ähnliches System in PHP wünschst, bist du hier komplett richtig!
Dabei verzichten wir auf komplexe Frameworks und halten alles schön leicht und simpel. Ideal für einfache Web APIs ohne Abhängigkeiten.
Inhalt
Los geht's!
Erstelle in deinem Lieblings-Codeeditor einen neuen Projektordner (ich nenne meinen Ordner "php-web-api"). Als Codeeditor bevorzuge ich übrigens Visual Studio Code, aber du kannst natürlich nehmen, womit du am liebsten arbeitest.
Der Startpunkt jeder PHP-Anwendung ist die Datei index.php, also legen wir diese als erstes an.
Die Ordner-Struktur sieht jetzt so aus:
- 📄 index.php
Okay, index.php lassen wir allerdings erst noch leer, denn es geht erst einmal woanders weiter.
Der Basis-Controller
Wir definieren einmalig die Klasse Controller
. Dafür legen wir zunächst den Ordner Classes an und erstellen die Datei Controller.php.
Unser Projekt-Ordner:
- 📁 Classes
- 📄 Controller.php
- 📄 index.php
Yay, wir kommen zum Code-Part! Hier ist schon mal Controller
mit einer Eigenschaft:
<?php
class Controller {
protected $Action;
}
Controller->Action
wird den Namen der Methode enthalten, die ausgeführt werden soll.
Fahren wir fort, indem wir den Konstruktor definieren:
<?php
class Controller {
protected $Action;
function \_\_construct() {
$this->Action = filter\_input(INPUT\_GET, "action");
if (!$this->Action) {
$this->Action = "Index";
}
// Let execute the method the user has set in the URL parameter
$this->runActionMethod($this->Action);
}
}
Welche Action (Controller-Methode) ausgeführt werden soll, wird über den URL-Parameter action (z.B. ?action=MyAction
) festgelegt.
filter_input(INPUT_GET, "action")
ist übrigens ähnlich wie $_GET["action"]
. Sollte der Parameter nicht gesetzt sein, soll als Fallback immer die Index-Action ausgeführt werden.
$this->runActionMethod($this->Action)
lässt die Action-Methode letztendlich ausführen. Controller->runActionMethod
definieren wir jetzt:
<?php
class Controller {
protected $Action;
function \_\_construct() {
// ...
}
private function runActionMethod($actionMethod) {
if (method\_exists($this, $actionMethod)) {
$reflection = new \\ReflectionMethod($this, $actionMethod);
if ($reflection->isPublic()) {
// Bypasses parameters from URL (GET) to the action method
$paramaters = \[\];
foreach($reflection->getParameters() as $arg) {
array\_push($paramaters, filter\_input(INPUT\_GET, $arg->name));
}
$response = call\_user\_func\_array(\[$this, $actionMethod\], $paramaters);
// Will print the response as JSON to client
header("Content-Type: application/json");
echo json\_encode($response);
}
else {
throw new \\RuntimeException("This Action Method is not public.");
}
}
else {
throw new \\RuntimeException("This Action Method does not exist.");
}
}
}
Zunächst wird geprüft, ob die Action-Methode existiert. Außerdem soll die Methode nur ausgeführt werden können, wenn sie public
ist. Um das zu prüfen, verwende ich [ReflectionMethod](https://www.php.net/manual/de/class.reflectionmethod.php)
. In den Zeilen 14-18 werden weitere Parameter aus der URL ausgelesen. Mit $response = call_user_func_array([$this, $actionMethod], $paramaters);
wird die Action-Methode ausgeführt und auch die Parameter werden dabei übergeben. Der Rückgabewert der Methode wird anschließend im JSON-Format ausgegeben.
Unser erster Controller
Für die Controller legen wir extra einen Ordner im Root-Ordner an:
- 📁 Classes
- 📄 Controller.php
- 📁 Controllers
- 📄 HelloWorldController.php
- 📄 index.php
Wie es sich für ein Tutorial gehört, nennen wir den Controller natürlich "HelloWorld". Alle Controller enden immer mit dem Zusatz "Controller".
In Controllers/HelloWorldController.php kommt jetzt das hier:
<?php
class HelloWorldController extends Controller {
public function Index() {
return "Hello";
}
public function SayHi($id) {
return "Hi, $id!";
}
}
Jede öffentliche Methode ist ein anzusprechender Endpunkt der Web API. Hier also Index
und SayHi
. SayHi
kann mit $id
außerdem noch ein Parameter mitgegeben werden.
Zurück zur index.php
Alle wichtigen Komponenten für die Web API haben wir angelegt: Die Basisklasse für alle Controller (Classes/Controller.php) und mit Controllers/HelloWorldController.php einen Beispiel-Controller. Nach dem gleichen Schema können im Controllers-Ordner noch beliebig viele weitere Controller hinzugefügt werden.
Jetzt gilt es diese Komponenten in der index.php zusammenzufassen, denn die ist ja noch leer. Füge folgenden Code ein:
<?php
require \_\_DIR\_\_ . "/Classes/Controller.php";
// Autoload Controller classes
$controllerFiles = scandir(\_\_DIR\_\_ . "/Controllers");
foreach ($controllerFiles as $currFile) {
if ($currFile != "Controller.php" && strpos($currFile, "Controller") !== false) {
require \_\_DIR\_\_ . "/Controllers/" . $currFile;
}
}
$controllerParameter = filter\_input(INPUT\_GET, "controller");
if ($controllerParameter && $controllerParameter != "php") {
$controllerClassName = $controllerParameter . "Controller";
if (class\_exists($controllerClassName, false)) {
new $controllerClassName(true);
}
else {
throw new \\RuntimeException("This Controller does not exist");
}
exit;
}
Zunächst importieren wir die Basis-Klasse (Classes/Controller.php). In den Zeilen 5-11 werden alle Controller-Klassen importiert, indem alle PHP-Dateien eingebunden werden, die sich im Controllers-Ordner befinden und auf "Controller" enden.
In den weiteren Zeilen wird der URL-Parameter controller abgerufen und basierend darauf der entsprechende Controller geladen und ausgeführt. Sollte der angefragte Controller nicht existieren, wird eine Fehlermeldung ausgegeben.
So. Also, im Grunde ist unsere Web API jetzt fertig. Um sie jetzt zu nutzen, können die Controller-Actions auf diese Weise angesprochen werden:
https://my-api.com/?controller=HelloWorld&action=SayHi&id=Tom
Schönere URLs
Okay, kann man so lassen. Sollte man aber vielleicht nicht. Viel schöner wäre doch so eine URL:
https://my-api.com/HelloWorld/SayHi/Tom
Dafür fügen wir dem Root-Ordner eine .htaccess-Datei hinzu:
- 📁 Classes
- 📄 Controller.php
- 📁 Controllers
- 📄 HelloWorldController.php
- 📄 .htaccess
- 📄 index.php
Und das fügst du ein:
RewriteEngine On
# For some webhostings you need to uncomment the next line
#RewriteBase /
# Server Routing Rule
# The routing structure is pretty flexible. Allowed is:
# /MyControllerName/MethodName/Value
RewriteRule (\[-a-zA-Z0-9\]+)/(\[-a-zA-Z0-9\]+)/(\[-a-zA-Z0-9\]+)/?$ index.php?controller=$1&action=$2&id=$3 \[QSA\]
# /MyControllerName/MethodName
RewriteRule (\[-a-zA-Z0-9\]+)/(\[-a-zA-Z0-9\]+)/?$ index.php?controller=$1&action=$2 \[QSA\]
# /MyControllerName
# In this case, the Index() method of the controller will be used. Of course this will only work when it's set.
RewriteRule (\[-a-zA-Z0-9\]+)/?$ index.php?controller=$1 \[QSA\]
Fertig!
Den gesamten Projektordner findest du auch nochmal auf GitHub: https://github.com/lgkonline/php-web-api
Nutze die Repo gerne als Vorlage, wenn du ein neues Projekt erstellen willst.
Wenn du Fragen hast oder Feedback hinterlassen willst, schreibe mir gerne auf Twitter über @lgkonline (EN) oder @3lgeekay (DE).