Sometimes you want to rename a class or move it to a different namespace. But as soon as it is used anywhere outside the package, this is breaking backwards compatibility and should not be done lightheartedly.
Luckily there is a way in PHP to have both, the old class and the new class, while deprecating the old one: class_alias()
.
How to use class_alias() without messing up class autoloading
Let’s say, the old class is OldClass
and we want to rename it to NewClass
.
First, we rename the class and move it from OldClass.php
to a new file NewClass.php
.
Now add the following line below the class in the same file (NewClass.php):
\class_alias(NewClass::class, OldClass::class);
Note: You can use the ::class
constant for nonexistent classes, it just resolves the class name to a fully qualified class name (FQN) with the rules explained in Name resolution rules, without actually looking up the class.
Now we still need the file OldClass.php
because it is loaded when a the OldClass
class is used. There we trigger the autoloader for the NewClass
class using class_exists():
\class_exists(NewClass::class);
You might ask: Why not “class_alias” in the old file which then autoloads the new file? The problem is that type hints do not trigger the autoloader. Similar to the ::class
constant, type hints are resolved to a class name which is compared to the class name of the passed object, without actually looking for the type hinted class! That’s why it is important to have the alias in place as soon as the real class is loaded.
Otherwise passing a NewClass
instance to the following function would fail because “NewClass” != “OldClass” and the alias is not defined yet:
function oldFunction(OldClass $old)
For the same reason it is a bad idea to keep the old class as subclass of the new one! With a class alias, both class names are treated as the same class. If the deprecated class extends the original class, a type hint against the deprecated class in client code will throw an error if an object of the new class is passed. Also it does not work for abstract classes, final classes, interfaces and traits.
Alternative location for “class_alias()”
Redditor mglaman suggested another approach, that works well if the package is loaded via composer: Collect the aliases in a separate file, deprecated.php
and include it additionally with composer:
{ "autoload": { "files": ["src/deprecated.php"] } }
This way you have a nice overview of deprecated code and the new classes don’t contain legacy code.
@deprecated Warning in IDE
It is a good practice to mark deprecated code with the @deprecated
docblock annotation. IDEs can recognize those and show a notice if you use deprecated methods or classes. Unfortunately, for alisases this is not possible, but found an IDE friendly workaround:
Add this code after the \class_alias()
call:
if (! \class_exists(OldClass::class)) { // essentially this is "if(false)" /** @deprecated this is an alias for NewClass */ class OldClass {} }
Alternatively, you could collect these pseudo classes in a separate file, like “.ide.php”, which you never load
Note: Don’t be confused by the “class_” function names. The technique does not only work for classes, but just as well for interfaces and traits.
Ist es damit dann nicht mehr nötig, alle Funktionsaufrufe durch den Typ der neuen Klasse zu ersetzen ?
Also nach
\class_alias(NewClass::class, OldClass::class);
kann ich weiter
function foo(OldClass ObjInstance)
benutzen, oder muß ich alle Funktionen anpassen ?
Richtig, das ist der Sinn dahinter, auch wenn man langfristig die Aufrufe vermutlich ersetzen möchte. So verschafft man sich für das Anpassen Zeit, und kann den Alias zu einem späteren Zeitpunkt löschen, wenn er nicht mehr benötigt wird.
Danke, damit ist das Verfahren sehr hilfreich.
Grundsätzlich geht das, aber das Problem mit TypeHints ist, sie triggern kein Autoloading. Daher muss “OldClass” auf jeden Fall bekannt gemacht werden.
Seitdem PHPStorm class_alias() erkennt, funktioniert das gekapselte @deprecated nicht mehr. Für PHPStorm sind das dann zwei verschiedene Implementationen.