Laravel Resources many different requests for the same Resource instance - laravel

I'm using Laravel Resource as a tool to sending API data. However, when I make just a basic controller with CRUD methods - all methods are different, for example
public function show(Offer $offer)
{
return $offer;
}
public function index()
{
return Offer::orderBy('created_at','desc')->get();
}
So I created OfferResource and for show method it's:
return new OfferResource($offer);
class OfferResource extends Resource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'body' => $this->body,
'title' => $this->title,
'specialities' => $this->specialities()->pluck('speciality_id'),
'specialities_name' => $this->specialities()->pluck('name')
];
}
}
how should I call OfferResource for method index()?
Should I create another Resource "Controller"? or is there a different approach to use the same OfferResource?

You can use collection() method to use this resource for index -
return OfferResource::collection(Offer::orderBy('created_at','desc')->get());
And it is best to use a resource for multiple purpose if you can use rather than creating new one for each purpose.
As we all know the concepts of code re-use, the very basic foundation of OOP, we want to write ones but use multiple times. That's why we write Resource class to use whenever we need to transform our data to a array in predefined format. And requirement always change, so it would be much better to use resource to keep up with changes.
A resource class represents a single model that needs to be transformed into a JSON structure.
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'email' => $this->email,
'created_at' => $this->created_at,
'updated_at' => $this->updated_at,
];
}
Laravel also provide a way to use this method to transform a collection of resources using collection() method.

Related

How to specify only methods for resource in array Laravel

In web.php i have this Route::resources method
Route::resources([
'products' => App\Http\Controllers\ProductsController::class,
'categories' => App\Http\Controllers\CategoriesController::class,
'page-info' => App\Http\Controllers\PageInfoController::class,
]);
How can I specify only index,edit and update for 'page-info' route? and how to add name to each route?
I know that I can do it like this
Route::resource('products', ProductsController::class);
Route::resource('page-info', PageInfoController::class)->only([
'index', 'edit', 'update'
]);
//itd...
but I like the array one resources and thought it is possible too
You can't do what you want, if the documentation does not explicitly say you can or not, you can always have a look at the source code.
So, source code for Laravel 8.x has:
/**
* Register an array of resource controllers.
*
* #param array $resources
* #param array $options
* #return void
*/
public function resources(array $resources, array $options = [])
{
foreach ($resources as $name => $controller) {
$this->resource($name, $controller, $options);
}
}
So, you can do
Route::resources([
'products' => App\Http\Controllers\ProductsController::class,
'categories' => App\Http\Controllers\CategoriesController::class,
'page-info' => App\Http\Controllers\PageInfoController::class,
], $options);
But the code shows that it will share the options for all the resources to be created, so the answer is a no, you can't (at least what I am looking at the source code).

Hiding fields in API Resources Using Gates in Laravel

I have a Product API resource in my application like so
/**
* Transform the resource collection into an array.
*
* #param Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'desc' => $this->desc,
'color' => $this->color,
'amount' => $this->amount,
'available' => $this->available,
'createdAt' => $this->created_at,
'updatedAt' => $this->updated_at,
];
}
I have few roles in my application, like admin, viewer.
When admin access the api, the api returns all fields but when the viewer access the api it returns only limited fields.
How can I handle this using Gates & Policies?
Can I do something like this
'createdAt' => $this->when($this->authorize('product.list'), $this->created_at)
You could use an Eloquent Accessor in your Product model:
public function getCreatedAtAttribute($createdAt)
{
if (Gate::allows('see-product-details', $this)) {
return $createdAt;
} else {
return null;
}
}
Of course you also have to write the see-product-details gate.
Otherwise this may work as well (not tested):
public function getCreatedAtAttribute($createdAt)
{
if ($this->authorize('view', [Product::class, $this])) {
return $createdAt;
} else {
return null;
}
}

Call to a member function first() on null in laravel resource

i try to build a customized response in my resource like this:
class ApplicationResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'sort'=> $this->sort,
'is_seen' => $this->is_seen,
'name' => $this->name,
'position' => $this->position,
'company' => $this->company,
'education' => $this->education,
'degree' => $this->degree,
'phone' => $this->phone,
'university' => $this->university,
'cv_folder_id' => $this->cv_folder_id,
'cv' => route('applications.cvShow', ['candidateCv' => $this->candidate_cv]),
'comments'=> ApplicationCommentsResource::collection($this->applicationComments),
'ratingFields'=> ApplicationRatingsResource::collection($this->applicationRatings()->get()),
'jobPostRatingFields' => JobPostRatingFieldsResource::collection($this->jobPost->jobPostRatingFields),
];
}
}
but i just get errors. the error i get is:
Call to a member function first() on null
i dont know how to build my response that if the collection is empty i dont get any error?
That simply means that you want to retrieve value that does not exist.
You can make simple condition like that:
if(is_null($this->sort)){
return "-";
}
Good luck!
I'm pretty sure the relationship is the problem.
But since there is not enough information, first of all find out in which line the error is, then check the relationships.
For example:
'comments'=> ApplicationCommentsResource::collection($this->applicationComments)
Model Application must have relationship applicationComments

Laravel avoid duplicate entry from model

I'm building a Laravel API. I have a models called Reservations. I want to avoid that a user creates two reservations for the same product and time period.
I have the following:
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
Edit after comments:
I'm also using validation
$validator = Validator::make($request->all(), [
'listing_id' => 'required|exists:listings,id',
'user_id_from' => 'required|exists:users,id',
'start_date' => 'required|date_format:"Y-m-d"|after:today',
'end_date' => 'required|date_format:"Y-m-d"|after:start_date'
]);
if ($validator->fails()) {
return response()->json(['error' => 'Validation failed'], 403);
}
Validation is working properly.
End of Edit
In my model I have casted the start_date and end_date as dates.
class Reservation extends Model
{
protected $fillable = ['listing_id', 'start_date', 'end_date'];
protected $dates = [
'start_date',
'end_date'
];
....
....
Documentation says:
The firstOrCreate method will attempt to locate a database record
using the given column / value pairs
However I notice that I'm still able to insert entries with the same attributes.
Any idea what I'm doing wrong or suggestions to fix it?
Probably there's a better way than this, but you can create an static method on Reservation to do this, like:
public static function createWithRules($data) {
$exists = $this->where('product_id', $data['product_id'])->whereBetween(*date logic that i don't remember right now*)->first();
if(!$exists) {
* insert logic *
} else {
* product with date exists *
}
}
So you can call Reservation::createWithRules($data)
You can achieve this using Laravel's built in ValidateRequest class. The most simple use-case for this validation, is to call it directly in your store() method like this:
public function store(){
$this->validate($request, [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
], $this->messages);
$reservation = Reservation::firstOrCreate([
'listing_id' => $request->listing_id,
'user_id_from' => $request->user_id_from,
'start_date' => $request->start_date,
'end_date' => $request->end_date,
]);
}
With this, you're validating users $request with by saying that specified columns are required and that they need to be unique, in order for validation to pass.
In your controller, you can also create messages function to display error messages, if the condition isn't met.
private $messages = [
'listing_id.required' => 'Listing_id is required',
'title.unique' => 'Listing_id already exists',
//... and so on
];
You can also achieve this by creating a new custom validation class:
php artisan make:request StoreReservation
The generated class will be placed in the app/Http/Requests directory. Now, you can add a few validation rules to the rules method:
public function rules()
{
return [
'listing_id' => 'required|unique,
'start_date' => 'required|unique,
//... and so on
];
}
All you need to do now is type-hint the request on your controller method. The incoming form request is validated before the controller method is called, meaning you do not need to clutter your controller with any validation logic:
public function store(StoreReservation $request)
{
// The incoming request is valid...
// Retrieve the validated input data...
$validated = $request->validated();
}
If you have any additional question about this, feel free to ask. Source: Laravel official documentation.

Laravel action when form validation generates error

I am working with a form request file like this:
ProjectCreateRequest.php
public function rules()
{
$project_name = $this->project_name;
$meta_activity = $this->meta_activity;
return [
'project_name' => 'required|max:255|unique:projects',
'customer_name' => 'required|max:255',
'otl_project_code' => 'sometimes|max:255|unique:projects,otl_project_code,NULL,id,meta_activity,'.$meta_activity,
'estimated_start_date' => 'date',
'estimated_end_date' => 'date',
'LoE_onshore' => 'numeric',
'LoE_nearshore' => 'numeric',
'LoE_offshore' => 'numeric',
'LoE_contractor' => 'numeric',
'revenue' => 'numeric',
'win_ratio' => 'integer'
];
}
There is the otl_project_code that must be unique with the meta_activity.
In case someone enters a pair of otl_project_code and meta_activity that already exists, it goes back to the create page with the error written below.
I would like to get instead that in the controller, I can catch this information, do something on the database then redirect to an update url.
Because I am working with a form validation request file, everything is entered in my controller like this:
public function postFormCreate(ProjectCreateRequest $request)
and I don't know how to catch this specific error in my controller to execute some actions with all the fields I submitted and not go back to the create page. Of course, this needs to happen only when there is the specific error I mentionned above.
Override the FormRequest response function in your ProjectCreateRequest:
/**
* Get the proper failed validation response for the request.
*
* #param array $errors
* #return \Symfony\Component\HttpFoundation\Response
*/
public function response(array $errors)
{
if ($this->expectsJson()) {
return new JsonResponse($errors, 422);
}
return $this->redirector->to($this->getRedirectUrl())
->withInput($this->except($this->dontFlash))
->withErrors($errors, $this->errorBag);
}
That's the public response on the FormRequest class so you can write your own logic to perform DB queries and redirect where needed.

Resources