I have an old library I need in my project that doesn't utilize PSR-0/4 or any namespacing at all. It has loads of PHP files with different classes. Let's say I add this library to my project via 'classmap' autoloading (as Composer documentation suggests):
"autoload": {
"classmap": [
"libraries/some-lib"
]
}
The problem is this library doesn't use inheritance/polymorphism - it has base classes with the same names and same methods (but different implementations of those methods) in different PHP files.
libraries/some-lib/Foo.php:
class Foo
{
public function bar()
{
...
}
}
libraries/some-lib/Foo-alternate.php:
class Foo
{
public function bar()
{
...
}
}
So when composer is generating it's classmap Ambiguous class resolution warnings are generated for them and only one version of Foo will be available to me in my project's code via \Foo because of name collisions.
My question is what is the way to go about this problem here without modifying library code? Is there any options Composer provides for situations like this so that I can import both versions of Foo class into composer's classmap and distinguish between the two in my project as a result?
PHP does not allow two classes to have the same name, so even if you will find workaround for this (like manually loading class from specified file depending on context) this will be more like brittle hack than real solution and it will fail when you try to load these two classes in the same request.
Although you want to avoid modifying library code, this is actually the simplest and most reliable solution. It should be easy to make these changes and I doubt that library which still not uses PSR-0/4 is actively developed, so maintenance of fork should not require any additional effort.
Is there any options Composer provides for situations like this so that I can import both versions of Foo class into composer's classmap and distinguish between the two in my project as a result?
Fully qualified name (which for non-namespaced classes is just class name) should be unique, this is PHP "limitation" and Composer autoloader relies on it. The best what you could get is to ignore one of these class using exclude-from-classmap setting:
"autoload": {
"exclude-from-classmap": ["libraries/some-lib/Foo-alternate.php"]
}
Then Composer will autoload only class from libraries/some-lib/Foo.php. You may still load manually second class (require __DIR__ . '/libraries/some-lib/Foo-alternate.php'), but you should be really careful with this (this will result fatal error if libraries/some-lib/Foo.php is already loaded).
Related
I am having difficulty getting psysh to instantiate classes.
I am trying to use PSR-4 namespaces, and have registered a psr-4 autoload in composer like this:
"autoload": {
"psr-4": {
"System\\": "phpclasses/"
}
},
There is a class in phpclasses\Test.php, class name Test with a static method called hello().
I open a command shell, start psysh, and psysh appears to be working normally.
If I try to run Test::hello(); it will fail, unless I call it like this first: echo System\Test::hello();
This actually fails with the message:
PHP Fatal error: Class 'System\Test' not found in eval()'d code on line 1
but then I can successfully run: echo Test::hello();
echo System\Test::hello(); will never work
I tried Use System; and use System\Test; has no beneficial effect.
Every class I use, I have to go through this routine, which is kind of a drag because some of the classes uses static methods, and each of those will only work if each class has gone through that fail first routine.
Basically the same technique must be used for static or non-static methods.
I am running psysh in a command shell in windows 10, xampp (php 5.6), composer (current) installed.
Any suggestions for what I am doing wrong or need to do differently?
The trouble here is that you're not following PSR-4. With the config you provided, it's expecting to find classes in the System namespace inside your phpclasses folder. So, for example, the file Test.php would have the class System\Test.
To just fix it, either change the prefix in your autoload settings to "", or add namespace System; to your Test.php file. If you want to understand why it's acting like it is, you have to understand a bit about how autoloading works in PHP:
PHP lets you register an autoloader to find classes which haven't been encountered yet. The autoloader is handed a class name, and given a chance to find it. Usually they work by mapping class names to files in some way. When they're asked for an unknown class, they translate the class name to a file name, and try to require the file.
PSR-4 is a standard for setting up such an autoloader, and Composer comes with a PSR-4 compliant autoloader for free. For it to work right, you have to lay out your classes and namespaces like PSR-4 expects. If you don't, you can run into strange issues like you're encountering.
When you first tried calling Test::hello(), the class wasn't defined. Your PSR-4 autoloader translated that to a file name, but per your config, there's nowhere defined for non-namespaced classes to live, so it couldn't find a file to load, and it ended up loading nothing. After the autoloader had a chance, PHP still didn't know about that class, so it threw an error.
When you tried calling System\Test::hello(), your PSR-4 autoloader looked it up in the config and translated it to a filename (phpclasses/Test.php), which did exist this time, so it loaded that file. PHP then tried calling the method, but it didn't know about that class, so it threw an error.
The third time, it had already loaded your file and discovered the non-namespaced Test class. So when you tried calling it again, it didn't even bother with the autoloader, and just executed your method.
I have a problem with composer auto loader. Currently working on a application which was developed about 10 years back. The folder structure of 2 libraries which are currently used in the project do not comply the psr0 and psr4 auto loading rules.
The structure after the composer install looks as follows
Example 1
Folder path: /vendor/AppBook/ORS/class/model/Country
Filename: class.Country.php
PHP Class name: Country
Example 2
Folder path: /vendor/AppBook/ORS/class/model/Country
Filename: class.CountryCollection.php
PHP Class name: CountryCollection
Please advise what should I do in order that composer auto loader can detect these files.
Thank you in advance
From the docs:
You can use the classmap generation support to define autoloading for all libraries that do not follow PSR-0/4. To configure this you specify all directories or files to search for classes.
Example:
{
"autoload": {
"classmap": ["src/", "lib/", "Something.php"]
}
}
You can still add composer.json to your legacy libraries and define classmap autoloading type for them.
You can rename these files to make them compatible to PSR-4 (unlikely because that requires using namespaces - in 10 years old code?) or PSR-0. Additionally, you have to remove any explicit loading of these files via include, include_once, require or require_once because the file names changed.
PHP will autoload these classes by their class name. This will possibly run into issues if the case sensitivity in the class name is not respected everywhere. Example:
class UpperCase {}
$a = new upperCase();
The autoloading would try to find a file ending with upperCase.php, which will not match the PSR-0 required UpperCase.php, so the code will fail. However, this will work, making the situation not better:
class UpperCase {}
$b = new UpperCase();
$a = new upperCase();
The reason is that PHP treats class names case insensitive, so once a class is loaded, you can use any case in it's name. It is only the first occurrence in your code path that has to match. The problem is to be sure where this really is, so essentially it has to be correct everywhere.
Yes, the classmap feature is the easier way. But you'd still want to remove include/require calls to optimize the performance a bit, so you have to touch the code anyway. And despite it's age, it has to be maintained - so why not do it fully, switching to a well-known autoloading standard. It will help you in the long run when you have to maintain PSR-0/4 compatible classes and this old code in parallel.
I've added the following to my composer.json file. This works fine but I have a long list of sub-namespaces (eg. Apple, Orange, Lemon, Pear, Banana... etc) that I want to include.
1) Do I have to indicate each sub-namespace or is there a shortcut eg. "Pure\\*": "pure"
composer.json:
"autoload": {
"psr-4": {
"Pure\\": "pure",
"Pure\\Apple\\": "pure/src/Pure/Apple",
"Pure\\Orange\\": "pure/src/Pure/Orange",
"Pure\\Lemon\\": "pure/src/Pure/Lemon"
}
}
2) Is it better to include a custom autoload file instead:
composer.json:
"autoload": {
"files": [
"pure/src/Pure/autoload.php"
]
}
autoload.php:
spl_autoload_register(function ($class) {
//etc...
}
Do I have to indicate each sub-namespace or is there a shortcut
When declaring the autoloading, you should use the longest possible or reasonable prefix.
If this example package is the only one you are ever creating, and it is the only one using Pure as the namespace, go with that if the number of longer prefixes in several subdirectories is too high. However, this assumes that any other package in the world that you are using should avoid doing the same thing with the same namespace.
Composer will be able to search for sub namespaces in all available directories, i.e. if you have two packages, and one says Pure is to be found in pure/src/Pure, and the other says Pure is in code/stuff, Composer will try to find a class Pure\Something\Class in one of these directories first, then has to try the second one if it does not find it. Composer will remember whether the pure/src/Pure/Something directory exists and avoid looking for anything starting with Pure\Something there if a second class in that namespace has to be loaded.
But from a code organization standpoint, one package should only ever provide a defined set of namespaces, and no other package should provide classes in the same namespace. You could accidentially add the same class into two packages and get interesting problems that may be hard to debug if the two files are different.
Is it better to include a custom autoload file instead:
No, avoid it at all cost. You won't get any benefit from this because the file always has to be loaded, and it is duplicate code that occupies some memory - you already have the autoloader from Composer. If you have code that does not conform to PSR-4 or PSR-0, you can use a classmap. For new code: Use PSR-4 only!
Also, your custom autoloader cannot be optimized by Composer. Although any optimization should be measured for effectiveness (read my detailed answer on this: Why use a PSR-0 or PSR-4 autoload in composer if classmap is actually faster?), using your own autoloader will entirely prevent your code from being optimized if it would be beneficial.
I've added the following to my composer.json file. This works fine but I have a long list of sub-namespaces (eg. Apple, Orange, Lemon, Pear, Banana... etc) that I want to include.
1) Do I have to indicate each sub-namespace or is there a shortcut eg. "Pure\\*": "pure"
composer.json:
"autoload": {
"psr-4": {
"Pure\\": "pure",
"Pure\\Apple\\": "pure/src/Pure/Apple",
"Pure\\Orange\\": "pure/src/Pure/Orange",
"Pure\\Lemon\\": "pure/src/Pure/Lemon"
}
}
2) Is it better to include a custom autoload file instead:
composer.json:
"autoload": {
"files": [
"pure/src/Pure/autoload.php"
]
}
autoload.php:
spl_autoload_register(function ($class) {
//etc...
}
Do I have to indicate each sub-namespace or is there a shortcut
When declaring the autoloading, you should use the longest possible or reasonable prefix.
If this example package is the only one you are ever creating, and it is the only one using Pure as the namespace, go with that if the number of longer prefixes in several subdirectories is too high. However, this assumes that any other package in the world that you are using should avoid doing the same thing with the same namespace.
Composer will be able to search for sub namespaces in all available directories, i.e. if you have two packages, and one says Pure is to be found in pure/src/Pure, and the other says Pure is in code/stuff, Composer will try to find a class Pure\Something\Class in one of these directories first, then has to try the second one if it does not find it. Composer will remember whether the pure/src/Pure/Something directory exists and avoid looking for anything starting with Pure\Something there if a second class in that namespace has to be loaded.
But from a code organization standpoint, one package should only ever provide a defined set of namespaces, and no other package should provide classes in the same namespace. You could accidentially add the same class into two packages and get interesting problems that may be hard to debug if the two files are different.
Is it better to include a custom autoload file instead:
No, avoid it at all cost. You won't get any benefit from this because the file always has to be loaded, and it is duplicate code that occupies some memory - you already have the autoloader from Composer. If you have code that does not conform to PSR-4 or PSR-0, you can use a classmap. For new code: Use PSR-4 only!
Also, your custom autoloader cannot be optimized by Composer. Although any optimization should be measured for effectiveness (read my detailed answer on this: Why use a PSR-0 or PSR-4 autoload in composer if classmap is actually faster?), using your own autoloader will entirely prevent your code from being optimized if it would be beneficial.
I have a set of PHP functions that I find useful. I want to create a PSR-4 compliant repository for them, but the guides I have found (1,2,3) seem to talk only about classes for autoloading.
For instance, my files are as follows, with one function per file:
my_cool_function1.php
my_cool_function2.php
... etc.
How can I create a PSR-4 compliant library from them?
The reason you're not able to find any documentation for PSR-4 autoloading files which aren't classes, that's because as the specification states - it's designed for autoloading classes.
Taken directly from the official specs:
This PSR describes a specification for autoloading classes from file paths. It is fully interoperable, and can be used in addition to any other autoloading specification, including PSR-0. This PSR also describes where to place files that will be autoloaded according to the specification.
More specifically;
The term "class" refers to classes, interfaces, traits, and other similar structures.
A file with functions isn't really a similar structure.
To autoload those files, you'll need to autoload using files:
"autoload": {
"files": [
"src/my_cool_function1.php",
"src/my_cool_function2.php"
],
"psr-4": {
"SomeNamespace\\": "src/YourNamespace/"
}
}
You'll notice from this, that the psr-4 spec maps (usually) to a namespace.
Don't forget you can use static functions in classes so that PSR-4 will load them
class MyClass {
public static my_cool_function1() {}
}
Then you can invoke them as just a normal function using the colon operator:
MyClass::my_cool_function1() {}