REST in schlank

Schlanke REST-Schnittstellen mit dem Slim-Framework
Kommentare

Nicht selten schlummern haufenweise (Kunden-)Daten in lokalen Datenbanken innerhalb eines Firmennetzwerks. Mit zunehmender Mobilität übersteigen die Anforderungen für eine Verfügbarkeit dieser internen Daten jedoch die Kapazitätem des Firmennetzwerks. Mitarbeiter und Kunden möchten z. B. auch via Smartphone mobil Zugriff auf diese Daten erhalten. Anstatt die komplette Datenbank nach außen zu öffnen, bietet sich eine schlanke REST-Schnittstelle an – die perfekte Anforderung für das Slim-Framework.

Slim ist ein von Josh Lockhart geschriebenes PHP-„Microframework“. Die einzige Anforderung an den Server ist PHP 5.3+. Die MIT-Lizenz macht das Framework für den Einsatz im Unternehmen interessant. Die Installation erfolgt manuell oder über Composer, und nach der Installation ist das obligatorische „Hallo Welt“ schnell geschrieben (Listing 1).

<?php
  require 'libs/Slim/Slim.php';
  \Slim\Slim::registerAutoloader();

  $app = new \Slim\Slim();
  $app->get('/hallo/:name', function ($name) {
    echo "Hallo $name";
  });
  $app->run();

REST & Slim: Her mit den Daten!

Das Kernstück einer REST-Schnittstelle sind ganz klar die Daten. Grundlage für die Anwendung ist eine bestehende Datenbank mit Kundenkontakten. Slim macht es dem Entwickler leicht und implementiert Callback-Funktionen für HTTP-Request-Methoden wie GET, POST, PUT usw. Zudem werden URIs entsprechend gemappt, sodass einem ansprechenden Schnittstellendesign nichts im Wege steht.

$app->get('/contacts', function () {
  echo json_encode(listContacts());
});

Diese erste Funktion listet die vorhandenen Kontakte aus der Datenbank. Damit die Anwendung eine nachvollziehbare und aufgeräumte Architektur erhält, werden die Funktionen entsprechend der Datenbanktabellen auf einzelne PHP-Dateien verteilt. Die Rückgabe der Daten erfolgt im JSON-Format. Jede Rückgabe erhält einen Status (ok/error) und eine Nachricht, die von der jeweiligen Clientanwendung ausgewertet werden kann.

function listContacts(){
  $contacts = ORM::for_table('contacts')->order_by_asc('surname')
                                        ->find_many();
  if (!empty($contacts)) 
  {
    $contacts_arr = Array();
    $result_arr = array('status' => 'ok','msg' =>'');
    foreach ($contacts as $contact) :
      array_push($contacts_arr,$contact->as_array());
    endforeach;
    $result_arr['contacts'] = $contacts_arr; 
  } else {
    $result_arr = array('status' => 'error', 'msg' => 'no contacts found');
  }
  return $result_arr;
}

Go for PHP Developers

mit Terrence Ryan (google)

Everything you need to know about PHP 7.2

mit Sebastian Bergmann (thePHP.cc)

Dem Leser ist in Listing 2 vielleicht die erste Zeile der Funktion aufgefallen. Die Datenbankanbindung erfolgt hier mithilfe von Idiorm – einem kleinen objektrelationalen Mapper. Für viele Clientanwendungen ist es sinnvoll, einen HTTP-Statuscode anstatt einer Fehlermeldung in einem JSON-String zurückzugeben. Dazu muss zuerst eine Anwendungsinstanz in die Funktion geholt werden. Dann kann der Statuscode bequem gesetzt werden:

$app = \Slim\Slim::getInstance();
$app->response->setStatus(404);

Die Funktionen zum Erstellen, Löschen und Archivieren eines Kontakts folgen wie in Listing 3 zu sehen der gleichen Vorgehensweise. Slim unterstützt neben der einfachen Übergabe von Parametern auch Wildcard- und optionale Parameter.

Da nicht jeder Browser bzw. jeder Client alle HTTP-Methoden beherrscht, können diese durch ein verstecktes Feld per HTTP POST überschrieben werden. Für ein HTTP DELETE würde das wie folgt aussehen:

<input type="hidden" name="_METHOD" value="DELETE"/>

 

// Create contact
$app->post('/contacts', function () {
  $post = $app->request()->post();
  echo json_encode(createContact($post));
});

// Update contact
$app->post('/contacts/:id', function ($id) {
  $post = $app->request()->post();
  echo json_encode(updateContact($post,$id));
});

// Delete contact
$app->delete('/contacts/:id', function ($id) {
  echo json_encode(archiveContact($id));
});

// Implementierung der Funktionen
// Create contact
function createContact($post){
  try {
    $contact = ORM::for_table('contacts')->create();
    $contact->surname      = $post['surname'];
    $contact->firstname    = $post['firstname'];
    $contact->phone1       = $post['phone1'];
    ...
    $contact->definably4   = $post['definably4'];
    $contact->created_at   = gmdate('Y-m-d H:i:s');
    $contact->updated_at   = gmdate('Y-m-d H:i:s');
    $contact->save();
    $reval = array('status' => 'ok', 'msg' => '', 'id' => $contact->id());
  } catch (PDOException $e) {
    $reval = array('status' => 'error', 'msg' => $e->getMessage());
  }
  return $reval;
}
// Update contact
function updateContact($post,$id){
  $contact = ORM::for_table('contacts')-> where('id', $id)->find_one();
  if ($contact === false) { 
    $app = \Slim\Slim::getInstance();
    $app->response->setStatus(404);
    return array('status' => 'error', 'msg' => 'contact not found')
  } else {
    $contact->svc_organisation_id  = $user['organisation_id'];
    $contact->surname              = $post['surname'];
    $contact->firstname            = $post['firstname'];
    $contact->phone1               = $post['phone1'];
    ...
    $contact->updated_at           = gmdate('Y-m-d H:i:s');
    $contact->save();
    $reval = array('status' => 'ok', 'msg' => '');
  }
  return $reval;
}
// Delete contact
function deleteContact($id){
  $contact = ORM::for_table('contacts')-> find_one();
  if ($contact === false) { 
    $app = \Slim\Slim::getInstance();
    $app->response->setStatus(404);
    return array('status' => 'error', 'msg' => 'contact not found');
  } else {
    $contact->delete();
    $reval = array('status' => 'ok', 'msg' => '');
  }
  return $reval;
}

Ein Kontakt mit mehreren Adressen

Vorhandene Adressen werden über einen Wildcard-Parameter aus der Datenbank selektiert. Eine Abfrage contacts/45/adresses soll also alle vorhandenen Adressen zu dem Kontakt mit der ID 45 ausgeben. Die zugehörige Funktion findet sich in Listing 4.

$app->get('/contacts/:contact+', function ($contact) {
  if (count($contact) > 1){
    if ($contact[1] == 'adresses')
      echo json_encode(readAdresses($contact[0]));
    } else {
      echo json_encode(readContact($contact[0]));
    }
});

Laden, was benötigt wird

Nach der Größe der Datenbank richtet sich in der Regel die Größe des Skripts, welches bei jedem Request durchlaufen wird. Dies kostet Zeit – von daher ist es sinnvoll, nur benötigte Teile in das Skript zu laden; und das geschieht mit einem Hook. Ein Hook ist eine Funktion, die an einem bestimmten Zeitpunkt den Anwendungsfluss unterbricht und ausgeführt wird. In unserem Fall direkt vor dem eigentlichen Routing (Listing 5). Anhand des Pfads wird dann das zu ladende Skript ermittelt.

$app->hook("slim.before.router",function() use ($app){
  $path = $app->request()->getPathInfo();
  if (strpos($path, "/contacts") !== false) {
    require 'app/contacts.php';
  }
  if (strpos($path, "/adresses") !== false) {
    require 'app/adresses.php';
  }
});

Was ist mit der Sicherheit?

Die Kommunikation mit einer REST-Schnittstelle muss natürlich über SSL verschlüsselt werden. Eine Zugriffsberechtigung auf Teile der Anwendung ist auch nicht weiter problematisch. Eine denkbare Lösung könnte wie folgt aussehen:

  1. Der Client authentifiziert sich bei der ersten Nutzung mit Benutzername und Passwort am Server.
  2. Bei korrekten Anmeldedaten sendet der Server eine Benutzeridentifikation (tuid) mit begrenzter Gültigkeit (z. B. zwei Wochen) zurück, welche vom Client gespeichert wird.
  3. Bei allen weiteren Anfragen sendet der Client für die Identifikation dann lediglich die Benutzeridentifikation (tuid) als zusätzlichen Eintrag im Header mit.
  4. Wenn die Benutzeridentifikation (tuid) nicht mehr gültig ist, muss der Client sich erneut mit Benutzernamen und Passwort am Server anmelden.

Jeder Zugriff auf die Schnittstelle muss dann zuerst auf eine vorhandene Berechtigung geprüft werden (Listing 6).

function authenticate() {
  $app = \Slim\Slim::getInstance();
  $tuid = $app->request->headers->get('tuid');
  $user = ORM::for_table('users')->where('ident', $tuid)->find_one();
  if ($user === false) {
    $app->response->setStatus(401);
    $json = array('status' => 'error','msg' => 'could not authenticate user');
    echo json_encode($json);
  } else {
    return $user->as_array();
  }
}

Das Slim-Framework eignet sich wie beschrieben perfekt für die Implementierung einer REST-Schnittstelle für bestehende Inhouse-Datenbanken. Der Artikel hat hier nur an der Oberfläche der Möglichkeiten von Slim gekratzt. Einen guten Überblick über alle Funktionen kann man sich mithilfe der ausgezeichneten Dokumentation verschaffen.

PHP Magazin

Entwickler MagazinDieser Artikel ist im PHP Magazin erschienen. Das PHP Magazin deckt ein breites Spektrum an Themen ab, die für die erfolgreiche Webentwicklung unerlässlich sind.

Natürlich können Sie das PHP Magazin über den entwickler.kiosk auch digital im Browser oder auf Ihren Android- und iOS-Devices lesen. In unserem Shop ist das Entwickler Magazin ferner im Abonnement oder als Einzelheft erhältlich.

Aufmacherbild: Spider rooftop framework von Shutterstock / Urheberrecht: Dennis Willam

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -