0

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
Bing
  • 3,071
  • 6
  • 42
  • 81
  • 1
    _"it should not matter at all the context around that function call -- it should always perform the same action"_ It seems you have a fundamental misunderstanding of how namespaces work. Namespaces are unique. You can't expect two different calls to a function with the same name to run two different pieces of code. I'm confused as to why you have an autoloader for each library when both libraries are Composer-controlled. There should be one autoloader for the whole project. – Alex Howansky Nov 22 '22 at 04:06
  • I must have misconfigured them when I installed them originally. I'm fairly new to Composer, and while I understand namespaces, the fact that they require the same library (GuzzleHttp) but different versions, it seemed to me like that's an issue of library management that I didn't foresee Composer even being able to handle this (does it dynamically re-name the namespaces? if they need to be unique, but are in conflict, how else could it resolve them?). That's why I asked about isolation, after all if I ran these function calls as independent threads they DO work as intended. – Bing Nov 22 '22 at 04:18
  • This comment sure makes it sound like I'm screwed trying to version manage packages with a single Composure install, unless I'm misunderstanding something: [And NO you cannot install different versions of the same package by composer.](https://stackoverflow.com/questions/53831504/prestashop-guzzle-conflict?rq=1#:~:text=And%20NO%20you%20cannot%20install%20different%20versions%20of%20the%20same%20package%20by%20composer.) I'm amazed if there's no answer to my underlying question: **Is there a way for me to isolate these function calls so that do not steal each other's namespace references?** – Bing Nov 22 '22 at 04:25
  • 2
    They're not "stealing each other's namespace references." There is _one_ namespace with a given name. Whichever branch of code claims that name first is going to "win" future references to it. You can't have multiple versions of the same namespace in the same script. If you use one composer instance to manage both packages, it will likely be able to find and install a version of Guzzle that works with both libraries. That said, the giggsey/libphonenumber-for-php that you linked to doesn't even use Guzzle, so I'm not sure how you're seeing a conflict. Post your `composer.json` file. – Alex Howansky Nov 22 '22 at 05:00
  • Sorry, "stealing" may be the wrong word, which is why I used "conflict" in my question's title. Like I said, I noticed that the first-to-claim seems to "win" which caused a problem when it was the "wrong" package. I have updated it to include some of my actual file structure and the relative composer.json files per your feedback (which I wholly appreciate)! (And I do realize from this convo that I probably should only have 1 at root.) – Bing Nov 22 '22 at 05:53
  • What Alex wrote, and, for me, if you allow the comment, it looks like as if you were in need to trick composer while for what you outline you could let have done composer the heavy work of the dependency management (which actually is what it has been written for). In the unfortunate event that you actually have a dependency conflict which blocks the resolution of the whole set, you need to address it differently for sure. `thirdparty` etc. and more prominent with `require_once("/vendor/libphonenumber/autoloader.php")` looks cumbersome to me. build back. – hakre Nov 22 '22 at 16:53
  • 2
    And no, the code is not purely functional. This is because it contains the `require` statement which has side-effects. Additionally it relies on the state of the file-system, which is prone to race conditions. – hakre Nov 22 '22 at 16:55
  • 1
    @hakre Not to get off-topic, but what kind of code is not subject to race-conditions? If I have a fundamental misunderstanding of "functional code" I'd like to educate myself, but currently I don't understand how race conditions come in to play in this definition. – Bing Nov 23 '22 at 07:16
  • @Bing: Yes, leave the race conditions aside, the side-effects is what is hurting here, once a class has been loaded, a second autoloader won't kick in and it can't be replaced anyway. You have two functions, but it only makes sense to call one of them per PHP process. Technically you have two programs, and with that mindset it naturally works. – hakre Nov 23 '22 at 20:56

1 Answers1

3

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.

Alex Howansky
  • 50,515
  • 8
  • 78
  • 98
  • 1
    For what it's worth, a lot of those aren't even used, just automatically included by my IDE. I have spent every working hour since this comment on implementing it, and have made great progress. This project is nearly 10 years old, hence some of these self-managed solutions (plus a fear of [`npm left-pad`](https://www.explainxkcd.com/wiki/index.php/2102:_Internet_Archive)) Thank you for you input, I have sincerely taken it to heart and already implemented 80% of it. – Bing Nov 23 '22 at 07:19