Zack Morris
Zack Morris

Reputation: 4823

Trait not found by Laravel base class using Composer 2 autoloader?

I'm on a Laravel project using new-ish versions of PHP, Laravel and Composer 2, as of this writing. I added a new app/Traits/MyTrait.php file beside several existing trait files but unfortunately Composer absolutely will not detect the new file. I'm getting this error:

Trait 'App\Traits\MyTrait' not found

Similar to:

Laravel Custom Trait Not Found

Here is the general layout of the code:

# app/Traits/MyTrait.php:
<?php

namespace App\Traits;

trait MyTrait {
    // ...
}
# app/Notifications/MyBaseClass.php:
<?php

namespace App\Notifications;

use App\Traits\MyTrait;

class MyBaseClass
{
    use MyTrait;

    // ...
}
# app/Notifications/MyChildClass.php
<?php

namespace App\Notifications;

class MyChildClass extends MyBaseClass
{
    // ...
}

The weird thing is that this code runs fine in my local dev, but no matter what I try, it won't work when deployed to the server while running in a Docker container. I've tried everything I can think of like saving "optimize-autoloader": true in composer.json and running composer dump-autoload -o during deployment, but nothing fixes it:

https://getcomposer.org/doc/articles/autoloader-optimization.md

I'm concerned that this inheritance permutation may not have been tested properly by Composer or Laravel, so this may be a bug in the tools. If worse comes to worse, I'll try these (potentially destructive) workarounds:

The sinister part about this is that we must have full confidence in our deploy process. We can't hack this in our server dev environments by manually deleting stuff like vendor and then have it fail in the production deploy because Composer tripped over stale data in its vendor folder. My gut feeling is that this is exactly what's happening, perhaps due to an upgrade from Composer 1 to Composer 2 or version change or stale cache files from work in recent months.

Even a verification like "this minimal sample project deployed to Docker works for us" would help to narrow this down thanks.

Edit: this is a useful resource on how the Composer autoloader works: https://jinoantony.com/blog/how-composer-autoloads-php-files

Upvotes: 1

Views: 1303

Answers (2)

DoubleM
DoubleM

Reputation: 123

I had the same problem and it was related to my file name. I had put it in lowercase at the beginning, that is: apiResponser.php. I added some changes and renamed my file to ApiResponser.php and sent it to production, but ... oh, oh!

I had the same problem.

The only way it worked for me was, do the git name replacement:

📦 git mv app/Traits/apiResponser.php app/Traits/ApiResponser.php

This way I was able to solve. I understand that you have solved it in another way, however this may help another developer. 🙂

Upvotes: 0

Zack Morris
Zack Morris

Reputation: 4823

The problem turned out to be caused by the container/filesystem on AWS being case-sensitive, but my local dev environment on macOS being case-insensitive.

My original trait (kept secret) ended with URL in its name, but I was including its path as, and using it in the base class as, Url.

So this issue had nothing to do with traits, base classes or Composer. It also didn't require any modification of composer.json or the way we call it during deployment. But I think it's still best practice to have this in composer.json, I use it this way in local dev too currently (good/bad?):

    "config": {
        "optimize-autoloader": true
    },

The real problems here (industry-wide) are:

  • Vague error messages
  • Lack of effort by code to drill down and find actual causes (by attempting to load as case-insensitive and returning a warning when found, for example)
  • Lack of action items for the user (have you checked the case? checked that the file exists? checked file permissions? etc etc, written into the error message itself, with perhaps a link to a support page/forum)

It wasn't convenient to ssh into the server (by design). So to troubleshoot, I temporarily committed this onto my branch:

# app/Http/Controllers/TestController.php
class TestController extends Controller
{
    public function test()
    {
        return response('<pre>' . 
            '# /var/www/html/vendor/composer/autoload_classmap.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_classmap.php') . "\n" .
            '# /var/www/html/vendor/composer/autoload_files.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_files.php') . "\n" .
            '# /var/www/html/vendor/composer/autoload_namespaces.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_namespaces.php') . "\n" .
            '# /var/www/html/vendor/composer/autoload_psr4.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_psr4.php') . "\n" .
            '# /var/www/html/vendor/composer/autoload_real.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_real.php') . "\n" .
            '# /var/www/html/vendor/composer/autoload_static.php' . "\n" . file_get_contents('/var/www/html/vendor/composer/autoload_static.php') . "\n"
        );
    }
}

# routes/api.php
Route::get('/test', 'TestController@test');

Then deployed without merging in GitLab and compared the response to the error in AWS Cloudwatch, which is when the typo jumped out.

Then I removed the temporary commit with:

git reset --soft HEAD^

And force-pushed my branch with:

git push --force-with-lease

So was able to solve this without affecting our CI/CD setup or committing code permanently to the develop or master branches.

I've been doing this for a lot of years, and even suspected a case-sensitivity issue here, but sometimes we're just too close to the problem. If you're knee-deep in code and about to have an anxiety attack, it helps to have another set of eyes review your thought process with you from first principles.

I also need to figure out how to run my local Docker containers as case-sensitive as well, to match the server (since that's the whole point of using Docker containers in the first place).

Upvotes: 2

Related Questions