Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

  • Register in Laravel
  • Login in Laravel
  • Reset my password
  • Update my personal details
  • Enter payment information in order to subscribe to a Stripe subscription
  • View and modify my Stripe subscription
  • See all my customer and subscription information on Stripe.com
  • Edit my information on Stripe.com and see that updated via the webhook on my servers

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

like image 770
Chuck Le Butt Avatar asked Mar 15 '21 18:03

Chuck Le Butt


Video Answer


2 Answers

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!

like image 175
Chuck Le Butt Avatar answered Oct 28 '22 05:10

Chuck Le Butt


PHP may not report duplicate classes, while there are no duplicate classes.

There are generally three possible pitfalls:

A) The issue with that duplicate class may be caused, well, by a duplicate class.
This can be resolved by finding and removing the offending class file, eg:

find . | grep Customer.php

This should return two results, of which one causes the error message; then run rm filename.

B) It might come from an outdated autoload.php, which can be refreshed by running composer dump-autoload.

C) It may be a namespace thing; eg. use Stripe\Customer as StripeCustomer;


That method does nothing but:

StripeSubscription::retrieve(
    ['id' => $this->stripe_id, 'expand' => $expand], $this->owner->stripeOptions()
 );

And App\Models\Customer likely is the $owner of SubscriptionBuilder.php.

like image 23
Martin Zeitler Avatar answered Oct 28 '22 04:10

Martin Zeitler