BrianHenryIE / strauss

Prefix PHP namespaces and classnames to allow multiple versions of libraries to exist without conflict.
https://brianhenryie.github.io/strauss/
MIT License
143 stars 23 forks source link
composer namespaces

PHPUnit PHPStan

Strauss – PHP Namespace Renamer

A tool to prefix namespaces, classnames, and constants in PHP files to avoid autoloading collisions.

A fork of Mozart for Composer for PHP.

Have you ever activated a WordPress plugin that has a conflict with another because the plugins use two different versions of the same PHP library? Strauss is the solution to that problem - it ensures that your plugin's PHP dependencies are isolated and loaded from your plugin rather than loading from whichever plugin's autoloader registers & runs first.

⚠️ Sponsorship: I don't want your money. Please write a unit test to help the project.

Table of Contents

Installation

As a .phar file (recommended)

There are a couple of small steps to make this possible.

Create a bin/.gitkeep file

This ensures that there is a bin/ directory in the root of your project. This is where the .phar file will go.

mkdir bin
touch bin/.gitkeep

.gitignore the .phar file

Add the following to your .gitignore:

bin/strauss.phar

Edit composer.json `scripts

In your composer.json, add strauss to the scripts section:

"scripts": {
    "prefix-namespaces": [
        "sh -c 'test -f ./bin/strauss.phar || curl -o bin/strauss.phar -L -C - https://github.com/BrianHenryIE/strauss/releases/latest/download/strauss.phar'",
        "@php bin/strauss.phar",
        "@php composer dump-autoload"
    ],
    "post-install-cmd": [
        "@prefix-namespaces"
    ],
    "post-update-cmd": [
        "@prefix-namespaces"
    ]
}

This provides composer strauss, which does the following:

  1. The sh -c command tests if bin/strauss.phar exists, and if not, downloads it from releases.
  2. Then @php bin/strauss.phar is run to prefix the namespaces.
  3. Ensure that composer's autoload map is updated.

As a dev dependency via composer (not recommended)

If you prefer to include Strauss as a dev dependency, you can still do so. You mileage may vary when you include it this way.

composer require --dev brianhenryie/strauss

Edit composer.json `scripts

"scripts": {
    "prefix-namespaces": [
        "strauss",
        "@php composer dump-autoload"
    ],
    "post-install-cmd": [
        "@prefix-namespaces"
    ],
    "post-update-cmd": [
        "@prefix-namespaces"
    ]
}

Usage

If you add Strauss to your composer.json as indicated in Installation, it will run when you composer install or composer update. To run Strauss directly, simply use:

composer prefix-namespaces

To update the files that call the prefixed classes, you can use --updateCallSites=true which uses your autoload key, or --updateCallSites=includes,templates to explicitly specify the files and directories.

composer -- prefix-namespaces --updateCallSites=true

or

composer -- prefix-namespaces --updateCallSites=includes,templates

Configuration

Strauss potentially requires zero configuration, but likely you'll want to customize a little, by adding in your composer.json an extra/strauss object. The following is the default config, where the namespace_prefix and classmap_prefix are determined from your composer.json's autoload or name key and packages is determined from the require key:

"extra": {
    "strauss": {
        "target_directory": "vendor-prefixed",
        "namespace_prefix": "BrianHenryIE\\My_Project\\",
        "classmap_prefix": "BrianHenryIE_My_Project_",
        "constant_prefix": "BHMP_",
        "packages": [
        ],
        "update_call_sites": false,
        "override_autoload": {
        },
        "exclude_from_copy": {
            "packages": [
            ],
            "namespaces": [
            ],
            "file_patterns": [
            ]
        },
        "exclude_from_prefix": {
            "packages": [
            ],
            "namespaces": [
            ],
            "file_patterns": [
            ]
        },
        "namespace_replacement_patterns" : {
        },
        "delete_vendor_packages": false,
        "delete_vendor_files": false
    }
},

The following configuration is inferred:

The following configuration is default:

The remainder is empty:

Autoloading

Strauss uses Composer's own tools to generate a classmap file in the target_directory and creates an autoload.php alongside it, so in many projects autoloading is just a matter of:

require_once __DIR__ . '/strauss/autoload.php';

If you prefer to use Composer's autoloader, add your target_directory (default vendor-prefixed) to your autoload classmap and Strauss will not create its own autoload.php when run. Then run composer dump-autoload to include the newly copied and prefixed files in Composer's own classmap.

"autoload": {
    "classmap": [
        "vendor-prefixed/"
    ]
},

Motivation & Comparison to Mozart

I was happy to make PRs to Mozart to fix bugs, but they weren't being reviewed and merged. At the time of writing, somewhere approaching 50% of Mozart's code was written by me with an additional nine open PRs and the majority of issues' solutions provided by me. This fork is a means to merge all outstanding bugfixes I've written and make some more drastic changes I see as a better approach to the problem.

Benefits over Mozart:

Strauss will read the Mozart configuration from your composer.json to enable a seamless migration.

Alternatives

I don't have a strong opinion on these. I began using Mozart because it was easy, then I adapted it to what I felt was most natural. I've never used these.

Interesting

Breaking Changes

Please open issues to suggest possible breaking changes. I think we can probably move to 1.0.0 soon.

Changes before v1.0

Changes before v2.0

The correct approach to this problem is probably via PHP-Parser. At least all the tests will be useful.

Acknowledgements

Coen Jacobs and all the contributors to Mozart, particularly those who wrote nice issues.