How to use composer psr-4 fallback correctly
Asked Answered
S

2

7

I have one directory that is going to keep all "helper" classes and functions. Let's call the directory helpers.

I want to configure PSR-4 fallback directory to point to this helpers directory:

    "autoload": {
          "psr-4": {
                "": "helpers/"
           }
     }

From Composer documentation:

... fallback directory where any namespace will be looked for.

So my understanding is that if my files/classes in that directory have PSR-4 compliant names my application should be able to find them there.

Now I created a file helpers/Logger.php with class Logger

What namespace should be for this class in order to 1) comply with PSR-4 and 2) just work?

I have tried

namespace Logger;

And load class as

$logger = new Logger();

But I receive error Class Logger not found

Deeper dive into composer code (loadClass() method) showed me that it actually finds and includes the file helpers/Logger.php, but the class still cannot be found for some reason.

According to PSR-4 namespace should be something like this:

namespace Helpers;

And class should be called like this:

$logger = new Helpers\Logger();

I have Class Helpers\Logger not found, but in addition the file helpers/Logger.php is not even included.

The logic inside Composer's loadClass() method for fallback is following:

    // PSR-4 lookup
    $logicalPathPsr4 = strtr($class, '\\', DIRECTORY_SEPARATOR) . $ext;

   ........

    // PSR-4 fallback dirs
    foreach ($this->fallbackDirsPsr4 as $dir) {
        if (file_exists($file = $dir . DIRECTORY_SEPARATOR . $logicalPathPsr4)) {
            return $file;
        }
    }

So it actually tries to match the file name against the fully qualified class name.

So it seems that I am missing something in my understanding of PSR-4.

But what exactly?

Edit

To check it all I run a simple file from project root, all other libraries that are configured via composer are loaded correctly (for example, Pimple is working fine):

<?php

require_once __DIR__ . '/vendor/autoload.php';
$app = new \Pimple\Container();

/** Register logger */
$app['logger'] = function ($c) {
    return new Helpers\Logger();
};

$app['logger']->info('test');
Sensor answered 14/2, 2018 at 17:28 Comment(2)
I have to ask because everyone forgets. Did you remember to require your autoload file?Mohandis
Yes, I did: require_once __DIR__ . '/vendor/autoload.php'; But let me edit my question as well to avoid confusion.Sensor
T
3

This fallback works as a definition of directory for global namespace. So yes, it may be used for autoloading any class in any namespace, but you still need to follow PSR-4 rules for them. Directory structure should represent namespaces structure. If you have rules like:

"autoload": {
      "psr-4": {
            "": "helpers/"
       }
 }

your Helpers\Logger class should be in helpers/Helpers/Logger.php, because in this way Composer will resolve class file path from autoloading rules.

helpers/Helpers/Logger.php
   ^       ^      ^
   |       |      |
   |       |    Class name part
   |       |            
   |     Namespace part
   |
 Static prefix for global namespace
Tasso answered 28/4, 2018 at 19:2 Comment(2)
Thanks for the reply, but I am still confused - is it possible to avoid this duplicate Helpers part? So the class Logger is in helpers/Logger.php? What namespace should be set for this class?Sensor
@MaksymBodnar Just don't use fallback, you probably don't need it. Define regular autoloading rules for namespace as "Helpers\\": "helpers/" and put your logger class into helpers/Logger.php.Tasso
S
0

PSR4 is working case-sensitive, so if you place a class in the folder helpers and the class itself uses the namespace Helpers, that won't work.

Salver answered 15/2, 2018 at 8:49 Comment(9)
According to PSR-4 (as I understand it) full path to the class consists of the prefix (which is an empty string in my case), base directory (which is helpers/ in my case) and namespace that translates to the file name/class name (which is Logger). So it should find just Logger.Sensor
Ah, okay, then the autoloader might look for Helpers\Logger in the file helpers/Helpers/Logger.php - but you might find that on your own if you run your script with XDebug enabled to see where exactly the file should be locatedSalver
I think I get this. I am just curious why it is not possible to have helpers/Logger.php with namespace Logger? Or this is not PSR-4 compliant?Sensor
Why shouldn't that be possible? Just put that properly in your composer.json such that it can map the namepace Logger to the folder helpers ;) But this introduces much more configuration than you need if you just match namespaces with the proper foldersSalver
Well, I wanted to use fallback feature - so I have a directory (helpers) where I can put all my helpers classes and don't have to add every single one to composer autoload explicitly. But this seems to be impossible.Sensor
No, it is possible, but you have to use the proper namespaceSalver
Could you give me a hint/advise, please? Because I have tried different combinations and it doesn't work unless I directly specify the prefix in autoload/psr-4 section.Sensor
What about placing the files at the position they belong to, just like I suggested multiple times and just like PSR4 tells you?Salver
I have already done that, just wanted to figure out what is the meaning of this fallback feature or how it is supposed to work.Sensor

© 2022 - 2024 — McMap. All rights reserved.