Eine Shop-Bibliothek zieht um: Aus der Klasse Shop_Warenkorb wird Shop\Neu\Warenkorb. Im eigenen Code ist das schnell ersetzt, aber Plugins von Dritten, alte Templates und gespeicherte Sessions kennen nur den alten Namen. Wie man mit use … as innerhalb einer Datei einen kürzeren Namen vergibt, zeigt das Tutorial zu PHP Namespaces. PHP class_alias() geht weiter: Es legt zur Laufzeit einen zweiten, überall gültigen Namen für dieselbe Klasse an. Genau das braucht alter Code, der nach dem Umzug weiterlaufen soll.
Alle Beispiele liefen unter Windows und Linux mit PHP 8.4, Versionsunterschiede wurden zusätzlich mit PHP 8.2, 8.3 und 8.5 geprüft.
Was PHP class_alias() macht
PHP class_alias() erzeugt keine neue Klasse und keine Kopie. Die Funktion trägt einen weiteren Namen in die Klassentabelle ein, der auf die vorhandene Klasse zeigt:
<?php
class Versand
{
public static int $zaehler = 0;
}
var_dump(class_alias('Versand', 'Mailer'));
/* bool(true) */
$m = new Mailer();
echo get_class($m), "\n";
/* Versand */
Mailer::$zaehler = 5;
echo Versand::$zaehler;
/* 5 */
Das Objekt gehört zur Klasse Versand, auch wenn es über den Alias entstanden ist. Statische Properties teilen sich beide Namen, weil es nur eine Klasse gibt.
Syntax und Parameter im Original
Drei Parameter, der dritte ist optional:
class_alias(
string $class,
string $alias,
bool $autoload = true
): bool
/* class_alias() is used to create an alias for a
class at runtime.
class - the name of the original class
alias - the alias name for the class
autoload - whether to autoload the original
class if it is not found
return - true on success, false on failure
(with a PHP Warning).
The aliased class can be used under both names;
objects created via the alias are instances of
the original class. Aliases work for classes,
interfaces, traits and enums. Since PHP 8.3 an
alias of an internal class can be created, too.
The alias is global, not in the same namespace
as the calling code. */
Der Alias gilt global und ab dem Moment des Aufrufs bis zum Ende der Anfrage. Entfernen lässt er sich nicht, PHP bietet dafür keine Funktion. Der dritte Parameter steuert, ob PHP für die Originalklasse den Autoloader fragt.
Alias und Originalklasse: get_class(), instanceof und ::class
Liegt die Originalklasse in einem Namespace, taucht fast überall der volle Originalname auf:
<?php
namespace App\Mail;
class Versand
{
public function wer(): string
{
return static::class;
}
}
class_alias(Versand::class, 'Mailer');
$m = new \Mailer();
echo get_class($m), "\n";
/* App\Mail\Versand */
echo $m->wer(), "\n";
/* App\Mail\Versand */
echo \Mailer::class, "\n";
/* Mailer */
var_dump($m instanceof \Mailer);
/* bool(true) */
$r = new \ReflectionClass('Mailer');
echo $r->getName();
/* App\Mail\Versand */
Am Objekt bleibt der Alias nur an einer Stelle sichtbar: Mailer::class. Dieser Ausdruck wird schon beim Kompilieren durch den geschriebenen Namen ersetzt, PHP schaut dafür nicht einmal in die Klassentabelle. $m::class liefert dagegen den Originalnamen, ebenso self::class, var_dump() und serialize(). Steht der Alias aber als Typ in einer Signatur, nennen Reflection und Fehlermeldungen den geschriebenen Namen. In get_declared_classes() fehlt der Alias; was die Funktion sonst liefert, zeigt das Tutorial zu get_declared_classes(). Wer wissen will, ob ein Name ein Alias ist, vergleicht ihn ohne Rücksicht auf Groß- und Kleinschreibung mit (new ReflectionClass($name))->getName(), denn auch für app\mail\versand liefert Reflection App\Mail\Versand.
Das Beispiel zeigt nebenbei eine Falle: Obwohl class_alias() im Namespace App\Mail aufgerufen wird, entsteht der globale Name Mailer, nicht App\Mail\Mailer. Der zweite Parameter ist ein String und wird nicht relativ zum Namespace aufgelöst. Wer den Alias im Namespace haben will, schreibt __NAMESPACE__ . '\Mailer'.
class_alias() oder use … as?
Beide vergeben einen anderen Namen, aber auf völlig verschiedenen Ebenen:
<?php
namespace Shop\Neu {
class Warenkorb {}
}
namespace Bestellung {
use Shop\Neu\Warenkorb as Korb;
echo Korb::class, "\n";
/* Shop\Neu\Warenkorb */
var_dump(class_exists('Korb'));
/* bool(false) */
class_alias(Korb::class, 'AlterKorb');
var_dump(class_exists('AlterKorb'));
/* bool(true) */
}
use … as ist eine Schreibhilfe für genau eine Datei. PHP ersetzt Korb beim Kompilieren durch den vollen Namen, zur Laufzeit gibt es keine Klasse Korb, und ein new $name mit dem String 'Korb' scheitert mit Class "Korb" not found. Der Name aus PHP class_alias() existiert dagegen wirklich: in jeder Datei, für Strings, für unserialize() und für fremden Code. Wer nur einen langen Klassennamen abkürzen will, nimmt use … as. Wer alten Code mit einer umbenannten Klasse versorgen muss, braucht den Alias.
Der Parameter autoload
Ist die Originalklasse beim Aufruf noch nicht geladen, fragt PHP class_alias() standardmäßig den Autoloader nach ihr. Wie Autoloader arbeiten, beschreibt das Tutorial zum Autoloading mit PSR-4:
<?php
spl_autoload_register(function (string $klasse) {
echo "Autoloader: $klasse\n";
$datei = __DIR__ . '/Artikel.php';
if ($klasse === 'Lager\Artikel') {
require $datei;
}
});
var_dump(@class_alias('Lager\Artikel', 'A1', false));
/* bool(false), der Autoloader wird nicht gefragt */
var_dump(class_alias('Lager\Artikel', 'A2'));
/* Autoloader: Lager\Artikel
bool(true) */
Die Datei Artikel.php enthält nur die Klasse:
<?php
/* Datei: Artikel.php */
namespace Lager;
class Artikel {}
Ohne das @ erscheint bei false die Warnung Class "Lager\Artikel" not found. Den Alias-Namen selbst lädt PHP nie nach.
Klassen umbenennen ohne Bruch mit PHP class_alias()
Wie man das sauber macht, zeigt eine Bibliothek, die in vielen Projekten im vendor-Ordner liegt. Der PHP-Parser von Nikita Popov hat in Version 5 etliche Klassen umbenannt, etwa Scalar\LNumber in Scalar\Int_. Am Ende der neuen Klassendatei steht dort ein Aufruf von PHP class_alias() mit dem Kommentar „@deprecated compatibility alias“. Unter dem alten Dateinamen liegt eine kleine Datei, die nur die neue einbindet. Übertragen auf eigenen Code:
<?php
/* Datei: src/Mail/Versand.php */
namespace App\Mail;
class Versand
{
}
/* @deprecated alter Name, entfällt in 3.0 */
class_alias(Versand::class, 'Mail_Versand');
Unter dem alten Pfad bleibt nur diese Datei:
<?php
/* Datei: Mail/Versand.php */
require __DIR__ . '/../src/Mail/Versand.php';
if (false) {
class Mail_Versand extends \App\Mail\Versand
{
}
}
Der Block mit if (false) wird nie ausgeführt. Er ist für Werkzeuge da, die Klassen durch Lesen statt Ausführen finden, etwa den Classmap-Generator von Composer, IDEs und statische Analyse. Fragt alter Code nach Mail_Versand, lädt der Autoloader die alte Datei, und die bindet die neue samt Alias ein:
<?php
require __DIR__ . '/Mail/Versand.php';
$v = new Mail_Versand();
echo get_class($v);
/* App\Mail\Versand */
Verlockend ist eine Abkürzung: den Alias erst im Autoloader anlegen, sobald jemand nach dem alten Namen fragt. Das hat einen Haken, den der Test deutlich zeigte. instanceof, Typdeklarationen und catch fragen keinen Autoloader. Solange niemand den alten Namen per new oder statischem Aufruf angefasst hat, gilt er als unbekannt:
flowchart TD
A[Alter Name im Code] --> B{Alias angelegt?}
B -- ja --> C[Originalklasse wird benutzt]
B -- nein --> D{new, statisch, unserialize?}
D -- ja --> E[Autoloader legt Alias an]
E --> C
D -- nein --> F[instanceof und Typen: false]
Im Code sieht die Falle so aus:
<?php
namespace Shop {
class Bon {}
}
namespace {
spl_autoload_register(function (string $k) {
if ($k === 'Alter_Bon') {
class_alias('Shop\Bon', 'Alter_Bon');
}
});
function drucke(Alter_Bon $bon): string
{
return 'gedruckt';
}
$bon = new Shop\Bon();
var_dump($bon instanceof Alter_Bon);
/* bool(false) */
try {
drucke($bon);
} catch (TypeError $e) {
echo strtok($e->getMessage(), ','), "\n";
}
/* drucke(): Argument #1 ($bon) must be of type
Alter_Bon */
class_exists('Alter_Bon');
echo drucke($bon);
/* gedruckt */
}
Erst nachdem class_exists() den Autoloader ausgelöst hat, stimmt alles. Der Alias gehört deshalb an das Ende der neuen Klassendatei. Der Autoloader-Weg taugt nur zusätzlich, etwa um mit trigger_error() und E_USER_DEPRECATED auf den alten Namen hinzuweisen. Im Test kam diese Meldung genau einmal, beim ersten new, und nur solange die neue Klasse noch nicht geladen war. Danach existiert der Alias schon, und der Autoloader wird für den alten Namen nicht mehr gefragt.
Ein Gewinn zeigt sich bei serialisierten Daten. unserialize() fragt den Autoloader sehr wohl, und mit Alias entsteht aus dem alten Namen ein Objekt der neuen Klasse:
<?php
namespace Shop\Neu {
class Warenkorb
{
public array $posten = [];
}
}
namespace {
$alt = 'O:9:"Shop_Korb":1:{s:6:"posten";'
. 'a:1:{i:0;s:5:"Tasse";}}';
echo get_class(unserialize($alt)), "\n";
/* __PHP_Incomplete_Class */
class_alias('Shop\Neu\Warenkorb', 'Shop_Korb');
$korb = unserialize($alt);
echo get_class($korb), ' ', $korb->posten[0];
/* Shop\Neu\Warenkorb Tasse */
}
Beim nächsten serialize() steht dann der neue Name in der Session. Gespeicherte Warenkörbe wandern so beim normalen Betrieb auf die neue Klasse.
Alias oder leere Unterklasse?
Statt PHP class_alias() liest man in älterem Code oft class Alt extends Neu {}. Die beiden Varianten verhielten sich im Test sehr unterschiedlich:
| Prüfung | class_alias() | leere Unterklasse |
get_class(new Alt()) | Neu | Alt |
new Neu() instanceof Alt | true | false |
Funktion mit Typ Alt bekommt new Neu() | funktioniert | TypeError |
serialize(new Alt()) | neuer Name | alter Name bleibt |
Originalklasse ist final | funktioniert | Fatal error |
Die Unterklasse ist ein neuer Typ, der Alias derselbe. Alter Code, der Objekte der neuen Klasse unter dem alten Typ erwartet, bricht nur mit der Unterklasse. Bei einer finalen Klasse meldet PHP sogar Class S cannot extend final class E, während der Alias klaglos entsteht.
Interfaces, Traits, Enums und interne Klassen
PHP class_alias() funktioniert nicht nur mit Klassen. Interfaces, Traits und Enums lassen sich genauso aliasen, und bei Exceptions fängt ein catch mit dem alten Namen auch die neue Klasse:
<?php
class ZahlungFehlgeschlagen extends RuntimeException {}
class_alias('ZahlungFehlgeschlagen', 'Zahlung_Fehler');
try {
throw new ZahlungFehlgeschlagen('Karte abgelehnt');
} catch (Zahlung_Fehler $e) {
echo get_class($e), "\n";
}
/* ZahlungFehlgeschlagen */
enum Status: string
{
case Offen = 'o';
case Bezahlt = 'b';
}
class_alias(Status::class, 'Bestellstatus');
echo Bestellstatus::from('b')->name;
/* Bezahlt */
Ein Interface-Alias erfüllt das Original: Eine Klasse mit implements AltesInterface besteht die Prüfung auf das neue Interface. interface_exists() erkennt den Alias, class_exists() nicht. Bei eingebauten Klassen entscheidet die PHP-Version:
<?php
class_alias('DateTimeImmutable', 'Zeitpunkt');
$z = new Zeitpunkt('2026-11-02');
echo get_class($z), ' ', $z->format('d.m.Y');
/* DateTimeImmutable 02.11.2026 */
Ab PHP 8.3 klappt das. PHP 8.2 brach im Test mit ValueError: class_alias(): Argument #1 ($class) must be a user-defined class name, internal class name given ab. Wer 8.2 noch unterstützt, prüft vorher PHP_VERSION_ID >= 80300. Statische Analyse wie PHPStan führt den geprüften Code nicht aus. Einen Laufzeit-Alias kennt PHPStan nur, wenn die Datei mit dem Aufruf unter bootstrapFiles eingetragen ist, denn diese Dateien führt es tatsächlich aus. Bloßes Mitlesen über scanFiles genügte im Test nicht, PHPStan meldete weiter class.notFound. Ohne diesen Eintrag helfen die if (false)-Blöcke aus dem Umbenennungsmuster, denn die Klasse darin findet PHPStan schon durch Lesen.
Fehlermeldungen bei PHP class_alias()
Ist der gewünschte Name schon belegt oder fehlt die Originalklasse, gibt es eine Warnung und false:
<?php
class Original {}
class Belegt {}
var_dump(class_alias('Original', 'Belegt'));
/* Warning: Cannot redeclare class Belegt
(previously declared in ...)
bool(false) */
var_dump(class_alias('GibtEsNicht', 'Egal'));
/* Warning: Class "GibtEsNicht" not found
bool(false) */
Die erste Meldung hat zwei Tücken. Seit PHP 8.4 lautet sie Cannot redeclare class Belegt (previously declared in …), in PHP 8.2 und 8.3 noch Cannot declare class Belegt, because the name is already in use. Und die Ortsangabe zeigt nicht auf die Klasse, die den Namen belegt, sondern auf die Originalklasse, die einen Alias bekommen sollte. Wer der Zeilennummer folgt, sucht an der falschen Stelle.
Härter trifft es reservierte Namen. class_alias('Original', 'int') endete mit Fatal error: Cannot use "int" as a class alias as it is reserved, und das Skript brach ab. Dasselbe galt für string, bool, self, static, void, mixed und weitere Typnamen. Sonst prüft PHP den Namen kaum: Ein Alias mit Leerzeichen oder ein leerer String wurde angelegt, ein führender Backslash einfach entfernt.
Fazit
PHP class_alias() gibt einer Klasse einen zweiten Namen, keinen zweiten Typ. Objekte, Reflection, serialize() und statische Properties gehören weiter zur Originalklasse, am Objekt liefert nur Alt::class den Alias-String. Für Abkürzungen innerhalb einer Datei ist use … as das bessere Werkzeug, für umbenannte oder verschobene Klassen der Alias.
Beim Umbenennen gehört der Aufruf von PHP class_alias() an das Ende der neuen Klassendatei, ergänzt um eine kleine Datei unter dem alten Namen. Ein Alias, der erst im Autoloader entsteht, lässt instanceof und Typprüfungen scheitern, bis der alte Name einmal geladen wurde. Interne Klassen lassen sich erst ab PHP 8.3 aliasen, und reservierte Typnamen beenden das Skript sofort.