I have some problem and little misunderstanding Laravel SP (ServiceProvider). I have abstract class Repository and her Interface:
abstract class Repository implements RepositoryInterface {
private $model;
private $parser;
public function __construct() {
$this->model = new $this->model_name();
} }
interface RepositoryInterface {
public function create(array $attributes);
public function update($id, array $attributes);
public function delete($id);
public function all();
public function find($id);
public function filter(array $parameters, $query=null);
public function query(array $parameters, $query=null); }
and some child UserRepository for example:
class UserRepository extends Repository implements UserRepositoryInterface {
protected $model_name = "App\Models\User";
public function __construct() {
parent::__construct();
}
public function activation($user_id) {
return "user";
}
public function deactivation($user_id) {
return "user";
} }
and simple ModelParser class:
class ModelParser {
protected $parameters;
protected $model;
public function __construct($model) {
$this->model = $model;
} }
This work fine, but I would pass ModelParser as DI in my construct of abstract Repository with parameter $model. I dont have idea. How should I do it ?
I use it like this:
class UserController extends Controller {
private $repository;
public function __construct(UserRepository $repository) {
$this->repository = $repository;
} }
Well it's kinda complicated since your ModelParser requires a $model as it's parameter. And because this $model may vary depends on its repository, it will be too complicated if we're trying to resolve it using Laravel service container binding.
There's an easier approach, we can make the ModelParser class's constructor receive an optional $model parameter. Then we can add an additional method to set this $model property like so:
namespace App\Models;
class ModelParser
{
protected $parameters;
protected $model;
// Make $model parameter optional by providing default value.
public function __construct($model = null) {
$this->model = $model;
}
// Add setter method for $model.
public function setModel($model)
{
$this->model = $model;
return $this;
}
}
And now you can inject the ModelParser into your abstract Repository class. Laravel will easily resolve this ModelParser parameter
namespace App\Models;
use App\Models\ModelParser;
use App\Models\RepositoryInterface;
abstract class Repository implements RepositoryInterface
{
private $model;
private $parser;
// Pass ModelParser instance to your constructor!
public function __construct(ModelParser $parser)
{
$this->model = new $this->model_name();
// Set the parser's model property.
$this->parser = $parser->setModel($this->model);
}
// Rest of your code.
}
And if you're extending the abstract Repository class, you still have to pass this ModelParser to the constructor like so:
namespace App\Models;
use App\Models\ModelParser;
use App\Models\UserRepositoryInterface;
class UserRepository extends Repository implements UserRepositoryInterface
{
protected $model_name = "App\Models\User";
public function __construct(ModelParser $parser)
{
parent::__construct($parser);
}
}
Actually, if you're not planning to pass another parameter or perform something else during the class instantiation, you can simply remove the __construct() method from UserRepository and rely on its parent (the abstract Repository).
Hope this help!
Related
I have a Laravel project where I have created an abstract class that several of my jobs will use as they all need to use the same method to find some data to proceed.
In Laravel, the way jobs work is that the constructor takes any values that you trigger the job with and in a handler method, dependencies can be injected, like so:
class SomeJob extends Job implements ShouldQueue
{
public function __construct(array $someData, int $someMoreData)
{
$this->someData = $someData;
$this->someMoreData = $someMoreData;
}
public function handle()
{
// Do something...
}
}
\Queue::pushOn(Queue::getDefaultQueue(), new SomeJob([1, 2, 3], 4));
This means that I cannot just pass dependencies into the abstract class from the extending class' constructor. The only way around it, that I can see, is to have a property on the abstract class and then set it in the handler method of the extended class.
abstract class SomeAbstractClass extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $configOne;
protected $configTwo;
protected $userRepository;
public function __construct()
{
$this->configOne = config('someConfig.valueOne');
$this->configTwo = config('someConfig.valueTwo');
}
public function doSomethingWithUserRepository()
{
return $this->userRepository->doSomething();
}
}
class SomeClass extends SomeAbstractClass
{
public function __construct(array $someData, int $someMoreData)
{
parent::__construct();
$this->someData = $someData;
$this->someMoreData = $someMoreData;
}
public function handle(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
}
This works as intended, but it does not seem like the correct way to do it. It seems a bit hacky even if it works. Is there a way to get around this? This must he a pretty common problem, also outside of Laravel.
Because the defined constructor is used to deliver data in the jobs in Laravel, so in this case, you have to treat handle() as the "constructor" method.
So consider this example:
<?php
abstract class SomeAbstractClass extends Job implements ShouldQueue
{
use InteractsWithQueue, SerializesModels;
protected $configOne;
protected $configTwo;
protected $userRepository;
public function __construct()
{
$this->configOne = config('someConfig.valueOne');
$this->configTwo = config('someConfig.valueTwo');
}
public function handle(UserRepository $userRepository)
{
$this->userRepository = $userRepository;
}
protected function doSomethingWithUserRepository()
{
return $this->userRepository->doSomething();
}
}
class SomeClass extends SomeAbstractClass
{
public function __construct(array $someData, int $someMoreData)
{
parent::__construct();
$this->someData = $someData;
$this->someMoreData = $someMoreData;
}
public function handle(UserRepository $userRepository)
{
parent::handle($userRepository);
// you can do whatever you liiike
$this->doSomethingWithUserRepository();
}
}
Is it OK to use properties/methods from parent classes in trait methods?
This code works, but is it good practice?
class Child extends Base{
use ExampleTrait;
public function __construct(){
parent::__construct();
}
public function someMethod(){
traitMethod();
}
}
trait ExampleTrait{
protected function traitMethod(){
// Uses $this->model from Base class
$this->model->doSomething();
}
}
I don't think it's good practice.
Instead you could have a method to fetch your model object, and have that method as an abstract signature in you trait:
trait ExampleTrait {
abstract protected function _getModel();
protected function traitMethod() {
$this->_getModel()->doSomething();
}
}
class Base {
protected $_model;
protected function _getModel() {
return $this->_model;
}
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod();
}
}
Or pass your model as a parameter to your trait method:
trait ExampleTrait {
protected function traitMethod($model) {
$model->doSomething();
}
}
class Base {
protected $_model;
}
class Child extends Base {
use ExampleTrait;
public function someMethod() {
$this->traitMethod($this->_model);
}
}
Both of these approaches let you utilize your IDE's type hinting.
I have base class like this:
class BaseAPI
{
public function __construct(Client $client, CacheInterface $cache, $apiBaseUri)
{
$this->client = $client;
$this->apiBaseUri = $apiBaseUri;
$this->cache = $cache;
}
}
And then in provider:
class APIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app
->when(BaseAPI::class)
->needs('$apiBaseUri')
->give(env('DEFAULT_API_URI',''));
}
}
That is working.
But when I'm making ancestor:
class GetLocations extends BaseAPI
{
protected $apiMethodName = 'locations';
protected $type = 'get';
}
I'm getting error. Of course I can manually write code for each of ancestors, but there is a lot's of them, so question is: is there any inheritance mechanism for binding?
Something like this:
https://symfony.com/doc/current/service_container/parent_services.html
Did you try to use interface for this?
class BaseAPI implements APIInterface
{
}
class APIServiceProvider extends ServiceProvider
{
public function register()
{
$this->app
->when(APIInterface::class)
->needs('$apiBaseUri')
->give(env('DEFAULT_API_URI',''));
}
}
I'm trying to implement a repository pattern in my zend framework 2 application. I have made a service
<?php
class UserService {
private $userRepository;
public function __construct(IUserRepository $repo) {
$this -> userRepository = $repo;
}
public function createUser($params) {
$this -> userRepository -> create($params);
}
public function findAllUsers() {
return $this -> userRepository -> getAllUsers();
}
}
which has a repository that implements an interface
class UserRepository implements IUserRepository {
public function getAllUsers() {
//return all users
}
public function getUserById($id) {
}
public function getOneUser($params){
}
public function getUsers($params){
}
public function create($params){
}
public function update($params){
}
public function delete($params){
}
}
<?php
interface IUserRepository {
public function getAllUsers();
public function getUserById($id);
public function getOneUser($params);
public function getUsers($params);
public function create($params);
public function update($params);
public function delete($params);
}
In my module.php I make use of dependency injection to determine which repository I inject into a controller
public function getControllerConfig() {
return array('factories' => array(
'My\Controller\Accounts' => function(){
return new AccountsController(new UserRepository());
},
),
);
}
In my controller I pass the repository to my service
class AccountsController extends AbstractActionController {
private $service;
public function __construct(IUserRepository $repo) {
$this->service = new UserService($repo);
}
public function indexAction() {
$all_users = $this->service->findAllUsers();
return new ViewModel(array('users' => $all_users));
}
}
My problem is that I'm using Doctrine as Orm and I want to use the entitymanager in my repositories but I don't know how to do that, any ideas and feedback are appreciated
There are several ways to do this, of course. The typical way you'd do this kind of thing in a ZF2/D2 project would be to start with DoctrineORMModule.
That module exposes Doctrine's EntityManager via the ZF2 Service Manager in a variety of handy ways (you can $sm->get('doctrine.entitymanager.orm_default') to explicitly get the EM instance).
Once you can get your entitymanager from the SM, you write a factory for your repository, and inject the EM.
That said, there's a cleaner way. Doctrine has built-in support for repositories, and you can extend the default implementation.
Your repository would then look like this:
<?php
use Doctrine\ORM\EntityRepository;
class UserRepository extends EntityRepository implements IUserRepository {
public function getAllUsers() {
return $this->findAll();
}
// ...
}
Just remember to add the repository class to the User Entity's metadata. For example, with an annotation:
/**
* #ORM\Entity(repositoryClass="MyDomain\Model\UserRepository")
*/
class User
{
}
I'm quite new to using abstract classes and interfaces in PHP.
I'm trying to initiate a extend of an abstract class, but it won't work. It might be a Laravel specific issue i'm having.
This is the case:
I have an interface
I have an abstract class that implements the interface
I have 'regular' class that extends the abstract class
I try to implement the class
This is the interface:
<?php namespace Collection\Services\Validation;
interface SomeInterface {
public function with(array $input);
public function passes();
public function errors();
}
This is the abstract class:
<?php namespace Collection\Services\Validation;
use Illuminate\Validation\Factory;
abstract class SomeClass implements SomeInterface {
protected $validator;
protected $data = array();
protected $errors = array();
protected $rules = array();
public function __construct(Factory $validator)
{
$this->validator = $validator;
}
public function with(array $data)
{
$this->data = $data;
return $this;
}
public function passes()
{
$validator = $this->validator->make($this->data, $this->rules);
if( $validator->fails() )
{
$this->errors = $validator->messages();
return false;
}
return true;
}
public function errors()
{
return $this->errors;
}
}
This is the "regular" class:
<?php namespace Collection\Services\Validation;
class SomeClassExtender extends SomeClass {
public function sayBye()
{
return 'bye';
}
}
This is the implementation:
<?php
use Collection\Services\Validation\PageFormValidator;
use Collection\Services\Validation\SomeClassExtender;
class PagesController extends BaseController {
protected $someClass;
public function __construct(SomeClassExtender $class)
{
$this->someClass = $class;
}
And then i get this error:
Illuminate \ Container \ BindingResolutionException
Target [Symfony\Component\Translation\TranslatorInterface] is not instantiable.
If i remove the initiation of the Factory class, the error is gone. The Factory class is also just a regular class.
What am i doing wrong here?
I see that you're following Chris Fidao's book. Got the same error as you are.
This is my solution, put this inside global.php
App::bind('Symfony\Component\Translation\TranslatorInterface', function($app) {
return $app['translator'];
});
EDIT:
I think the problem with Factory is that you need to bind the translator interface to $app['translator']. Here's what I found...
If you look at the Factory class, it requires the translator interface -- A quick look into its public __construct in the API:
public function __construct(TranslatorInterface $translator, Container $container = null)
{
$this->container = $container;
$this->translator = $translator;
}
Then if you look at the public function register() in ValidationServiceProvider, you'll find that Laravel binds the TranslatorInterface to $app['translator']:
$validator = new Factory($app['translator'], $app);
Then seems like a service provider to bind $app['translator'] is needed, or we can just bind it in global.php.
I think this is the best working solution, found the same exact problem . Solved it by,
injecting the already bound "validator" object in the Validator facade.
<?php namespace Illuminate\Support\Facades;
/**
* #see \Illuminate\Validation\Factory
*/
class Validator extends Facade {
/**
* Get the registered name of the component.
*
* #return string
*/
protected static function getFacadeAccessor() { return 'validator'; }
}
Instantiate the Factory class with App::make('validator')
Do it this way,when instantiating your SomeClassExtender class.
$someClassExtender = new SomeClassExtender( App::make('validator') );
This article by #PhilipBrown Advanced Validation as a Service for Laravel 4 - http://culttt.com/2014/01/13/advanced-validation-service-laravel-4/