Laravel combine resource collection when returning response - php

I have a resource which looks like so;
class TestingResource extends JsonResource
{
public function toArray($request)
{
return [
'first' => AnotherResource::collection($this->first),
'second' => AnotherResource::collection($this->second),
];
}
}
What I want to do is combine the 2 so I only have to return one element like so;
class TestingResource extends JsonResource
{
public function toArray($request)
{
return [
'first' => AnotherResource::collection($this->combined),
];
}
}
I tried doing array_merge($this->first, $this->second) but it doesnt work.
Is there any way of getting this to work?

You can use the concat() for collections like:
public function toArray($request)
{
$first = FirstResource::collection(First::all());
$second = SecondResource::collection(Second::all());
$combined = new Collection();
return $combined->concat($first)->concat($second);
}
This concatenates key. The merge() will overwrite the values.

Related

how to use collection on same model laravel resources

We are currently developing a feature in codotto.com where a user can comment on an IT meetup. Each comment can have an answer to it. We are only allowing for one-level deep answers, so something like:
- Comment 1
- Answer to comment 1
- Answer to comment 1
- Comment 2
- Answer to comment 2
- Answer to comment 2
I have the following database structure:
// meetup_messages
- id
- user_id
- meetup_id
- meetup_message_id (nullable) -> comments that do not answer will have this set to nullable
In my model I define the answers as a HasMany relationship:
class MeetupMessage extends Model
{
// ...
public function answers(): HasMany
{
return $this->hasMany(self::class, 'meetup_message_id');
}
}
Then on my controller, I get all comments that do not have answers:
public function index(
IndexMeetupMessageRequest $request,
Meetup $meetup,
MeetupMessageService $meetupMessageService
): MeetupMessageCollection
{
$meetupMessages = MeetupMessage::with([
'user',
// 'answers' => function ($query) {
// $query->limit(3);
// }
'answers'
])
->whereNull('meetup_message_id')
->whereMeetupId($meetup->id)
->paginate();
return new MeetupMessageCollection($meetupMessages);
}
Then on my MeetupMessageCollection:
class MeetupMessageCollection extends ResourceCollection
{
public function toArray($request)
{
return parent::toArray($request);
}
}
Then on my MeetupMessageResource:
<?php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
use Illuminate\Support\Collection;
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect([
// 'answers' => new MeetupMessageCollection($this->whenLoaded('answers')),
])
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$collection->put('answers', MeetupMessageCollection::collection($this->answers));
}
);
}
}
But I get the following error: Call to undefined method App\\Models\\Meetup\\MeetupMessage::mapInto(). How can I still use MeetupMessageCollection by passing the answers to it?
As #matialauriti pointed out, you cant use resource collections inside collections in Laravel
class MeetupMessageResource extends JsonResource
{
public function toArray()
{
return [
'answers' => new MeetupMessageCollction($this->answers) // ❌ You can't do this
]
}
}
My solution was to pull my resource formation to a private method and re-use it if answers is present:
class MeetupMessageResource extends JsonResource
{
public function toArray($request)
{
return collect($this->messageToArray($this->resource))
->when($this->relationLoaded('user'), function (Collection $collection) {
$collection->put('user', $this->userToArray($this->user));
})
// ✅ Now I don't need to use Resources inside my API Resource class
->when(
is_null($this->meetup_message_id) && $this->relationLoaded('answers'),
function (Collection $collection) {
$answers = $this
->answers
->map(function (MeetupMessage $answer) {
return array_merge(
$this->messageToArray($answer),
['user' => $this->userToArray($answer->user)]
);
});
$collection->put('answers', $answers);
}
);
}
private function messageToArray(MeetupMessage $meetupMessage): array
{
return [
'id' => $meetupMessage->id,
'message' => Purify::config(MeetupMessageService::CONFIG_PURIFY)->clean($meetupMessage->message),
'answersCount' => $this->whenCounted('answers'),
'createdAt' => $meetupMessage->created_at,
];
}
}

Laravel find() does not get data from the DB

I have a resource Controller with this index method like this:
public function index()
{
$args = [];
$args = array_merge($args, $this->data_creator(35, 12, 'book'));
$args = array_merge($args, $this->data_creator(37, 12, 'kit'));
$args = array_merge($args, $this->data_creator(38, 12, 'game'));
$args['menu_links'] = [
'books' => route('shopping-products.category', Category::find(25)->slug),
'videos' => route('shopping-products.category', Category::find(24)->slug),
'kits' => route('shopping-products.category', Category::find(23)->slug),
'games' => route('shopping-products.category', Category::find(22)->slug),
];
return view('frontend.shop.products.index', $args);
}
But it returns this error:
Trying to get property 'slug' of non-object
And when I dd(Category::find(25), Category::find(24), Category::find(23), Category::find(22)); I get NULL results.
Meaning that it can not find data with specified ids.
However there are 25 records stored at the categories table:
So what is going wrong here? How can I fix this issue?
I would really appreciate any idea or suggestion from you guys...
Thanks in advance.
Here is Category.php Model:
class Category extends Model
{
use Sluggable, SoftDeletes;
protected $table = 'categories';
protected $primaryKey = 'cat_id';
protected $guarded = [];
/**
* Return the sluggable configuration array for this model.
*
* #return array
*/
public function sluggable()
{
return [
'slug' => [
'source' => 'cat_name'
]
];
}
public function path()
{
return "/products/categories/$this->slug";
}
public function children()
{
return $this->hasMany(Category::class, 'cat_parent_id', 'cat_id');
}
public function parents()
{
return $this->hasMany(Category::class, 'cat_id', 'cat_parent_id');
}
public function products()
{
return $this->belongsToMany(Product::class, 'category_products', 'ctp_cat_id', 'ctp_prd_id');
}
public function news()
{
return $this->belongsToMany(News::class, 'category_news', 'ctn_cat_id', 'ctn_nws_id');
}
public function galleries()
{
return $this->belongsToMany(Gallery::class, 'category_galleries', 'ctg_cat_id', 'ctg_gly_id');
}
public function uploaded()
{
return $this->hasMany(UploadedFile::class, 'upf_object_id', 'cat_id')->where('upf_object_type_id', '=', '107');
}
public function articles()
{
return $this->belongsToMany(Article::class, 'article_category', 'act_cat_id', 'act_art_id');
}
public function olympiadExam()
{
return $this->belongsToMany(OlympiadExam::class, 'olympiads_exams_categories', 'oec_ole_id', 'oec_cat_id');
}
public function olympiadExamQuestion()
{
return $this->belongsToMany(OlympiadExamQuestion::class, 'olympiads_exams_questions_categories', 'oes_cat_id', 'oes_oeq_id')->orderBy('oeq_number', 'asc');
}
public function attr_attributes()
{
return $this->hasMany(CategoryAttribute::class, 'category_id', 'cat_id');
} //
public function attr_product()
{
return $this->hasMany(Product::class, 'prd_cat_att_id', 'cat_id');
} //
public function couponRelation()
{
return $this->hasMany(couponRelation::class, 'object_id', 'cat_id')->where('object_type', 'product_category');
}
public function magazines()
{
return $this->belongsToMany(Magazine::class, 'category_magazine', 'category_id', 'magazine_id');
}
}
And when I do: dd(Category::where('cat_id', 25), Category::where('cat_id', 24), Category::where('cat_id', 23), Category::where('cat_id', 22)); I get this as result:
The problem is because you are using SoftDeletes so soft deleted models will automatically be excluded from query results. In your case, look like Category with id 22, 23, 24, 25 are soft deleted. To get it, you need to use withTrashed() as mentioned in the doc. For example:
Category::withTrashed()->find(22)->slug
per an answer above: if you are using soft deletes you need to add
Category::withTrashed()
However, you can wrap the command in an optional() helper function.
optional(Category::find(22))->slug
// if you are using soft delete
optional( Category::withTrashed()->find(22) )->slug
this will return null if 22 does not exist instead of throwing an exception error.

Laravel get data from db array not working

When I run the code I get no error but the data I am trying to display is not displaying it's just blank.. can someone tell me what I'm doing wrong?
My controller:
public function openingPage($id) {
$this->getGames();
$games = $this->getGames();
return view('caseopener')->with('games',$games);
}
private function getGames() {
$games = array();
foreach ($this->data->items as $item) {
$game = new Game($item);
$games[] = array(
'id' => $game['id'],
'name' => $game['name'],
'price' => $game['price'],
'image' => $game['image'],
);
}
return $games;
}
The 'Game' Model that is used in 'getGames function':
class Game extends Model
{
private $id;
public $data;
public function __construct($id) {
parent::__construct();
$this->id = $id;
$this->data = $this->getData();
}
private function getData() {
$game = DB::table('products')->where('id', 1)->first();
if(empty($game)) return array();
return $game;
}
}
The view:
#foreach ($games as $game)
<div class="gold">$ {{ $game['price'] }}</div>
#endforeach
I think you are over-complicating things. You could simplify your flow like this:
Given your provided code, it seems like you are using a custom table name ('products') in your Game model. So we'll address this first:
Game.php
class Game extends Model
{
protected $table = 'products'; //
}
Now, it seems like you're searching an array of Game ids ($this->data->items). If so, you could make use of Eloquent for your query, specially the whereIn() method:
YourController.php
public function openingPage($id)
{
$games = Game::whereIn('id', $this->data->items)->get();
return view('caseopener')->with('games', $games);
}
Optionally, if you want to make sure of just returning the id, name, price and image of each Game/product, you could format the response with API Resources:
php artisan make:resource GameResource
Then in your newly created class:
app/Http/Resources/GameResource.php
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class GameResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'name' => $this->name,
'price' => $this->price,
'image' => $this->image,
];
}
}
So now just update your controller:
YourController.php
use App\Http\Resources\GameResource;
public function openingPage($id)
{
$games = Game::whereIn('id', $this->data->items)->get();
return view('caseopener')->with('games', GameResource::collection($games));
} // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Create nested API

I'm trying to make an api that have lists and inside each list there is anther list inside of it called cards and the cards list is the cards of this list.
I tried to show it in index function and didn't work it was like this:
public function index()
{
// $list = List -> cards();
$list = List::cards();
return response( $list );
}
Card Model:
public function list()
{
return $this->belongsTo( List::class() );
}
Card Model:
public function cards()
{
return $this->hasMany( Card::class() );
}
What i want to output is json data like this:
"lists":[
'name':listname
'cards':[
'card one': card name,
]
]
If you use Laravel framework use Resource for response, in Resource of laravel you can load cards. For example in ListController :
public function index()
{
return ListResource::collection(List::all()->paginate());
}
And in ListResource :
public function toArray($request)
{
'cards' => CardResource::collection('cards');
}
belongsTo or hasMany accepts model name as a first argument. In your case you need to pass your model class name in your relations methods.
public function list()
{
return $this->belongsTo(List::class);
}
and
public function cards()
{
return $this->hasMany(Card::class);
}
So if you want to receive models including relations you can use with method.
return response(List::query()->with('cards'));
You can use resources.
Http\Resources\List:
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class List extends JsonResource
{
public function toArray($request)
{
$cards = [];
foreach ($this->cards as $card) {
$cards[] = $card->name;
}
return [
'name' => $this->name,
'cards' => $cards,
];
}
}
Http\Controllers\ListController:
namespacce App\Http\Controllers;
use App\Http\Resources\List as ListResource;
use App\Components\List;
class ListController extends Controller
{
$lists = List::query()->get();
return ListResource::collection($lists)->response();
}

Laravel query with multiple where not returning expected result

I'm trying to build a query from a Repository in a Model with 2 where clauses.
This is the data I have in a MySql table:
id name environment_hash
1 online_debit abc
2 credit_cart abc
I want to query by name and environment_hash. To do this, I created the method findByHashAndMethod() (see below).
But when I use it in my controller, like this:
$online_debit = $this->ecommercePaymentMethodRepository->findByHashAndMethod($hash, 'online_debit')->first();
or this:
$credit_card = $this->ecommercePaymentMethodRepository->findByHashAndMethod($hash, 'credit_cart')->first();
I keep getting both rows and not only the ones filtered. What's wrong with the code?
This is my PaymentMethodRepository.php
class EcommercePaymentMethodRepository extends BaseRepository
{
public function findByHashAndMethod($hash = null, $payment_method)
{
$model = $this->model;
if($hash)
{
$filters = ['environment_hash' => $hash, 'name' => $payment_method];
$this->model->where($filters);
}
else
{
$this->model->where('environment_hash', Auth::user()->environment_hash)
->where('name', $payment_method);
}
return $model;
}
public function model()
{
return EcommercePaymentMethod::class;
}
}
And this is my model EcommercePaymentMethod.php
<?php
namespace App\Models;
use Eloquent as Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class EcommercePaymentMethod extends Model
{
use SoftDeletes;
public $table = "ecommerce_payment_methods";
protected $dates = ['deleted_at'];
public $fillable = [
"name",
"payment_processor_id",
"active",
"environment_hash"
];
protected $casts = [
"name" => "string"
];
public function payment_processor()
{
return $this->hasOne('App\Models\EcommercePaymentProcessor');
}
}
While I am not entirely sure why ->first() would ever return more than one result, your Repository method had some few glaring issues that's prone to errors.
class EcommercePaymentMethodRepository extends BaseRepository
{
// 1. Do not put optional parameter BEFORE non-optional
public function findByHashAndMethod($payment_method, $hash = null)
{
// 2. Call ->model() method
$model = new $this->model();
// 3. Logic cleanup
if (is_null($hash)) {
$hash = Auth::user()->environment_hash;
}
return $model->where('environment_hash', $hash)
->where('name', $payment_method);
}
public function model()
{
return EcommercePaymentMethod::class;
}
}

Categories