0

I'm writing a plugin that uses a few third-party libraries (one from a long-abandoned repo that I'm modifying). Library A uses Library B, and my code uses both.

In Library A are 50-odd files with things like this at the top;

namespace LibraryA\Folder1;

use LibraryA\Folder1\Class1;
use LibraryA\Folder2\Class2;
use LibraryB\Class3;
...

public function __construct($val)
{
    $obj = new LibraryB\Class3($val);
    ...
} 

Now, I want to autoload stuff and keep these libraries self-contained in my plugin's own directory so it doesn't pollute the outside environment, and for ease of installation/updating. I've added a simple autoloader to my project in the hope that it will pick up these libraries from my plugin folder (no, I'm not using composer).

function my_autoloader($className) {
    $file = '/path/to/plugin/'.str_replace('\\', DIRECTORY_SEPARATOR, $className) . '.php';

    if (file_exists($file)) {
        require_once $file;
    }
}
spl_autoload_register('my_autoloader');

The trouble is, the third-party library A uses library B and $className is being passed the fully qualified - and incorrect - namespace, resulting in:

Uncaught Error: Class "LibraryA\Folder1\Class1\LibraryB\Class3.php" not found...

How do I work around this most effectively?

I've tried messing with adding a single backslash before the class names in the use statements. It didn't seem to do anything, though I'm still experimenting. In any case, even if it does work, I don't relish the thought of going through every class in Library A and changing all references to Library B to use \ prefixes so they 'revert' to the root, top level directory.

Do I need to use spl_autoload_register() multiple times to register each namespace independently, so the autoloader somehow treats them differently when it encounters the various use and new statements? If so, how? Do I need separate autoloaders? One for each library?

Or can I code my way out of this by improving my_autoloader() to detect bogus class names and strip off the LibraryA\Unwanted\Paths\ before using require_once()?

Any guidance on approaches welcome (including hints that I'm maybe using the autoloader wrong).

Most answers on SO I've found so far say "use composer" and that's not an option at the moment.

I appreciate that the above isn't very specific, and apologise for that, but I don't know how to attach a bunch of separate, namespaced files to the post that I could use to demonstrate a reproducible test case, so I've outlined the problem instead with the bare minimum code. If that's not clear enough then I'll try and find a way to improve it.

5
  • 1
    As I said when this was in the Staging Ground, in what sense do you think the fully-qualified names being given to the autoloader are "incorrect" or "bogus"? Since the code you show is clearly a made-up example, it's really hard to tell what you're actually trying to do. Are you saying the third-party code is broken? Or that you want to change how it works in some way? Commented Aug 19 at 20:29
  • 1
    One thing that stands out is that in your example you've called a namespace "Folder1". A namespace is not a folder, it's just part of the class name. The definition of that class can be saved in any file you like - you could put a class called MyProject\SomeFeature\Widgets\Frobulator into a file called the_best_class_ever.php if you wanted; or maybe your autoloader could be include md5($class_name) . '.php'; and the file name would be 3a08845e941a98a622e311b47708d3d2.php. I'm not clear if you really want a different class name, or just to put that class in a different file. Commented Aug 19 at 21:08
  • @IMSoP Poor naming in my example, sorry. I was trying to show that the namespaces in this case do actually mirror a directory structure. Without composer, if I "install" (copy) Library A and Library B to my CMS' "vendor" folder, it works. If I "install" (copy) the library trees to my local plugin directory with my own autoloader, they don't. So there's some magic going on in the CMS autoloader for it to know that when LibraryA/Foo/Bar says use LibraryB\Baz; new Baz() it finds it in /vendor/LibraryB/Baz. But in the self-contained case, it tries to look for ./LibraryA/Foo/Bar/LibraryB/Baz 🤔 Commented Aug 20 at 0:39
  • 1
    Which is why I keep pressing you to use an actual example, not try to make one up. Case in point, use LibraryB\Baz; new Baz() means the class name is \LibraryB\Baz, contradicting your previous example. Commented Aug 20 at 6:49
  • 1
    To be really clear, namespaces and autoloading are completely separate features; first, the namespace and use statements are looked at, by the compiler, to create a long class name; then, the autoloader is given that class name, and needs to load that class or the program will crash. The autoloader can't change what name is expected, that decision has already been made. So if there is any magic, rather than just a misunderstanding, it would have to edit the source code of the classes, so that they have different class names or use statements actually written in them. Commented Aug 20 at 6:51

1 Answer 1

1

You used the wrong class name.

use LibraryB\Class3 is equivalent to use LibraryB\Class3 as Class3, in other words, after this declaration, Class3 is considered the class LibraryB\Class3.

However, LibraryB\Class3 in the constructor lacks a leading slash, so it is considered a class name located in the current namespace, which is LibraryA\Folder1\SomeClass\LibraryB\Class3.

namespace LibraryA\Folder1\SomeClass

use LibraryA\Folder1\Class1
use LibraryA\Folder2\Class2
use LibraryB\Class3
...

public function __construct($val)
{
    // LibraryA\Folder1\SomeClass\LibraryB\Class3
    $obj = new LibraryB\Class3($val);
    // LibraryB\Class3
    $obj = new Class3($val); 
    // LibraryB\Class3
    $obj = new \LibraryB\Class3($val); 
    ...
} 
Sign up to request clarification or add additional context in comments.

3 Comments

Yes, that's precisely the problem in Library A that I downloaded and don't particularly want to go through and change every file to add the backslashes! I was hoping to be able to code my way around it. The original developer (who has long since left the code to rot, by the looks of things) assumed a composer environment. It all works fine if I "install" it in a proper composer-esque way, but I'm trying not to pollute our global /vendor directory. Plus, LibraryB has additional resources that use hard-coded paths and assumes a directory structure that would screw up our /vendor space.
The problem is that the current name is wrong, even if you can include the corresponding file, PHP does not recognize this class name, unless class_alias is used. I didn't mean you must add backslashes, actually, since you have written the use declaration, there is no need to add the LibraryB\ prefix.
How you install it isn't going to make any difference to what class names the code is looking for. Composer isn't doing anything magic, it's just mapping "fully qualified class name X" to "file on disk called Y".

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.