Cover Image

Im folgende Blog-Post möchte ich zeigen, wie leicht man mit PHP einen einfachen GraphQL Server schreiben kann. Wir nutzen dafür eine bekannte PHP Bibliothek und ddev um eine PHP Umgebung im aktuellen PHP 8.1 aufzusetzen. Den gezeigte Code aus dem Blog-Post habe ich bereits auf Github bereitgestellt (Link in der Box am Ende).

Das Projekt legen wir über einfache Shell Befehle an.

mkdir php-graphql
cd php-graphql
ddev config --omit-containers="db,dba" --php-version="8.1"
ddev composer init

Es sollte jetzt ein lokale ddev Projekt über die URL https://php-graphql.ddev.site aufrufbar sein. Da noch keine index.php Datei im Projekt enthalten ist, sollte ein 404 Fehler im Browser erscheinen.

In der interaktiven Shell habe ich dann das Paket webonyx/graphql-php in der aktuellsten Version installiert. Das Paket ist auch so ziemlich die Standard GraphQL Server Implementierung die auch in vielen bekannten PHP Projekten (u.a. Magento, API Platform, Pimcore) verwendet wird.

Die composer.json sieht dann folgendermaßen aus:

{
    "name": "muench-dev/graphql-server-example",
    "description": "A simple graphql server in PHP",
    "type": "project",
    "require": {
        "webonyx/graphql-php": "^14.11"
    },
    "license": "MIT",
    "autoload": {
        "psr-4": {
            "MuenchDev\\GraphqlServerExample\\": "src/"
        }
    },
    "authors": [
        {
            "name": "Christian Münch",
            "email": "christian@muench-worms.de"
        }
    ]
}

Das interaktive Kommando installiert auch gleich die Abhängigkeiten. Falls man das manuell machen will, reicht mit ddev ein einfaches ddev composer install.

Und nun kann es losgehen.

GraphQL Schema

Wir starten damit ein Schema zu definieren. Man kann das GraphQL Schema programmatisch definieren. Ich möchte das aber nicht und habe mich entschieden das Schema über eine Datei zu pflegen. Das Schema kann dort über die Schema Definition Language definiert werden.

Um mir nicht selbst etwas auszudenken, habe ich ein Beispiel auf der php-graphql Seite geborgt und dort erst mal die Mutation entfernt. Wir möchten uim Starten ein Query erstellen. In dem Fall hat es den Namen greetings. Als Eingabe möchten wir ein Objekt mit den Eigenschaften firstName und lastName über die Variable input senden.

Dazu legen wir jetzt die Datei schema.graphql mit dem folgenden Inhalt an.

schema {
  query: Query
}

type Query {
  greetings(input: HelloInput!): String!
}

input HelloInput {
  firstName: String!
  lastName: String
}

Da diese Schemen inzwischen sehr geläufig sind, kann man auch in der IDE oder dem Editor seiner Wahl sehr häufig Plugins installieren die das Arbeiten mit den Schema Dateien einfacher machen. In VS Code wurde mir direkt angeboten eine Extension zu installieren, was auch super funktioniert hatte.

Nun sind wir soweit den Server zu definieren.

Der GraphQL Server

Es gibt verschiedene GraphQL Server Implementierungen in der PHP Welt. Das graphql-php Paket beinhaltet bereits einen Server über die Klasse GraphQL\Server\StandardServer.

// @link https://webonyx.github.io/graphql-php/executing-queries/#using-server
$server = new StandardServer($config);
$server->handleRequest();

Die $config kann entweder ein PHP Array sein, oder als Instanz von GraphQL\Server\ServerConfig definiert werden. Die Objekt Variante ist ein Builder der bequem über Setter alle Möglichkeiten der Konfiguration zugänglich macht.

Um das Schema zu registrieren, müssen wir erst die Datei einlesen.

$contents = file_get_contents('schema.graphql');
$schema = BuildSchema::build($contents);

Danach kann das Schema dann registriert werden.

$config = ServerConfig::create()
    ->setSchema($schema)
    ->setDebugFlag($debug)
;

Ich habe noch eine Debug-Option gesetzt, damit wir mehr Infos bei der Ausgabe von Fehlern für die Entwicklung erhalten.

Das Schema im GraphQL Client

Es gibt unzählige GraphQL Clients. Zum Testen des Schema sollte jeder Client ausreichen sein. Ich habe hier den Client Altair verwendet. Dieser lässt auch direkt als Extension in den Browser integrieren.

Im Client gibt man nun einfach die URL zum Server ein und sollte dann auch das Schema mit unserem Query sehen.

Schema im Altair GraphQL Client

  1. Doku öffnen
  2. Schema neu laden
  3. Query “greeting”

Das Query kann jetzt im Client an den Server geschickt werden. Dazu gibt man auf der linken Seite im Client das folgen Query ein:

query {
  greetings(input: {firstName: "Peter", lastName: "Bimbelhuber"})
}

Wenn das Query ausgeführt wird, sollte aber ein Fehler erscheinen, da der Server noch keine Business Logik hat um mit dem Query umzugehen.

Man sollte dann folgende Ausgabe bekommen:

{
  "errors": [
    {
      "debugMessage": "Cannot return null for non-nullable field \"Query.greetings\".",
      "message": "Internal server error",
      "extensions": {
        "category": "internal"
      },
      "locations": [
        {
          "line": 2,
          "column": 3
        }
      ],
      "path": [
        "greetings"
      ]
    }
  ]
}

Query auswerten

Um ein Query auszuwerten, brauchen wir einen sogenannten Resolver. Unsere Greeting Logik ist sehr einfach. Es soll nur der Vor- und Nachname aus dem Eingabeparameter genommen werden, und eine persönliche Grußnachricht als String zurückgesendet werden.

Ein so einfacher Resolver sieht dann z.B. wie folgt aus:

$rootResolver = [
    'greetings' => function($root, $args, $context, $info) {
        return trim(
            sprintf(
            'Hello %s %s',
            $args['input']['firstName'],
            $args['input']['lastName'] ?? ''
            )
        );
    }
];

Den Resolver registrieren wir über die GraphQL Server Config.

$config = ServerConfig::create()
    ->setSchema($schema)
    ->setRootValue($rootResolver)
    ->setDebugFlag($debug)
;

Das war es. Nun sollte der Server uns ordentlich begrüßen.

Unser Query müsste nun als Antwort dies produzieren:

{
  "data": {
    "greetings": "Hello Peter Bimbelhuber"
  }
}

Das war nun ein einfacher GraphQL Server mit PHP der ein einfaches Query behandelt. Mit GraphQL ist aber noch viel mehr möglich. So kann man auch mehr als ein Query absetzen. Queries schachteln. Daten über Mutations ändern.