``` PHP Anfänger-Guide
Anfänger-Guide · 2,5h · Stand 2026

PHP
in 10 Kapiteln.

Von der Installation bis zur ersten Composer-Anwendung – modern und idiomatisch.

Was du danach kannst PHP-Code mit Type-Hints schreiben · Arrays und Klassen modern nutzen · Funktionen sauber strukturieren · Fehler richtig behandeln · Composer-Pakete installieren und in eigenen Projekten verwenden.
PHP 8.4

Inhalt

Teil 1 · Grundlagen
1

PHP installieren und starten

Setup mit Docker, DDEV oder lokal, erstes Script

15 Min
2

Variablen und Typen

$-Variablen, strict_types, Typumwandlung

15 Min
3

Strings und Formatierung

Interpolation, Heredoc, String-Funktionen

15 Min
Teil 2 · Strukturen
4

Arrays: Liste und Map

Indiziert, assoziativ, gemischt

15 Min
5

Control Flow

if, match, foreach, while

15 Min
6

Funktionen mit Type-Hints

Parameter, Rückgabewerte, Named Arguments

15 Min
Teil 3 · Objekte und echte Anwendungen
7

Klassen und Objekte

Promoted Constructor, readonly, Methoden

15 Min
8

Namespaces und Autoloading

PSR-4, use-Statements, Datei-Organisation

15 Min
9

Fehlerbehandlung

Exceptions, try/catch, eigene Fehler

15 Min
10

Composer und erste Anwendung

Pakete installieren, kleines CLI-Tool bauen

15 Min
01

PHP installieren und starten

Setup mit Docker, DDEV oder lokal
Frage zum Einstieg Du willst PHP-Code schreiben und ausführen. Aber wie kommt PHP auf deinen Rechner? Klassische XAMPP-Installation, modernes Docker, oder einfach php -S für schnelle Tests? Die richtige Wahl spart dir später Stunden.

Drei Wege, PHP zu starten

Für lokale Entwicklung gibt es drei sinnvolle Optionen, jede mit ihrem Anwendungsfall:

MethodeWann
Lokal (brew, apt)Kleine Scripts, schneller Test
DDEVMehrere Projekte mit DB
Docker direktCustom-Setup, CI/CD

Für den Einstieg reicht eine lokale Installation. Auf macOS mit Homebrew: brew install php. Auf Ubuntu: apt install php8.4-cli. Auf Windows: am einfachsten WSL2 mit Ubuntu darin.

Erstes Script

Lege eine Datei hello.php an mit folgendem Inhalt:

<?php
declare(strict_types=1);

echo "Hallo Welt!\n";
echo "PHP-Version: " . PHP_VERSION . "\n";

Im Terminal: php hello.php. Du siehst die Ausgabe. Glückwunsch – das ist alles, was PHP zum Start braucht.

Die Zeile declare(strict_types=1) gehört in jede moderne PHP-Datei. Sie aktiviert strenge Typprüfung und verhindert, dass PHP heimlich Strings in Zahlen umwandelt. Verlässlicher Code.

Eingebauter Web-Server

Für Web-Entwicklung braucht PHP normalerweise einen Webserver (Apache, nginx). Für lokales Testen gibt es einen eingebauten Server:

# Im Verzeichnis mit deinen .php-Dateien
php -S localhost:8000

Öffne http://localhost:8000/hello.php im Browser. Der Server ist nicht für Production gedacht, aber für lokale Entwicklung perfekt.

DDEV für echte Projekte Sobald du eine Datenbank brauchst, mehrere Projekte gleichzeitig, oder ein konkretes PHP-Framework wie Shopware oder Laravel, lohnt sich DDEV – wraps Docker mit sinnvollen Defaults. ddev config in deinem Projektordner reicht meist.
Recall
  1. Wie führst du eine PHP-Datei im Terminal aus?
  2. Wozu ist declare(strict_types=1) gut?
  3. Wann nutzt du php -S, wann DDEV?
02

Variablen und Typen

$-Variablen, strict_types, Typumwandlung
Frage zum Einstieg PHP-Variablen beginnen mit einem $. Warum eigentlich? Und wie kann eine Variable mal eine Zahl, mal ein String sein, ohne dass es explodiert? Die Antwort zeigt, wie PHPs Typsystem funktioniert.

Variablen mit $

Jede Variable in PHP beginnt mit dem Dollar-Zeichen. Das macht sie im Code sofort erkennbar – du musst nie raten, ob ein Name eine Variable oder eine Funktion ist:

$name = 'Marek';
$age = 34;
$height = 1.82;
$isActive = true;
$email = null;

PHP ist dynamisch typisiert: derselbe Variable darf erst eine Zahl, dann ein String zugewiesen werden. Das ist flexibel, kann aber zu Bugs führen – deshalb sind Type-Hints in Funktionen so wichtig (Kapitel 6).

Die wichtigsten Typen

TypBeispielWofür
int42Ganze Zahlen
float3.14Kommazahlen
string'Hi'Text
booltrueWahrheitswert
array[1, 2]Liste oder Map
nullnullKein Wert

Den Typ einer Variable findest du mit gettype($x) oder mit Funktionen wie is_int($x), is_string($x).

Typumwandlung

PHP wandelt Typen oft automatisch um – das ist bequem, aber tückisch. Mit explizitem Cast bist du auf der sicheren Seite:

$input = '42';                // string
$number = (int) $input;        // 42 als int
$float = (float) '3.14';        // 3.14
$text = (string) 42;            // "42"

// Boolean-Cast: 0, "", "0", null, [] sind false
if ($value) {              // implizit zu bool
  echo 'truthy';
}
!
Vergleich mit == ist tückisch '0' == false ist true. 'abc' == 0 war in alten PHP-Versionen auch true. Nutze immer === für strikten Vergleich, der auch den Typ prüft.
Recall
  1. Wie erkennst du in PHP-Code, ob etwas eine Variable ist?
  2. Was ist der Unterschied zwischen == und ===?
  3. Welche Werte werden bei Boolean-Cast zu false?
03

Strings und Formatierung

Interpolation, Heredoc, String-Funktionen
Frage zum Einstieg In PHP gibt es einfache und doppelte Anführungszeichen für Strings. Beide funktionieren – aber sie verhalten sich unterschiedlich. Warum gibt es überhaupt zwei, und welche solltest du wann nutzen?

Strings: '' vs ""

Beide sind Strings, aber doppelte Anführungszeichen interpolieren Variablen, einfache nicht:

$name = 'Marek';

echo "Hallo $name";       // Hallo Marek
echo 'Hallo $name';       // Hallo $name (wörtlich)

// Komplexere Ausdrücke: geschweifte Klammern
$user = ['name' => 'Marek'];
echo "Hallo {$user['name']}";

// Verkettung mit .
echo 'Hallo ' . $name . '!';

Faustregel: Doppelte Anführungszeichen wenn du Variablen einsetzen willst, einfache sonst. Einfache sind minimal schneller, aber das ist heute praktisch egal.

Heredoc und Nowdoc für Mehrzeiler

Für längeren Text mit Variablen oder ohne gibt es Heredoc und Nowdoc:

$name = 'Marek';

$mail = <<<TEXT
Hallo $name,

vielen Dank für deine Bestellung.

Grüße
TEXT;

// Nowdoc (kein Interpolation, wörtlich)
$tpl = <<<'TPL'
Verwende {{ name }} für den Namen.
TPL;

Wichtige String-Funktionen

PHP hat eine riesige Auswahl an String-Funktionen. Diese kennst du nach einer Woche auswendig:

FunktionEffekt
strlen($s)Länge des Strings
strtoupper / strtolowerGroß-/Kleinschreibung
trim($s)Whitespace vorne/hinten entfernen
str_replace($a, $b, $s)a durch b ersetzen
str_contains($s, $needle)Prüft, ob enthalten
explode(',', $s)String zu Array
implode(',', $arr)Array zu String
sprintf('%05d', 42)Formatiert "00042"
Methoden-Verkettung mit Pipes nicht möglich Anders als bei Objektmethoden gibt es bei String-Funktionen keine Verkettung. trim(strtolower($s)) ist die Schreibweise – von innen nach außen lesen.
Recall
  1. Wann nutzt du "", wann ''?
  2. Was ist der Unterschied zwischen Heredoc und Nowdoc?
  3. Wie prüfst du, ob ein String einen anderen enthält?
04

Arrays: Liste und Map

Indiziert, assoziativ, gemischt
Frage zum Einstieg In den meisten Sprachen sind Listen und Dictionaries zwei verschiedene Datenstrukturen. In PHP ist beides ein array – das macht vieles flexibel, aber auch leicht verwirrend. Wie funktioniert das?

Indizierte Arrays (Listen)

Eine Liste ohne explizite Keys – die Indizes werden automatisch 0, 1, 2, ...:

$fruits = ['Apfel', 'Birne', 'Kirsche'];

$fruits[0];                // 'Apfel'
$fruits[2];                // 'Kirsche'
count($fruits);            // 3

// Hinten anhängen
$fruits[] = 'Banane';       // jetzt 4 Elemente
array_push($fruits, 'Mango'); // alternative Schreibweise

Assoziative Arrays (Maps)

Mit String-Keys wird das Array zu einer Map – die häufigste Form in PHP-Code, weil viele APIs (JSON, Datenbank-Zeilen) so aussehen:

$user = [
  'name' => 'Marek',
  'age' => 34,
  'roles' => ['admin', 'editor'],
];

$user['name'];            // 'Marek'
$user['email'] = 'm@e.de';  // neuer Key

// Sicher: gibt null wenn nicht da, kein Fehler
$phone = $user['phone'] ?? 'unbekannt';

Der Null-Coalescing-Operator ?? ist eine der nützlichsten PHP-Features. Er gibt den linken Wert zurück, wenn er existiert und nicht null ist, sonst den rechten.

Über Arrays iterieren

foreach ist das Werkzeug der Wahl. Es funktioniert für beide Array-Typen:

// Nur Werte
foreach ($fruits as $fruit) {
  echo $fruit;
}

// Key und Wert
foreach ($user as $key => $value) {
  echo "$key: $value\n";
}

Array-Funktionen

FunktionWofür
array_map($fn, $arr)Jedes Element transformieren
array_filter($arr, $fn)Elemente mit Bedingung behalten
array_reduce($arr, $fn, $init)Aggregieren zu einem Wert
array_keys / array_valuesKeys oder Werte extrahieren
in_array($x, $arr)Enthält Wert?
sort($arr)Sortiert in-place
Recall
  1. Was ist der Unterschied zwischen indizierten und assoziativen Arrays?
  2. Was macht ?? in PHP?
  3. Wie iterierst du über ein assoziatives Array mit Keys und Werten?
05

Control Flow

if, match, foreach, while
Frage zum Einstieg Wie sagst du PHP "tu das nur, wenn X" oder "wiederhole das, bis Y"? Die klassischen Konstrukte kennst du aus jeder Sprache – aber PHP 8 hat mit match ein modernes Werkzeug ergänzt, das vieles eleganter macht.

if, elseif, else

if ($score >= 90) {
  $grade = 'A';
} elseif ($score >= 75) {
  $grade = 'B';
} else {
  $grade = 'C oder schlechter';
}

// Ternary für einfache Fälle
$status = $age >= 18 ? 'erwachsen' : 'minderjährig';

match: das moderne switch

Seit PHP 8 gibt es match – ähnlich wie switch, aber strikt im Vergleich, ein Ausdruck (also mit Rückgabewert), kein fallthrough:

$status = match($code) {
  200, 201, 204 => 'success',
  301, 302       => 'redirect',
  404            => 'not found',
  500            => 'server error',
  default        => 'unknown',
};

Beachte die Kommas zwischen Werten – sie bedeuten "oder". Und das default ist Pflicht (sonst Exception), das schützt vor vergessenen Fällen.

Schleifen

// for: bekannte Anzahl
for ($i = 0; $i < 10; $i++) {
  echo $i;
}

// foreach: über Arrays
foreach ($items as $item) {
  echo $item;
}

// while: unbestimmte Anzahl
while ($line = fgets($file)) {
  processLine($line);
}

// break und continue
foreach ($items as $item) {
  if ($item->skip) continue;
  if ($item->stop) break;
  process($item);
}
match statt switch In neuem Code: nimm match. Strikter Vergleich (===), kein fallthrough-Fehler, Ergebnis als Wert. switch nur noch in Legacy-Code oder wenn du komplexe Blöcke pro Fall brauchst.
Recall
  1. Wann nutzt du foreach, wann for?
  2. Was sind die wichtigsten Vorteile von match gegenüber switch?
  3. Was ist der Unterschied zwischen break und continue?
06

Funktionen mit Type-Hints

Parameter, Rückgabewerte, Named Arguments
Frage zum Einstieg Eine Funktion ohne Type-Hints ist wie ein Brief ohne Adresse – sie kommt vielleicht an, aber niemand weiß, was sie erwartet. Modernes PHP-Code hat Types überall. Wie schreibst du sie, und was kannst du damit?

Funktion deklarieren

function add(int $a, int $b): int {
  return $a + $b;
}

function greet(string $name, string $greeting = 'Hallo'): string {
  return "$greeting, $name!";
}

echo add(3, 4);              // 7
echo greet('Marek');          // Hallo, Marek!

Die Syntax: function name(typ $param): rückgabetyp. Beides ist optional, aber heute Standard.

Named Arguments

Seit PHP 8 kannst du Argumente beim Aufruf mit Namen übergeben. Praktisch bei Funktionen mit vielen Defaults:

function createUser(
  string $name,
  int $age = 0,
  bool $isAdmin = false,
  ?string $email = null,
): User { /* ... */ }

// Positional (alt)
createUser('Marek', 34, false, 'm@e.de');

// Named (modern, lesbarer)
createUser(
  name: 'Marek',
  email: 'm@e.de',
  isAdmin: true,
);

Nullable und Union Types

Mit ? wird ein Typ nullable, mit | sind mehrere Typen erlaubt:

// Darf string oder null sein
function find(int $id): ?User {
  return $id > 0 ? new User($id) : null;
}

// Union: int ODER string
function parse(int|string $input): int {
  return (int) $input;
}

// void: gibt nichts zurück
function log(string $msg): void {
  file_put_contents('app.log', $msg);
}
!
Closures vs. globale Variablen Funktionen sehen keine Variablen aus dem umgebenden Code. Du musst sie explizit übergeben. Closures (anonyme Funktionen) können mit use ($var) Variablen importieren – aber Globals mit global $x sind tabu.
Recall
  1. Wie deklarierst du eine Funktion, die string zurückgibt?
  2. Was ist der Vorteil von Named Arguments?
  3. Was bedeutet ?string als Typ?
07

Klassen und Objekte

Promoted Constructor, readonly, Methoden
Frage zum Einstieg In PHP 5 brauchten Klassen viel Boilerplate: Properties, Konstruktor, Getter, Setter – seitenweise Code für simple Daten. PHP 8 hat das drastisch reduziert. Was sieht eine moderne Klasse heute aus?

Klasse mit Promoted Constructor

Das wichtigste PHP-8-Feature für Klassen: Konstruktor-Parameter werden automatisch zu Properties, wenn du sie mit einem Sichtbarkeits-Modifier markierst:

class User {
  public function __construct(
    public readonly string $name,
    public readonly int $age,
    private ?string $email = null,
  ) {}

  public function isAdult(): bool {
    return $this->age >= 18;
  }

  public function getEmail(): ?string {
    return $this->email;
  }
}

$user = new User(name: 'Marek', age: 34);
echo $user->name;              // 'Marek'
echo $user->isAdult() ? 'ja' : 'nein';

readonly verhindert nachträgliche Änderungen. public, private, protected steuern die Sichtbarkeit von außen.

Vererbung und Interfaces

interface Greetable {
  public function greet(): string;
}

class User implements Greetable {
  public function __construct(public readonly string $name) {}

  public function greet(): string {
    return "Hallo, $this->name";
  }
}

class Admin extends User {
  public function greet(): string {
    return "Hallo Admin $this->name";
  }
}

Static Methods und Konstanten

class Math {
  public const PI = 3.14159;

  public static function square(int $x): int {
    return $x * $x;
  }
}

echo Math::PI;            // 3.14159
echo Math::square(5);     // 25
i
Konstruktor-Promotion sparsam einsetzen Bei mehr als 5-6 Parametern wird die Konstruktor-Liste unübersichtlich. Dann lieber DTOs oder Builder-Pattern. Aber für die meisten Cases mit 2-4 Properties ist Promotion ein riesiger Gewinn an Lesbarkeit.
Recall
  1. Was generiert public readonly string $name im Konstruktor?
  2. Was ist der Unterschied zwischen extends und implements?
  3. Wie greifst du auf eine static-Konstante zu?
08

Namespaces und Autoloading

PSR-4, use-Statements, Datei-Organisation
Frage zum Einstieg Bei zwei Klassen mit dem Namen User – eine für Admins, eine für Kunden – kracht es. Wie verhindert PHP solche Konflikte? Namespaces sind die Antwort, und sie hängen eng mit Composers Autoloading zusammen.

Namespace deklarieren

Ein Namespace gibt deinen Klassen einen "Pfad". Konvention: ein Namespace pro Datei, am Anfang deklariert:

<?php
declare(strict_types=1);

namespace App\Domain\User;

class User {
  public function __construct(public readonly string $name) {}
}

Die volle Bezeichnung dieser Klasse ist jetzt App\Domain\User\User. Eine andere Klasse mit demselben Namen in einem anderen Namespace kollidiert nicht.

Klassen importieren mit use

<?php
namespace App\Controller;

use App\Domain\User\User;
use App\Domain\Order\Order;

class UserController {
  public function show(int $id): User {
    return new User('Marek');  // kein voller Pfad nötig
  }
}

Mit Aliassen kannst du Namen umbenennen, wenn es Konflikte gibt:

use App\Domain\User\User as DomainUser;
use App\Http\User as HttpUser;

PSR-4: Datei = Namespace

PSR-4 ist die Konvention, wie Namespaces auf Dateipfade abgebildet werden. Composer nutzt das für Autoloading. Eine typische Struktur:

my-project/
├── composer.json
├── vendor/                # installierte Pakete
└── src/
    ├── Controller/
    │   └── UserController.php   # App\Controller\UserController
    └── Domain/
        └── User/
            └── User.php         # App\Domain\User\User

In composer.json definierst du das Mapping:

{
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

Nach composer dump-autoload findet PHP jede Klasse anhand ihres Namespaces automatisch – kein manuelles require mehr.

Eine Klasse, eine Datei Konvention: pro Datei genau eine Klasse, Datei-Name gleich Klassen-Name (case-sensitive!). User.php enthält class User. Die meisten Frameworks und Tools verlassen sich darauf.
Recall
  1. Wozu sind Namespaces da?
  2. Was bedeutet PSR-4 in einem Satz?
  3. Wie importierst du eine Klasse aus einem anderen Namespace?
09

Fehlerbehandlung

Exceptions, try/catch, eigene Fehler
Frage zum Einstieg Was passiert, wenn eine Datei nicht existiert, eine Datenbankverbindung scheitert oder eine API einen unerwarteten Wert liefert? In PHP gibt es zwei Welten: alte false-Rückgaben und moderne Exceptions. Wie navigierst du beide?

Exceptions verstehen

Eine Exception ist PHPs moderne Art, Fehler zu signalisieren. Wird sie nicht gefangen, bricht das Script ab. Mit try/catch reagierst du gezielt:

try {
  $data = json_decode($json, flags: JSON_THROW_ON_ERROR);
  $user = processData($data);
} catch (\JsonException $e) {
  echo "Ungültiges JSON: " . $e->getMessage();
} catch (\Exception $e) {
  echo "Anderer Fehler: " . $e->getMessage();
} finally {
  cleanup();
}

Der finally-Block läuft immer – auch wenn die Exception nicht gefangen wurde. Praktisch für Cleanup wie Datei schließen oder Locks freigeben.

Mehrere Exception-Typen

PHP kennt eine ganze Hierarchie von Exception-Typen. Du kannst gezielt darauf reagieren:

ExceptionWann
InvalidArgumentExceptionFalsche Funktionsargumente
RuntimeExceptionFehler zur Laufzeit
TypeErrorType-Hint verletzt
ValueErrorWert außerhalb erlaubtem Bereich
JsonExceptionJSON-Parsing fehlgeschlagen
PDOExceptionDatenbankfehler

Eigene Exceptions

Für domänenspezifische Fehler definierst du eigene Exception-Klassen. Sie erben von \Exception:

class InsufficientFundsException extends \Exception {}

function withdraw(int $amount): void {
  if ($amount > $this->balance) {
    throw new InsufficientFundsException(
      "Brauche $amount, habe nur $this->balance"
    );
  }
  $this->balance -= $amount;
}
!
Niemals @-Suppression Mit @functionCall() kannst du Warnings unterdrücken. Tu's nicht – du versteckst nur Probleme, die später schwer zu finden sind. Wenn du weißt, dass etwas schiefgehen kann, behandle es explizit mit try/catch.
Recall
  1. Was passiert, wenn eine Exception nicht gefangen wird?
  2. Wann läuft der finally-Block?
  3. Warum solltest du eigene Exception-Klassen schreiben?
10

Composer und erste Anwendung

Pakete installieren, kleines CLI-Tool bauen
Frage zum Einstieg Du hast PHP gelernt – aber wie kommst du jetzt zu einem echten Projekt? Composer ist die Brücke: er installiert Pakete aus dem riesigen PHP-Ökosystem, lädt Klassen automatisch und gibt deinem Projekt Struktur. Lass uns ein kleines Tool bauen.

Projekt aufsetzen

Composer ist der Standard-Paketmanager für PHP. Installation einmal global, dann pro Projekt:

# Neues Projekt
mkdir hello-cli
cd hello-cli
composer init           # interaktiver Wizard

# Paket hinzufügen
composer require symfony/console

# Projektstruktur anlegen
mkdir src
mkdir bin

Composer erzeugt composer.json mit deinen Abhängigkeiten und den Ordner vendor/ mit den installierten Paketen.

composer.json mit Autoload

{
  "name": "marek/hello-cli",
  "type": "project",
  "require": {
    "php": "^8.4",
    "symfony/console": "^7.0"
  },
  "autoload": {
    "psr-4": {
      "App\\": "src/"
    }
  }
}

Nach Änderungen am Autoload: composer dump-autoload ausführen, damit PHP die neuen Pfade kennt.

Kleines CLI-Tool bauen

Mit Symfony Console schreibst du in wenigen Zeilen ein professionelles CLI-Programm:

// src/GreetCommand.php
<?php
namespace App;

use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Output\OutputInterface;

class GreetCommand extends Command {
  protected static $defaultName = 'greet';

  protected function configure(): void {
    $this->addArgument('name', InputArgument::REQUIRED);
  }

  protected function execute(InputInterface $in, OutputInterface $out): int {
    $name = $in->getArgument('name');
    $out->writeln("<info>Hallo, $name!</info>");
    return Command::SUCCESS;
  }
}
// bin/app.php
<?php
require __DIR__ . '/../vendor/autoload.php';

use Symfony\Component\Console\Application;
use App\GreetCommand;

$app = new Application();
$app->add(new GreetCommand());
$app->run();

Im Terminal: php bin/app.php greet Marek. Du siehst "Hallo, Marek!" in Grün. Das ist ein vollständiges CLI-Tool mit Argument-Parsing, Help-Output und Exit-Codes – mit weniger als 50 Zeilen Code.

Erst Tools, dann Framework Mit Composer und einzelnen Symfony-Komponenten kommst du bei kleinen Projekten weit. Erst wenn du Routing, Templates, ORM, Auth zusammen brauchst, lohnt sich der Sprung zu einem vollen Framework wie Laravel oder Symfony.
Recall
  1. Welche Datei steuert Composers Abhängigkeiten und Autoload?
  2. Wozu ist composer dump-autoload?
  3. Welche Library nimmst du für CLI-Tools in PHP?

Wie es weitergeht

Du hast PHP jetzt in 10 Kapiteln vom ersten Script bis zur Composer-Anwendung durchlaufen. Aber Wissen verblasst ohne Wiederholung. Plane aktive Wiederholung ein – effektiver als jedes Re-Reading.

Spaced-Repetition-Plan

Heute

Guide gelesen, Recall-Fragen aus jedem Kapitel beantwortet.

+1 Tag

OnePager überfliegen, alle Recall-Fragen aus dem Kopf beantworten.

+7 Tage

Mini-Projekt: ein eigenes CLI-Tool mit Composer und Symfony Console.

+30 Tage

Cheatsheet als Referenz – ein neues Paket aus Packagist einbinden.

Was als nächstes lernen

Mit diesen Grundlagen kannst du in jede Spezialisierung einsteigen. Empfehlungen je nach Interesse:

Begleitmaterial

Dieser Guide ist Teil eines Sets:

```