Cannot call abstract method interface in Service-Repository pattern - php

In my Laravel9 project, I have many controllers that have similar functions like:
TestController.php
namespace App\Http\Controllers\Api;
use App\Contracts\TestInterface;
use App\Http\Controllers\Controller;
class TestController extends Controller
{
public function __construct(
private TestInterface $testService,
private $moduleName = 'Test Name',
) {}
public function index()
{
$tests = $this->testService->index();
$response = response([
'message' => __('Read' . $this->moduleName . 'successfully'),
'data' => $tests,
], 200);
return $response;
}
}
Test1Controller.php
namespace App\Http\Controllers\Api;
use App\Contracts\Test1Interface;
use App\Http\Controllers\Controller;
class Test1Controller extends ApiController
{
public function __construct(
private Test1Interface $test1Service,
private $moduleName = 'Test Name 1',
) {}
public function index()
{
$test1s = $this->test1Service->index();
$response = response([
'message' => __('Read' . $this->moduleName . 'successfully'),
'data' => $test1s,
], 200);
return $response;
}
}
So I modified them into:
TestController.php
namespace App\Http\Controllers\Api;
use App\Contracts\TestInterface;
use App\Http\Controllers\Api\ApiController;
class TestController extends ApiController
{
protected function service()
{
return TestInterface::class;
}
}
Test1Controller.php
namespace App\Http\Controllers\Api;
use App\Contracts\Test1Interface;
use App\Http\Controllers\Api\ApiController;
class Test1Controller extends ApiController
{
protected function service()
{
return Test1Interface::class;
}
}
ApiController.php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
abstract class ApiController extends Controller
{
private $serviceClass;
public function __construct()
{
$this->serviceClass = $this->service();
}
abstract protected function service();
public function index()
{
return $this->serviceClass::index();
}
}
TestInterface.php
namespace App\Contracts;
interface TestInterface extends BaseInterface
{
}
BaseInterface.php
namespace App\Contracts;
interface BaseInterface
{
public function index();
}
Then I bind interfaces into services in AppServiceProvider.php, like:
$this->app->bind('App\Contracts\TestInterface', 'App\Services\TestService');
$this->app->bind('App\Contracts\Test1Interface', 'App\Services\Test1Service');
TestService.php:
namespace App\Services;
use App\Contracts\TestInterface;
use App\Repositories\TestRepository;
class TestService implements TestInterface
{
public function __construct(
private TestRepository $testRepo,
) {}
public function index()
{
return $this->testRepo->index();
}
}
Test1Service.php
namespace App\Services;
use App\Contracts\Test1Interface;
use App\Repositories\Test1Repository;
class Test1Service implements Test1Interface
{
public function __construct(
private Test1Repository $test1Repo,
) {}
public function index()
{
return $this->test1Repo->index();
}
}
When I call TestController::index on my route, I get:
Cannot call abstract method App\Contracts\TestInterface::index()
How can I fix it or do any better suggestions? Thanks.

I just solved the problem by modifying the following line in ApiController.php:
private $serviceClass;
public function __construct(
private Container $app,
)
{
$this->serviceClass = $app->make($this->service());
}

Related

Dependency Injection

I have this code
Controller
<?php
namespace App\Exchange\Helpers;
use App\Contracts\Exchange\Notification;
class Locker
{
protected $notification;
public function __construct(Notification $notification)
{
$this->notification = $notification;
}
public function index()
{
return $this->notification->sendMessage('test');
}
Interface
<?php
namespace App\Contracts\Exchange;
interface Notification
{
public function sendMessage($message);
}
File Kernel.php
namespace App\Providers;
use App\Contracts\Exchange\Notification;
use App\Exchange\Helpers\Notification\Telegram;
use Illuminate\Http\Request;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*
* #return void
*/
public function register()
{
$this->app->bind(Notification::class, function (){
return new Telegram(env('TELEGRAM_EXCHANGE_TOKEN'), env('TELEGRAM_EXCHANGE_CHAT_ID'));
});
}
If I try to use new Locker(); I get a TypeError error: Too few arguments to function App\Exchange\Helpers\Locker::__construct(), 0 passed in Psy Shell code on line 1 and exactly 1 expected
Your controller should extend Illuminate\Routing\Controller in order for dependency injection to work. Or just refactor your __construct method using app helper:
<?php
namespace App\Exchange\Helpers;
use App\Contracts\Exchange\Notification;
class Locker
{
protected $notification;
public function __construct()
{
$this->notification = app(Notification::class);
}
public function index()
{
return $this->notification->sendMessage('test');
}
}

BindingResolutionException and Target is not instantiable

Error:
BindingResolutionException in Container.php line 887:
Target [App\Helpers\Contracts\AccessTokenInterface] is not instantiable.
Route:
Route::get('/', 'LoginController#handleProviderCallback');
Controller:
namespace App\Http\Controllers;
use App\Helpers\Contracts\AccessTokenInterface;
class LoginController extends Controller
{
public function handleProviderCallback(AccessTokenInterface $accessTokenInstance)
{
return $accessTokenInstance->getSomething();
}
}
Provider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Helpers\Contracts\AccessTokenInterface;
use AccessToken;
class TokenServiceProvider extends ServiceProvider
{
protected $defer = true;
public function boot()
{
//
}
public function register()
{
$this->app->bind(AccessTokenInterface::class, function(){
return new AccessToken();
});
}
}
Helper:
namespace App\Helpers;
use App\Helpers\Contracts\AccessTokenInterface;
class AccessToken implements AccessTokenInterface
{
protected $something;
public function setSomething()
{
$something = 100;
}
public function __construct()
{
$this->setSomething();
}
public function getSomething(){
return $something;
}
Interface:
namespace App\Helpers\Contracts;
Interface AccessTokenInterface
{
public function getSomething();
public function setSomething();
}
I have registered Provider in providers and AccessToken in aliases for Helper.
I have read some stackoverflow answers but can't find solution. I am new to this.
Where I am going wrong?
Write in your config/app.php
'providers' => [
...
App\Providers\TokenServiceProvider::class,
...
]
Hope it help you :)

BindingResolutionException and Target is not instantiable

Error:
BindingResolutionException in Container.php line 887:
Target [App\Helpers\Contracts\AccessTokenInterface] is not instantiable.
Route:
Route::get('/', 'LoginController#handleProviderCallback');
Controller:
namespace App\Http\Controllers;
use App\Helpers\Contracts\AccessTokenInterface;
class LoginController extends Controller
{
public function handleProviderCallback(AccessTokenInterface $accessTokenInstance)
{
return $accessTokenInstance->getSomething();
}
}
Provider:
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App\Helpers\Contracts\AccessTokenInterface;
use AccessToken;
class TokenServiceProvider extends ServiceProvider
{
protected $defer = true;
public function boot()
{
//
}
public function register()
{
$this->app->bind('AccessTokenInterface::class', function(){
return new AccessToken();
});
}
}
Helper:
namespace App\Helpers;
use App\Helpers\Contracts\AccessTokenInterface;
class AccessToken implements AccessTokenInterface
{
protected $something;
public function setSomething()
{
$something = 100;
}
public function __construct()
{
$this->setSomething();
}
public function getSomething(){
return $something;
}
Interface:
namespace App\Helpers\Contracts;
Interface AccessTokenInterface
{
public function getSomething();
public function setSomething();
}
I have registered Provider in providers and AccessToken in aliases for Helper.
I have read some stackoverflow answers but can't find solution. I am new to this.
Where I am going wrong?
Remove the quotes from the bind method in your provider:
$this->app->bind(AccessTokenInterface::class, function(){
return new AccessToken();
});

Symfony: Set response in base controller

<?php
abstract class BaseController extends Controller {
public function setContainer(ContainerInterface $container) {
parent::setContainer($container);
$this->containerInitialized();
}
protected function containerInitialized() {
if($this->getUser()->getNickName()===null){
// I need to directly returns a Response
return new Response('...');
}
}
}
class UserHomeController extends BaseController{
public function indexAction(){
}
}
I need to directly returns a Response on 'containerInitialized' function, not need to call Action's function!
My current practices is throw 'SetResponseException', and listen 'onKernelException' event, when Exception is instance of 'SetResponseException', just replace Response content! But I think a little farfetched!
What you really want is use an event listener.
<?php
namespace AppBundle\EventListener;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterControllerEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Security\Core\Authentication\Token\Storage\TokenStorageInterface;
use Symfony\Component\HttpFoundation\Response;
use AppBundle\Controller\BaseController;
class BaseControllerListener implements EventSubscriberInterface
{
private $tokenStorage;
public function __construct(TokenStorageInterface $token_storage)
{
$this->tokenStorage = $tokenStorage;
}
public function onKernelController(FilterControllerEvent $event)
{
if (!$this->isBaseController($event->getController())) {
return;
}
if ($this->tokenStorage->getToken()->getUser()->getNickName() === null) {
$event->setResponse(new Response('...'));
}
}
protected function isBaseController($controller)
{
if (!is_array($controller)) {
return;
}
return $controller[0] instanceof BaseController;
}
public static function getSubscribedEvents()
{
return array(
KernelEvents::CONTROLLER => 'onKernelController',
);
}
}
then add this to you configuration
services:
app.base_controller_subscriber:
class: AppBundle\EventListener\BaseControllerListener
arguments:
- "#security.token_storage"
tags:
- { name: kernel.event_subscriber }
now you can just do this:
namespace AppBundle\Controller;
// ...
abstract class BaseController extends Controller
{
// other stuff
}
namespace AppBundle\Controller;
// ...
class UserHomeController extends BaseController
{
public function indexAction()
{
}
}
for each controller that will extends BaseController, the BaseControllerListener::onKernelController method will be executed.

Laravel 5 repository injection

I'm quite new in Laravel 5, what I am trying to do is a simple repository with dependency injection. But I'm stuck with this error:
Argument 1 passed to
App\Http\Controllers\Api\UserController::__construct() must implement
interface App\Repositories\UserInterface, instance of
App\Repositories\UserRepository given
Here is my code:
UserController:
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Response;
use App;
use Auth;
use Crypt;
use Lang;
use Image;
use Storage;
use Config;
use Validator;
use App\User;
use App\Repositories\UserInterface;
class UserController extends Controller
{
protected $config;
protected $users;
public function __construct(UserInterface $users)
{
$this->middleware('api');
$this->middleware('auth', ['except' => 'getInfo']);
$this->users = $users;
$this->config = Config::get('images.avatar');
}
UserInterface:
namespace App\Repositories;
use App\Repositories\BaseInterface;
interface UserInterface extends BaseInterface
{
};
BaseInterface:
namespace App\Repositories;
interface BaseInterface
{
public function all();
public function paginate($count);
public function find($id);
}
BaseRepository
namespace App\Repositories;
use App\Repositories\BaseInterface;
class BaseRepository implements BaseInterface
{
protected $model;
public function __call($name, $args)
{
// $this->getNewInstance()->{$name($args)};
return call_user_func_array([
$this->getNewInstance(),
$method], $args);
}
public function all($relations = [])
{
$instance = $this->getNewInstance();
return $instance->with($relations)->all();
}
public function find($id, $relations = [])
{
$instance = $this->getNewInstance();
return $instance->with($relations)->find($id);
}
public function findOrFail($id, $relations = [])
{
$instance = $this->getNewInstance();
return $instance->with($relations)->findOrFail($id);
}
public function paginate($count)
{
}
protected function getNewInstance()
{
return new $this->model;
}
}
UserRepository
namespace App\Repositories;
use App\Repositories\BaseRepository;
Class UserRepository extends BaseRepository
{
protected $model = 'App\User';
}
RepositoryServiceProvider
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
use App;
class RepositoryServiceProvider extends ServiceProvider
{
/**
* Register any error handlers.
*
* #return void
*/
public function boot()
{
}
/**
* Register the service provider.
*
* #return void
*/
public function register()
{
//
App::bind('App\Repositories\UserInterface', 'App\Repositories\UserRepository');
}
}
Of course RepositoryServiceProvider is added under service providers in my config/app.php
Please help, I'm almost sure that I've tried everything whatever I found in Google.
Your UserRepository has to implement UserInterface:
namespace App\Repositories;
use App\Repositories\BaseRepository;
class UserRepository extends BaseRepository implements UserInterface
// ^^^^^^^^^^^^^^^^^^^^^^^^
{
protected $model = 'App\User';
}

Categories