I have created a class called Provider
class Provider
{
private $provider;
public function __construct(ProviderInterface $provider)
{
$this->provider = $provider;
}
}
In the factory class, I have this:
class ProviderFactory
{
public static function adapt(string $provider)
{
switch ($provider) {
case 'ProviderOne':
return app(Provider::class, [app(ProviderOne::class)]);
case 'ProviderTwo':
return app(Provider::class, [app(ProviderTwo::class)]);
}
}
}
When calling ProviderFactory::adapt($request->provider) I get an error:
Target [App\Services\ProviderInterface] is not instantiable while building [App\Services\Provider].
If I understand correctly I need bind() logic as it needs to know what to bind to the ProviderInterface. Do I need add bind() logic in the service provider?
Add below to the register function of your service provider I am assuming that your interface has the namespace as below
$this->app->bind('App\Services\ProviderInterface');
Shouldn't $provider be public?
class Provider
{
public $provider;
public function __construct(ProviderInterface $provider)
{
$this->provider = $provider;
}
}
Related
I am implementing the Repository Pattern (service) in a Laravel application and I have some doubts about the usage of interfaces with these services.
I have created an interface called CRUD (code bellow) to serve as a way to always keep the same names for the services that are going to implement CRUD methods.
<?php
namespace App\Interfaces;
interface CRUD
{
public function create(array $data);
public function update(int $id, array $data);
public function delete(string $ids);
};
Bellow there's an example of how I call my service and the service itself, and that's where my doubts are. Usually I'll see people witing an interface for each service and demanding the controller to have injected an objet of that type. Because of that, people will have to bind a specific type (interface) to the controller. It seems redundant and thus I simply passed the service I need.
Now, is this ok or I should pass the CRUD interface to the controller in this case? Or should I even create another interface specifically for each service?
<?php
namespace App\Http\Controllers\Cms;
use App\Http\Controllers\Controller;
use App\Http\Requests\GroupRequest;
use App\Models\Group;
use App\Services\GroupsService;
use Illuminate\Http\Request;
class GroupsController extends Controller
{
private $service;
public function __construct(GroupsService $service)
{
$this->service = $service;
}
public function store(GroupRequest $request)
{
$result = $this->service->create($request->all());
return redirect()->back()->with('response', $result);
}
public function update(GroupRequest $request, $id)
{
$result = $this->service->update($id, $request->all());
return redirect()->back()->with('response', $result);
}
public function destroy($groups_id)
{
$result = $this->service->delete($groups_id);
return redirect()->back()->with('response', $result);
}
}
<?php
namespace App\Services;
use App\Models\Group;
use App\Interfaces\CRUD;
use Exception;
class GroupsService implements CRUD
{
public function listAll()
{
return Group::all();
}
public function create(array $data)
{
$modules_id = array_pop($data);
$group = Group::create($data);
$group->modules()->attach($modules_id);
return cms_response(trans('cms.groups.success_create'));
}
public function update(int $id, array $data)
{
try {
$modules_ids = $data['modules'];
unset($data['modules']);
$group = $this->__findOrFail($id);
$group->update($data);
$group->modules()->sync($modules_ids);
return cms_response(trans('cms.groups.success_update'));
} catch (\Throwable $th) {
return cms_response($th->getMessage(), false, 400);
}
}
public function delete(string $ids)
{
Group::whereIn('id', json_decode($ids))->delete();
return cms_response(trans('cms.groups.success_delete'));
}
private function __findOrFail(int $id)
{
$group = Group::find($id);
if ($group instanceof Group) {
return $group;
}
throw new Exception(trans('cms.groups.error_not_found'));
}
}
If you want to use Repository Design Patteren You have to create seprate Interface for each service accroing to SOLID Principle. You have to create custom service provider and register your interface and service class and then inject interface in construtor of controller.
You can also follow below article.
https://itnext.io/repository-design-pattern-done-right-in-laravel-d177b5fa75d4
I did something with repo pattern in laravel 8 you might be interested:
thats how i did it:
first of all, you need to implement a provider
in this file i created the binding:
App\ProvidersRepositoryServiceProvider.php
use App\Interfaces\EventStreamRepositoryInterface;
use App\Repositories\EventStreamRepository;
use Illuminate\Support\ServiceProvider;
class RepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind(EventStreamRepositoryInterface::class, EventStreamRepository::class);
}
}
then in file:
app\Interfaces\EventStreamRepositoryInterface.php
interface EventStreamRepositoryInterface {
public function index();
public function create( Request $request );
public function delete($id);
}
in file:
App\Repositories\EventStreamRepository.php
class EventStreamRepository implements EventStreamRepositoryInterface{
public function index()
{
return EventStream::with(['sessions'])
->where([ ["status", "=", 1] ] )
->orderBy('created_at', 'DESC')
->get();
}
public function create(Request $request)
{
request()->validate([
"data1" => "required",
"data2" => "required"
]);
$EventStream = EventStream::create([
'data1' => request("data1"),
'data2' => request('data2')
]);
return $EventStream->id;
}
public function delete($id)
{
return EventStream::where('id', $id)->delete();
}
}
in file:
App\Http\Controllers\EventStreamController.php
use App\Interfaces\EventStreamRepositoryInterface;
class EventStreamController extends Controller{
private EventStreamRepositoryInterface $eventStreamRepository;
public function __construct(EventStreamRepositoryInterface $eventStreamRepository)
{
$this->eventStreamRepository = $eventStreamRepository;
}
public function index():JsonResponse
{
$this->eventStreamRepository->index();
}
public function store(Request $request ):JsonResponse
{
$this->eventStreamRepository->create($request);
}
public function destroy($id):JsonResponse
{
$this->eventStreamRepository->delete($id);
}
}//class
note: i think i removed all unnecessary -validations- and -returns- in controller for better reading.
Hope it helps!!
I've created a Service Provider with a class that has a model passed into the constructor.
The model needs to be a specific record based off the $id taking from the URL eg /path/{$id}
How can I use the requested model in the Service Provider?
An option is to pass the model into the execute method but for now I'll need to pass it into the construct.
MyController
class MyController {
public function show(MyClass $myClass, $id)
{
$model = MyModel::find($id);
return $myClass->execute();
}
}
MyClass
class MyClass
{
$private $myModel;
public function __construct(MyModel $myModel)
{
$this->myModel = $myModel;
}
public function execute()
{
//do something fun with $this->myModel
return $theFunStuff;
}
}
MyServiceProvider
public function register()
{
$this->app->singleton(MyClass::class, function ($app) {
return new MyClass(/* How can I use $myModel? */);
});
}
I don't see any value / reason to use a singleton here.
The service provider registers the singleton before your route is resolved, so there is no way to pass the $model from the controller into the register method. I would remove the service provider and do the following:
From the docs:
If some of your class' dependencies are not resolvable via the
container, you may inject them by passing them as an associative array
into the makeWith method:
$api = $this->app->makeWith('HelpSpot\API', ['id' => 1]);
So in your case something like this:
public function show($id)
{
return app()->makeWith(MyClass::class, ['myModel' => MyModel::find($id)])->execute();
}
Or shorter with the help of route model binding:
public function show(MyModel $myModel)
{
return app()->makeWith(MyClass::class, compact('myModel'))->execute();
}
Note that the argument names passed to makeWith have to match the parameter names in the class constructor.
I have next entities: Event, Ticket. I want to get some ticket information for latest event using different data sources.
So in my controller I use DI:
private $latestEventService;
public function __construct()
{
$this->latestEventService= new LatestEventService();
}
and inside methods:
$this->latestEventService->getTicketsAmount();
app/Services/LatestEventService:
private $provider;
public function __construct($provider = 'db')
{
$this->provider = new config('services.latest_event_providers')[$provider]();
}
public function getTicketsAmount()
{
return $this->provider->getTicketsAmount();
}
public function getFreeTicketsAmount()
{
return $this->provider->getFreeTicketsAmount();
}
public function getDiscountedTicketsAmount()
{
return $this->provider->getDiscountedTicketsAmount();
}
config/services:
'latest_event_providers' => [
'db' => '\App\Providers\LatestEvent\DBProvider',
'api'=> '\App\Providers\LatestEvent\APIProvider'
]
DBProvider:
class DBProvider extends ServiceProvider implements ILatestEventProvider
{
public function getTicketsAmount()
{
//fetch from db
}
public function getFreeTicketsAmount()
{
//fetch from db
}
public function getDiscountedTicketsAmount()
{
//fetch from db
}
}
app/Interfaces/ILatestEventProvider:
interface ILatestEventProvider
{
public function getTicketsAmount();
public function getFreeTicketsAmount();
public function getDiscountedTicketsAmount();
}
Can I leave this as it is? I'm worried about DBProvider, it's not familiar to other "default" Laravel providers (there is no "default" boot() and register() methods).
Service provider in Laravel is a place where you bootstrap your application. DBProvider isn't service provider it's just tickets calculator for latest event(just service)
Right DI in your controller will be
private $latestEvent;
public function __construct(LatestEventFactory $latestEventFactory, $provider)
{
$this->latestEvent = $latestEventFactory->createInstance($provider);
}
For creating LastEvent service we use Factory design pattern
class LastEventFactory
{
public function createInstance($provider): ILatestEvent
{
return new config('services.latest_event_providers')[$provider]();
}
}
Next, you should create two classes which implement ILatestEvent (DBLastEvent and ApiLastEvent)
interface ILatestEvent
{
public function getTicketsAmount();
public function getFreeTicketsAmount();
public function getDiscountedTicketsAmount();
}
In this case, you don't need service provider because Laravel service container smart enough to resolve LatestEventFactory without service provider
I would like have access to controller methods from my custom service. I created class MyManager and I need to call inside it createForm() and generateUrl() functions. In controller I can use: $this->createForm(...) and $this->generateUrl(...), but what with service? It is possible? I really need this methods! What arguments I should use?
If you look to those two methods in Symfony\Bundle\FrameworkBundle\Controller\Controller class, you will see services name and how to use them.
public function generateUrl($route, $parameters = array(), $referenceType = UrlGeneratorInterface::ABSOLUTE_PATH)
{
return $this->container->get('router')->generate($route, $parameters, $referenceType);
}
public function createForm($type, $data = null, array $options = array())
{
return $this->container->get('form.factory')->create($type, $data, $options);
}
Basically, you class need services router and form.factory for implementing functionality. I do not recommend passing controller to your class. Controllers are special classes that are used mainly by framework itself. If you plan to use your class as service, just create it.
services:
my_manager:
class: Something\MyManager
arguments: [#router, #form.factory]
Create a constructor with two arguments for services and implement required methods in your class.
class MyManager
{
private $router;
private $formFactory;
public function __construct($router, $formFactory)
{
$this->router = $router;
$this->formFactory = $formFactory;
}
// example method - same as in controller
public function createForm($type, $data = null, array $options = array())
{
return $this->formFactory->create($type, $data, $options);
}
// the rest of you class ...
}
assuming you are injecting the service into your controller , you can pass the controller object to your service function
example
class myService
{
public function doSomthing($controller,$otherArgs)
{
$controller->generateForm();
}
}
class Mycontroller extends Controller
{
public function indexAction()
{
$this->get("my-service")->doSomthing($this,"hello");
}
}
In my Zend Framework 2 project, I have an external lib and I want to save my information in the base with the model.
....
....
....
EDITED MESSAGE :
I explain again my need: In my controllers, I make insertions and deletions in the database and I want to log all actions in a "t_log" table . To do it, I have thought to create an external class.
My question is: How I can call my models method from my external class ?
namespace Mynamespace;
use Firewall\Model\Logs;
use Firewall\Model\LogsTable;
class StockLog
{
public function addLog()
{
$log = $this->getServiceLocator()->get('Firewall\Model\LogTable');
$log->save('user added');
die('OK');
}
}
My model :
namespace Firewall\Model;
use Zend\Db\TableGateway\TableGateway;
use Zend\Db\Sql\Select;
class UserGroupTable
{
protected $tableGateway;
public function __construct(TableGateway $tableGateway)
{
$this->tableGateway = $tableGateway;
}
public function save()
{
// How I Can call this method from the StockLog method ?
}
}
Thanks you !
getServiceLocator is a function of \Zend\Mvc\Controller\AbstractActionController so it is supposed to be used in your controllers.
I dont know what your StockLog class is, but it is not extending any other class, so i guess it has not that function and your error is one step before, in the call to getSErviceLocator that is not defined, so its not returning an object.
Probably you can inject the service locator with something like
class StockLog
{
private $serviceLocator= null;
public function setServiceLocator(ServiceLocatorInterface $serviceLocator)
{
$this->serviceLocator = $serviceLocator;
}
public function add()
{
# Do you know how I can call the service ??
$User = $this->serviceLocator->get('Firewall\Model\UserTable');
}
}
and then, when you create your StockLog object, in your controller, you inject the servicelocator
public class yourController extends AbstractActionController {
public function yourAction(){
$mStockLog = new StockLog ();
$mStockLog->setServiceLocator($this->getServiceLocator());
/...
}
}
Also, if you only need the 'Firewall\Model\UserTable' service, you should inject just that, instead of the serviceLocator.
At any rate you should minimice the knowledge of your model classes about the rest of the system, hving always in mind the dependency inversion principle, to get a better decoupling
UPDATE
inject the log table
namespace Mynamespace;
use Firewall\Model\Logs; use Firewall\Model\LogsTable;
class StockLog {
private $logTable= null;
public function setLogTable($logTable)
{
$this->logTable= $logTable;
}
public function addLog()
{
$this->logTable->save('user added');
die('OK');
}
}
and then, when you create your StockLog (in your controller, or wherever you do it, before you use it) you inject the logtable object
$mStockLog = new StockLog ();
$mStockLog->setLogTable($this->getServiceLocator()->get('Firewall\Model\LogTable'));
Of course, Im suposing that you configured correctly your Firewall\Model\LogTable class to be retrieved by means of the service manager, in getServiceConfig() in your Module.php
public function getServiceConfig() {
return array (
'factories' => array (
'Firewall\Model\LogTable' => function ($sm) {
$logTable = //create it as you use to
return $logTable;
}
)
}