Calling multiple methods of the same class in Laravel - php

i was always curious how does Laravel's eloquent work and how they pass many methods in the same line of code.
Example: auth()->user()->where(...)->get()
In this example they use both methods "where" and "get"
So i tried creating the following repository:
class SkillKeyRepository
{
protected $model;
/**
* #param SkillKey $model
*/
public function __construct(SkillKey $model)
{
$this->model = $model;
}
public function all(array $column = []): Collection
{
return $this->model::all($column);
}
public function where($column, $attribute): SkillKeyRepository
{
$this->model::where($column, $attribute);
return $this;
}
public function get()
{
return $this->model::get();
}
}
After that in my service i tried the following:
class SkillKeyService
{
use ServiceTrait;
protected $repository;
/**
* #param SkillKeyRepository $repository
*/
public function __construct(SkillKeyRepository $repository)
{
$this->repository = $repository;
}
public function get()
{
dd($this->repository->where('key', 'TagName')->get());
}
The correct result should be a collection of one item. But it's returning all the data from the database and its ignoring the where() funcion and going directly to the get() function.

Problem is you use the model, to create a query and then trying to chain more to the query using the original model add newQuery method
class SkillKeyRepository
{
protected $query;
/**
* #param SkillKey $model
*/
public function __construct(SkillKey $model)
{
$this->query = $model->newQuery();
}
public function all(array $column = []): Collection
{
return $this->query::all($column);
}
public function where($column, $attribute): SkillKeyRepository
{
$this->query::where($column, $attribute);
return $this;
}
public function get()
{
return $this->query::get();
}
}

The problem is you are using the model, to create a query and then trying to chain more to the query using the original model, however this won't work because you need the previously generated query.
The easiest way to do what you want is to actually return the query builder object itself:
class SkillKeyRepository
{
protected $model;
/**
* #param SkillKey $model
*/
public function __construct(SkillKey $model)
{
$this->model = $model;
}
public function all(array $column = []): Collection
{
return $this->model->all($column);
}
public function where($column, $attribute): QueryBuilder
{
return $this->model->newQuery()->where($column, $attribute);
}
}
(new SkillKeyRepository(new SkillKey()))->where('a','b')->get(); // Should work
However you should really take a step back and reconsider what you are doing. Laravel already has the query builder object to do exactly this, why are you trying to re-write it?

Related

Make PHPStan understand Laravel Eloquent Builder query()

I am having a hard time making larastan / phpstan understand that query() should be based on Company model and not Eloquent\Model. What am I missing?
<?php
namespace App\Repositories;
use App\Models\Company;
/**
* #extends AbstractBaseRepository<Company>
*/
class CompanyRepository extends AbstractBaseRepository
{
public function __construct()
{
parent::__construct(new Company());
}
public function firstByDomain(string $domain): ?Company
{
return $this->query()
->where('domain', $domain)
->first();
}
}
<?php
namespace App\Repositories;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Model;
/**
* #template TModel of Model
*/
abstract class AbstractBaseRepository
{
/** #var TModel */
protected $model;
/** #param TModel $model */
public function __construct(Model $model)
{
$this->model = $model;
}
public function query(): Builder
{
return $this->model->query();
}
}
And this is causing this error:
Method App\Repositories\CompanyRepository::firstByDomain() should return App\Models\Company|null but returns Illuminate\Database\Eloquent\Model|null.
It seems to me that this is caused by the query() method, returning an Eloquent Builder for Illuminate\Database\Eloquent\Model where I believe it should return an Eloquent Builder for App\Models\Company here.
You need to change the query method in AbstractBaseRepository to something like this:
/** #return Builder<TModel> */
public function query(): Builder
{
return $this->model->query();
}
because Builder class is also generic. Also PHPStan does not check the function/method bodies. So your return type needs to be accurate.

Where put eloquent relationship with Repository Pattern in Laravel

I'm trying to learn and implement the Repository Pattern in my app built with Laravel 5.6.
I have implemented my Controller:
class CompaniesController extends Controller
{
protected $company;
public function __construct(ICompanyRepository $company) {
$this->company = $company;
}
public function index(){
$companies = $this->company->getAllCompanies();
return view('companies::index')->with("companies", $companies);
}
}
Then I have implemented the repository interface:
interface ICompanyRepository
{
public function getAllCompanies();
public function findBy($att, $columns);
public function getById($id);
public function with($relations);
}
I have implemented My Repository:
class CompaniesRepository implements ICompanyRepository
{
protected $model;
public function __construct(Companies $model){
$this->model = $model;
}
public function getAllCompanies(){
return $this->model->all();
}
public function findBy($att, $columns)
{
return $this->model->where($att, $columns);
}
public function getById($id)
{
return $this->model->findOrFail($id);
}
public function with($relations)
{
return $this->model->with($relations);
}
}
And then I have created the model:
class Companies extends Model
{
protected $fillable = [];
protected $casts = [
'settings' => 'array'
];
//my question is here!
public function members(){
return $this->hasMany('Companies\Entities\CompaniesMembers');
}
}
For now I have put the relations (in this case members function) in the model, but with this way, If I had to change my ORM, I should change both repository and model, because for now I use Eloquent, but I don't know if in future I will use Doctrine or others.
So my question is:
Where is the best place the relationships and the functions for db?
Is right put all in the Model or It would be better put all in Repository?
So in the EloquentRepo you make a function that combines company with companymembers by a foreach and use modelToObject($model) Something like this. I hope this will help you the good direction.
EloquentRepo:
private function modelToObject($model)
{
if (is_null($model)) {
$entity = null;
} else {
$entity = new Product(
$model->{Model::COL_ID},
$model->{Model::COL_NAME}
);
}
return $entity;
}
Entity:
class Product{
private $id;
private $name;
public function __construct(int $id, string $name) {
$this->setId($id)
->setName($name);
}
public function setName(string $name): Product{
$this->name = $name;
return $this;
}
public function getName(): string {
return $this->name;
}
}

Adding Setters and Getters to Laravel Model

If I want an Eloquent Model class to have setters and getters for the sake of implementing an interface does the following approach make sense or is there a 'laravel' approach to the problem
class MyClass extends Model implements someContract
{
public function setFoo($value) {
parent::__set('foo', $value);
return $this;
}
public function getFoo() {
return parent::__get('foo');
}
}
You are probably looking for accessors (getters) and mutators (setters).
Example of an accessor (getter) in Laravel:
public function getFirstNameAttribute($value)
{
return ucfirst($value);
}
Example of a mutator (setter) in Laravel:
public function setFirstNameAttribute($value)
{
$this->attributes['first_name'] = strtolower($value);
}
For new laravel you can do this in model :
/**
* Interact with the user's first name.
*
* #param string $value
* #return \Illuminate\Database\Eloquent\Casts\Attribute
*/
protected function firstName(): Attribute
{
return Attribute::make(
get: fn ($value) => ucfirst($value),
set: fn ($value) => strtolower($value),
);
}
Wanna know more? see at :
https://laravel.com/docs/9.x/eloquent-mutators#defining-a-mutator

How to create view/renderer for the polymorphic model

I need to render polymorphic models in different situations.
Model's base class:
abstract class BaseDiscount {
abstract public function createRenderer(IDiscountRendererFactory $factory);
}
IDiscountRendererFactory:
interface IDiscountRendererFactory {
/**
* #return IDiscountRenderer
*/
public function createDiscountRenderer(Discount $model);
/**
* #return IDiscountRenderer
*/
public function createActionRenderer(Action $model);
}
Discount model class:
class Discount extends BaseDiscount {
public function createRenderer(IDiscountRendererFactory $factory) {
return $factory->createDiscountRenderer($this);
}
}
Action model class:
class Action extends BaseDiscount {
public function createRenderer(IDiscountRendererFactory $factory) {
return $factory->createActionRenderer($this);
}
}
IDiscountRenderer:
interface IDiscountRenderer {
public function render();
}
In the client module I have:
class ConcreteDiscountRenderer implements IDiscountRenderer {
public function __construct(Discount $model) {
//do something
}
public function render() {
//do something
}
}
class ConcreteActionRenderer implements IDiscountRenderer {
public function __construct(Action $model) {
//do something
}
public function render() {
//do something
}
}
class ConcreateDiscountRendererFactory implements IDiscountRendererFactory {
public function createDiscountRenderer(Discount $model) {
return new ConcreteDiscountRenderer($model);
}
public function createActionRenderer(Action $model) {
return new ConcreteActionRenderer($model);
}
}
$repository = new DiscountRepository();
/** #var BaseDiscount[] $discounts */
$discounts = $repository->findAll();
$factory = new ConcreateDiscountRendererFactory();
foreach ($discounts as $discount) {
$discount->createRenderer();
$renderer = $discount->createRenderer($factory);
$renderer->render();
}
In other application parts may be other implementatations.
I think I got some combination of Visitor and AbstractFactory patterns.
Is it a right approach or is there a better solution?
UPD
If I add new model class, like DiscountProgramm extends BaseDiscout, I have to refactor IDiscountFactoryInterface and all it's realizations. Is there an approach which allows to avoid that?

Why my model not work in serviceRepository laravel?

I have a service-providers here as well as http://dfg.gd/blog/decoupling-your-code-in-laravel-using-repositiories-and-services
My ServiceProvider:
class UsersRepositoryServiceProvider extends ServiceProvider
{
public function register()
{
$this->app->bind('App\Repositories\Users\UsersInterface', function($app)
{
return new UsersRepository(new User);
});
}
And i get error: Call to a member function find() on a non-object
My Repository:
use \Illuminate\Database\Eloquent\Model;
//..
protected $usersModel;
public function __consturct(Model $users)
{
$this->usersModel = $users;
}
/**
* Get user by id
*
* #param mixed $userId
* #return Model
*/
public function getUserById($userId)
{
return $this->convertFormat($this->usersModel->find($userId));
}
__consturct is misspelled.
correct is __construct

Categories