0

I have registered a Paypal service provider:

App\Providers\PaypalHelperServiceProvider::class,

and, when I type hint it in my controller it properly resolves:

public function refund(Request $request, PaypalHelper $paypal) {...

Here is my provider class:

class PaypalHelperServiceProvider extends ServiceProvider
{
  protected $defer = true;

  public function register()
  {
      $this->app->bind('App\Helpers\PaypalHelper', function() {
          $test = 'test';
          return new PaypalHelper();
      });
    }

    public function provides()
    {
      $test = 'test';
      return [App\Helpers\PaypalHelper::class];
    }
  }

Everything works as expected. Now I wanted to be able to modify controller to take a PayPal interface. I would then update my service provider to conditionally pass in either the real class or a mock one for testing, using the APP_ENV variable to determine which one to use. I put some debuggers into the service provider class and could not get it to ever go in. I thought perhaps that it only loads them on need, so I put a breakpoint inside my controller. The class did resolve, but it still never went into the service provider class! Can someone explain to me why this is the case? Even when I modified the code to pass in a different class type it did not pick up.

EDIT:

Here is the code flow I see when I debug this: ControllerDispatcher -> resolveClassMethodDependencies -> resolveMethodDependencies -> transformDependency. At this point we have the following laravel code in the RouteDependencyResolveerTrait:

 protected function transformDependency(ReflectionParameter $parameter, $parameters, $originalParameters)
{
    $class = $parameter->getClass();

    // If the parameter has a type-hinted class, we will check to see if it is already in
    // the list of parameters. If it is we will just skip it as it is probably a model
    // binding and we do not want to mess with those; otherwise, we resolve it here.
    if ($class && ! $this->alreadyInParameters($class->name, $parameters)) {
        return $this->container->make($class->name);
    }
}

Since getClass() always resolves to the interface name, when we call container->make(), it always fails with

Target [App\Helpers\PaypalHelperInterface] is not instantiable.
user1015214
  • 2,733
  • 10
  • 36
  • 66
  • in your controller if you instead gets the class with $paypal = app('App\Helpers\PaypalHelper'); does that work? – mrhn Sep 06 '19 at 20:40
  • @mrhn No, that doesn't help. Even when I manually set the class in my helper to return a `Exception` class, for example, it STILL returns the PayPal class. It never seems to go back into my provider class, as if there was some type of caching. – user1015214 Sep 09 '19 at 17:28
  • the class in `provides` is not correct ... namespacing .. that would end up being `App\Providers\App\Helpers\PayPalHelper` – lagbox Oct 02 '19 at 18:25
  • @lagbox - No, I don't think so, I have this statement above `use App\Helpers\PaypalHelperInterface;` – user1015214 Oct 02 '19 at 18:54
  • unless you aliased `App` it is incorrect ... basically there is nothing bound for the interface you are trying to resolve in the code you have above and it is not in the provides array – lagbox Oct 02 '19 at 19:04

2 Answers2

0

Change

      $this->app->bind('App\Helpers\PaypalHelper', function() {
          $test = 'test';
          return new PaypalHelper();
      });

To

if (app()->environment('testing')) {
    $this->app->bind(
        PaypalHelperInterface::class,
        FakePaypalHelper::class
    )
} else {
    $this->app->bind(
        PaypalHelperInterface::class,
        PaypalHelper::class
    );
}
PtrTon
  • 3,705
  • 2
  • 14
  • 24
  • See my comment on the ticket, this doesn't seem to really help, since it doesn't ever seem to re-read my provider class. Even if I manually set the response as a different class, it returns my original PayPalHelper class. – user1015214 Sep 09 '19 at 17:29
  • @user1015214 Remove `protected $defer = true;`. You don't have the defer set up properly. You're telling Laravel to [defer the provider](https://laravel.com/docs/5.8/providers#deferred-providers), but never tell it when to actually refer to it because you don't have the `Provides` method defined. – jfadich Oct 02 '19 at 17:07
  • @jfadich I removed that, it still is not going into my provider class. – user1015214 Oct 02 '19 at 17:16
  • @user1015214 make sure you have the `use` statements setup in your provider or use fully qualified namespaces. – Bryan Oct 02 '19 at 17:19
  • Why are you doing this in the service in the first place instead of using [test mocks](https://laravel.com/docs/6.x/mocking#mocking-objects)? – jfadich Oct 02 '19 at 17:22
  • @jfadich I"m open to suggestions here. But the reason why I didn't go that route is because I"m not trying to create unit tests at the moment. Rather, I'm trying to make sure that QA is able to test the full front end workflow. Currently, because of the way the site is set up with Paypay and SSO, its difficult to just swap out paypay configs with a sandbox one. Therefore, I felt this was the only way to do it. – user1015214 Oct 02 '19 at 17:25
0

I finally found out the issue. My problem was that my provider wasn't being picked up at all. Here was the solution

composer dumpautoload
php artisan cache:clear

https://laravel.io/forum/02-08-2015-laravel-5-service-provider-not-working

user1015214
  • 2,733
  • 10
  • 36
  • 66