Laravel trait crud controller with request validation and resources - laravel

I'm trying to refactor my code to be more reusable.
I created a trait CrudControllerTrait to implement the index,show,store,update,destroy methods.
But I found 2 problems:
BrandController.php
public function store(BrandNewRequest $request)
{
$requestData = $request->validated();
return new BrandResource($this->brands->store($requestData));
}
ProductController.php
public function store(ProductNewRequest $request)
{
$requestData = $request->validated();
return new ProductResource($this->products->store($requestData));
}
The trait method would be:
public function store(xxxxx $request)
{
$requestData = $request->validated();
return new xxxxxResource($this->repository()->store($requestData));
}
Problem1: The hint type. How can I abstract them? If I remove it shows that errror:
"message": "Too few arguments to function App\\Http\\Controllers\\BrandController::store(), 0 passed and exactly 1 expected"
Problem2: Return the resource. How can create the new resource? On the collection I can solve it doing this:
public function index()
{
$models = $this->repository()->index();
return $this->resource()::collection($models);
}
The resource is on the controller who uses the trait:
public function resource()
{
return BrandResource::class;
}
But with single resource didn't know how to do it...
The idea is, that I have so much controllers using the same pattern: BrandController, ProductController, etc. I'd love to reuse these 5 crud methods on the same trait...

The only way I found is creating an abstract method.
trait CrudRepositoryTrait
{
abstract function model();
public function index()
{
return $this->model()::with($this->with())->get();
}
public function find($id)
{
return $this->model()::findOrFail($id);
}
public function store($data)
{
$request = $this->dtoRequest($data);
return $this->model()::create($request);
}
(...)
}
And then, an example how to use this treat:
class ProductRepository implements ProductRepositoryContract
{
use CrudRepositoryTrait;
function model()
{
return Product::class;
}
(...)
}
By this way I could reuse a lot of code.

Related

Call to a member function hasAccessOrFail() on null error when using backpack in Laravel

I've been using backpack in Laravel but I want to replace action-domain-responder architecture with MVC.So I've created an Action which my route refers like below:
Route::get('post',[
'as' => 'post.index',
'uses' => 'Core\Post\Actions\ApiGetListOfPostsAction',
'operation' => 'list'
]);
class ApiGetListOfPostsAction extends BaseAction implements IAction
{
private $service;
public function __construct(ApiGetListOfPostsService $service)
{
$this->service = $service;
}
public function __invoke(Request $request): mixed
{
$data = $this->service->process();
return response()->json($data);
}
}
and my service has this code:
class ApiGetListOfPostsService extends CrudController
{
use ListOperation, CreateOperation, DeleteOperation, UpdateOperation;
public function setup()
{
CRUD::setModel(\App\Models\Post::class);
CRUD::setRoute(config('backpack.base.route_prefix') . '/post');
CRUD::setEntityNameStrings('post', 'posts');
}
protected function setupListOperation()
{
CRUD::column('title');
CRUD::column('content');
}
public function process()
{
return $this->index();
}
}
I've extended CrudController in my service class but I've got this error:
Call to a member function hasAccessOrFail() on null
which related to the ListOperation Trait and this code:
public function index()
{
$this->crud->hasAccessOrFail('list');
}
I need to send all requests to the Service class. How can I pass requests to the service class?
When I deleted middleware from CrudController I have no problem.
$this->middleware(function ($request, $next) {
$this->crud = app()->make('crud');
$this->crud->setRequest($request);
$this->setupDefaults();
$this->setup();
$this->setupConfigurationForCurrentOperation();
return $next($request);
});
I think your Action is missing something.
When using inheritance from a parent class, it might help to put this line in your constructor.
public function __construct(ApiGetListOfPostsService $service)
{
parent::__construct(); // <- Subclass constructor
$this->service = $service;
}
Doc: https://www.php.net/manual/en/language.oop5.decon.php

How to get model relations in Laravel?

I would like to get the model relations, in array;
My model look like:
class User extends Model
{
public function profile() {
return $this->haOne(Profile::class);
}
public function settings() {
return $this->morphOne(Settings::class, 'settingsable');
}
public function addresses() {
return $this->hasMany(Addresses::class);
}
}
And my code:
$user = User::with(['profile', 'settings', 'addresses'])->find(1);
$user->getRelations(); // return ['profile', 'settings', 'addresses'];
If I have more then 10 relation, I don't want to list all.
I would like to get like this:
$relations = ['profile', 'settings', 'addresses'];
Is this posible?
You could try adding a scope to the model, and so, you have to only write them once.
class User extends Model
{
public function profile() {
return $this->haOne(Profile::class);
}
public function settings() {
return $this->morphOne(Settings::class, 'settingsable');
}
public function addresses() {
return $this->hasMany(Addresses::class);
}
public function scopeWithRelations(Builder $query){
return $query->with([...]);
}
}
$users = User::withRelations()->get();
This way you only have to write them once there, and everywhere in the code you'll use the scope.
Not exactly 100% what you're asking, but this could be a solution.

Laravel Nova metrics filtering

I have a model called Property which has an 'active' flag. I want a metric at the top of my resource which shows a count of active Properties.
My calculate method is exactly as in the doc but this shows all Properties rather than active ones:
public function calculate(Request $request)
{
return $this->count($request, Property::class);
}
How can I add a filter?
I've tried a where clause:
public function calculate(Request $request)
{
return $this->count($request, Property::class)->where('active','=',1);
}
And a query scope:
public function calculate(Request $request)
{
return $this->count($request, Property::class)->active();
}
I thought I might be able to use the Nova filter I set up on the resource list page but that didn't seem to work either. I'm sure it's really easy but I haven't worked it out. Thanks for your help!
Your can use every type of Eloquent\Builder instance in the $model param.
Instead of:
public function calculate(Request $request)
{
return $this->count($request, Property::class);
}
Set a Scope on your Model
App\Property.php
...
public function scopeActive($query)
{
return $query->where('active', 1);
}
public function scopeInactive($query)
{
return $query->where('active', 0);
}
And use this scope as the $model param in your calculate method, because the call of the scope returns a Eloquent\Builder Instance
public function calculate(Request $request)
{
return $this->count($request, Property::active());
// return $this->count($request, Property::inactive());
}
Edit
Of course you can make the Eloquent Builder call inline:
public function calculate(Request $request)
{
return $this->count($request, Property::where('active', 1));
}

accessing custom method in model

Im practicing laravel and im making a custom method for my user
In my user model i have build a function like this
public function employee(){
return $this->where('user_type','employee');
}
and then in my controller I'm accessing the function like this
public function index(){
$users = User::latest()->employee();
return UserResource::collection($users);
}
but it return an error Method Illuminate\Database\Query\Builder::employee does not exist.how to fix this?
Use local scope instand
public function scopeEmployee($query)
{
return $query->where('user_type', 'employee');
}
Your controller can be as it was !
public function index(){
$users = User::latest()->employee()->get();
return ProductsResource::collection($users);
}

Custom Eloquent relation which sometimes returns $this Model

I have a Model Text which has a 1-to-many-relation called pretext(), returning a 1-to-many-Relationshop to Text, like so:
class Text extends Model
{
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function derivates(){
return $this->hasMany('App\Models\Text', 'pretext_id');
}
}
If a $text does not have any pretext (which, in my scenario, means $text['pretext_id'] == 0) $text->pretext() shall return the $text itself. When I try
public function pretext(){
if ( $this->belongsTo('App\Models\Text', 'pretext_id') ) {
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
else {
return $this;
}
}
I get the error
local.ERROR: LogicException: Relationship method must return an object of type Illuminate\Database\Eloquent\Relations\Relation
when the else-part is executed. So my questions are:
How do I turn $this into an object of type Relation? or alternatively:
How can I achieve my goal on a different way?
Try dynamic props
class Text extends Model
{
protected $appends = ['content'];
public function pretext(){
return $this->belongsTo('App\Models\Text', 'pretext_id');
}
public function getContentAttribute(){
$pretext = $this->pretext()->get();
if ($pretext->count()) {
return $pretext;
}
return $this;
}
}
Then in controller or view if you have the instance
(consider optimizing it if you have N+1 issues)
$obj = Text::find(1);
dd($obj->content);
I think you can create another method that calling pretext() and check the returned value.
public function getPretext() {
$value = pretext();
return ($value)? $value : $this;
}

Resources