Search code examples
phpfunctional-programmingnamespacescomposer-php

PHP: Namespace conflict (from third parties) in purely functional (static) code


I have a project which is entirely functional code, as in: all functions are static and should not depend upon how they were called. If you call function foo("hello", 12.345, true); it should not matter at all the context around that function call -- it should always perform the same action.

The problem I have is that I'm using two third-party libraries, who, through composure, both import GuzzleHttp and define it as a Namespace in their autoloaders, though they use different versions of the Guzzle client. (The two 3rd party SDKs I've imported are for Plivo and libphonenumber, though not sure it's relevant.) The way the functions are written I do something like this:

function sendPlivoSms($number, $message) {
    require_once("/vendor/plivo/autoloader.php");
    $gz_client = new \GuzzleHttp\Client();
    // more code here
}

function formatNationalPhoneNumber($number) {
    require_once("/vendor/libphonenumber/autoloader.php");
    $gz_client = new \GuzzleHttp\Client();
    // more code here
}

The issue arises when I call formatNationalPhoneNumber before sendPlivoSms -- it loads the GuzzleHttp Namespace, which then causes the sending SMS function to call the wrong Client class, and therefore breaks (specifically due to a missing chooseHandler function). The autoloaders are loading different versions of the Guzzle code, at different locations in the code source (which is 100% okay aside from the namespace hijacking).

Is there a way for me to isolate these function calls so that do not steal each other's namespace references?

For example, could I "unset" the namespace at the end of the function call (again: to keep the function perfectly static)? Or is there a way for me to put the namespace under a limited scope somehow?

File tree and composer.json follows:

 /
  composer.json
  tons of other directories and code here...
  ./thirdparty/
    ./libphonenumber/
        ./vendor/
            ./composer.json
  ./vendor/
      all composer directories loaded here, except the libphonenumber ones

Solution

  • You have numerous problems here.

    Your primary problem is that you should not have thirdparty as a subdir. That code for libphonenumber should be under the top-level vendor subdir, alongside the other libraries. You should not be downloading, installing, or maintaining it yourself -- because (as you've noticed) you'll create conflicts. Let Composer do all the work.

    Delete the thirdparty subdir and run composer require giggsey/libphonenumber-for-php at the top level project dir. This will download libphonenumber into the vendor subdir and add its name into your project's composer.json file.

    Then, in your own source code, you want to use only the one top level autoloader that Composer built for you:

    require_once 'vendor/autoload.php';
    

    (Or whatever the path would be, relative to your code.)

    This should be run once, at initialization. Don't put an include inside each function that uses an external library. Given that, you don't need to worry about different versions of Guzzle. Whatever version got installed by Composer will just work for anything that needs it.

    That said, it looks like you're using extraordinarily old versions of things. You're at:

    "monolog/monolog": "1.0.*",
    

    This version is over ten years old! You also have:

    "ext-mcrypt": "*",
    

    This indicates that you're probably running PHP 5, which was end-of-lifed years ago. If your code is that old, then you can't really expect current versions of modern libraries like Guzzle to work nicely with it.