PHP Backward Compatibility – Impacts of Using class_alias() to Maintain Backwards Compatibility in PHP

backward compatibilitynamespaceobject-oriented-designPHPrefactoring

Scenario: An open-source PHP project that has existed for several years has too many classes in the main namespace that we want to refactor some of them into another namespace, but we have a widely used and diverged plugin API, and refactoring may cause a major backwards incompatibility. This plugin API is basically loading external PHP files that interact with the classes and functions in the core code.

This project is not a Composer project (don't ask why), and it uses an class autoloading library maintained by ourselves.

I recently discovered the function class_alias, which might help with this problem.

I am considering refactoring the classes in the main namespace into a sub-namespace, and update references to them in the project. For the PHP files that originally contained the refactored classes (now they have been moved), I want to put something like this:

<?php
class_alias(NewClassName::class, OldClassName::class);

This will be loaded by the autoloading library when a plugin requires the use of OldClassName. I have tested this method and primary tests show that it works well.

Next step is to clean up the main namespace directory to fulfill the real motive of our refactoring – there are too many files in the main namespace directory. Hence, I am creating a new directory next to the source directory called als (alias directory), and making als a secondary source folder where alias class files are located.

Before, the project looked like this:

src/
    src/Foo/Bar.php

Now, the project looks like this:

src/
    src/Foo/Bar/Bar.php

als/
    als/Foo/Bar.php

Tests show that this method is working well so far.

However, are there any possible side-effects, from technical/performance perspective, documentation perspective or code structure perspective? We have never tried doing something like this and have never seen anyone doing this. Many developers are watching the repository for reference of the API, so we want to be more careful before trying these changes.

Best Answer

I have done it and it's possible, though there are a few pitfalls around autoloading to consider. I described the approach in a blog post:

PHP: Using class_alias to maintain BC while moving/renaming classes

Summary:

  • Do not put the class_alias() code into the original class 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)
    
  • If you do not use composer or a single bootstrap file that is always loaded, the best way I found is to put class_alias() into the new class file:

    \class_alias(NewClass::class, OldClass::class)
    

    Then you have to make sure that the new class with the alias is also loaded if somebody tries to actually instantiate the old class (or use static properties, methods or constants). To do this, I trigger the autoloader in the old class file:

    \class_exists(NewClass::class);
    
Related Topic