phplaravelstripe-paymentslaravel-cashier

Laravel: Cannot declare class App\Models\Customer, because the name is already in use


I'm a bit perplexed. I have a simple method on my User (actually "Customer") model to return a user's subscription renewal date:

public function subscriptionRenewalDate() : string
{
    $subscription = $this->subscriptions()->active()->first()->asStripeSubscription();

    return Carbon::createFromTimeStamp($subscription->current_period_end)->format('F jS, Y');
}

I call this method on the authenticated user from a blade template ({{ auth()->user()->subscriptionRenewalDate() }}) and it works fine locally, but as soon as I upload it to the remote staging server it fails with the error:

Cannot declare class App\Models\Customer, because the name is already in use

It points to this line in the Customer model:

class Customer extends Authenticatable
{

What's weird is that it's not just the remote staging server, it's also Travis CI where it fails (when running PHPUnit tests -- these same tests work fine locally).

Obviously it seems like some sort of caching or configuration problem, but I cannot understand what would cause this error.

It's not database related because the tests use RefreshDatabase. If I remove the ENV variable (CASHIER_MODEL: App\Models\Customer) it fails with the expected "cannot find model User" error, so it's correctly getting the ENV variables. I've tried clearing my Laravel caches (php artisan optimize:clear) and composer dump-autoload. It's very confusing.

All I know is that the error is caused by asStripeSubscription(). If I remove that from the method, the blade template loads fine (everything works fine).

To be clear, I can successfully (and this is locally, remotely, and in Travis CI):

The only time there's a problem is when asStripeSubscription() appears in the code. And it's only on remote servers. Locally even that works fine.

I've tried moving this call to various models. I've tried calling it from a controller and passing the result to the view. I've even tried rebooting the server and clearing Travis CI's caches. The error remains the same.

What would cause asStripeSubscription() to generate this error? If I could replicate locally I could debug it! It stubbornly insists on working locally perfectly, but failing remotely.

I'm using Laravel 8, PHP 7.3 and Cashier 12.10.

I just cannot fathom what would lead to the Cannot declare class App\Models\Customer, because the name is already in use error.


Stack trace:

From Laravel.log:

[2021-03-22 10:29:23] production.ERROR: Cannot declare class App\Models\Customer, because the name is already in use {"userId":1127215,"exception":"[object] (Symfony\Component\ErrorHandler\Error\FatalError(code: 0): Cannot declare class App\Models\Customer, because the name is already in use at /var/app/current/app/Models/Customer.php:13) [stacktrace]

enter image description here


Solution

  • I found the solution. I wasn't aware of this, but it basically was a caching problem relating to ENV variables, and how I was passing those variables in.

    With our Elastic Beanstalk staging server, I was declaring environment variables through a .config file in the .ebextensions folder. For example:

    option_settings:
      "aws:elasticbeanstalk:application:environment":
         APP_NAME: Membership
         APP_ENV: production
         ...
    

    And this works perfectly well in most cases. You can see the variables in AWS, and most apps will work exactly as expected.

    However in this case I needed to make sure I ran php artisan config:cache as part of the deployment (thanks @DimitriMostrey). And if you do this... it ignores the ENV variables you've passed to Elastic Beanstalk! Huh.

    I've no idea why, because you can easily confirm that your app is able to read env('DB_HOST') etc. but it won't actually be able to use them to connect to your database or whatever.

    And if you don't run config:cache then Cashier doesn't work as expected (giving that utterly confusing error).

    So I moved my production environment variables from the .config file to their own .env file, and then used a command to rename that file to .env during deployment.

    And now everything works as expected.

    With Travis CI it was something similar: I moved important environment variables to .env.travis and then made sure I ran config:cache in .travis.yml.

    And in Travis CI now everything works as expected, too.

    I had no idea that Elastic Beanstalk environment variables were treated differently to those in .env files... but I won't forget in a hurry!