1

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:

  • Calling composer dump-autoload -o (greatly slows deployment, as this is a large project, and so far doesn't seem to fix it anyway)
  • Deleting via rm vendor/composer/autoload_classmap.php, rm vendor/composer/autoload_psr4.php and/or rm vendor/composer/autoload_namespaces.php (or similar) in the vendor folder before each deployment to force Composer to rebuild.
  • Deleting via rm -rf vendor

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

Zack Morris
  • 4,727
  • 2
  • 55
  • 83
  • The above should work. And as you say, it works for you on dev. So it works. What we don't know is why it doesn't work after you deploy, but there are many details there we do not know we would be guessing. – yivi Dec 21 '21 at 06:50
  • A couple of points: doing `composer dump-outoload -o` is par the course when deploying. You **should** be doing this when deploying a new version of your application, always. The other options are less rational, but since in my deplyoments (and in most **good** deployment strategies I can think of), you'd be rebuilding `vendor` completely on deployment.... are a bit superfluous. Again, since we do not know about your deployment strategey, just guessing – yivi Dec 21 '21 at 06:53
  • Particularly if you are using docker containers: then your `vendor` directory should never be "stale", since on each deployment you are building a new image which should include going through `composer install`. – yivi Dec 21 '21 at 06:58
  • And for your ease of mind, yes, "this minimal sample project deployed to Docker works for us", completely. There is noting even remotely weird about it. – yivi Dec 21 '21 at 06:58
  • "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." This is not by any means an unusual "inheritance permutation" and I can guarantee you it's used hundreds of times in the Laravel base code. "stale data in its vendor folder" you should not have a pre-existing `vendor` directory in your deployment target at all. Your `composer.lock` should be part of your VCS and `composer install` run during deployment. What is your dev environment vs your live? – miken32 Dec 21 '21 at 18:36
  • @yivi and miken32 (huh, can't @ more than one person?!) thanks for your quick replies, the problem turned out to be due to using a case-sensitivity filesystem on AWS, and I agree with your assessments. Sorry, when I said "bug in the tools", I meant that a caching bug might affect a codepath involving traits more than classes because they're used less often. You're right that there are rarely bugs in package managers and similar stuff like compilers, which are used by so many people. – Zack Morris Dec 21 '21 at 20:43

2 Answers2

2

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).

Dharman
  • 30,962
  • 25
  • 85
  • 135
Zack Morris
  • 4,727
  • 2
  • 55
  • 83
0

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.

DoubleM
  • 123
  • 8