Laravel API ResourceCollection WhenLoaded - laravel

I try to include a relationship in my resource array if it has been eager loaded, but don't get it working.
Anyone has an idea, how I can check the relationships in the ResourceCollection?
Database schema looks like this:
Here is my Post Model
class Post extends Model
{
function categories() {
return $this->belongsToMany('App\Category');
}
}
Here is my Category Model
class Category extends Model
{
function posts() {
return $this->belongsToMany('App\Post');
}
}
Here is my Post Controller
Class PostController extends Controller
{
public function index()
{
return new PostResourceCollection(Post::with("categories")->get());
}
}
Here is my Post ResourceCollection
class PostResourceCollection extends ResourceCollection
{
public function toArray($request)
{
return [
'data' => $this->collection->transform(function($page){
return [
'type' => 'posts',
'id' => $page->id,
'attributes' => [
'name' => $page->title,
],
];
}),
//'includes' => ($this->whenLoaded('categories')) ? 'true' : 'false',
//'includes' => ($this->relationLoaded('categories')) ? 'true' : 'false',
];
}
}

Maybe too late, below solution is a workaround for this case:
return [
...,
'includes' => $this->whenLoaded('categories', true),
];
Loading custom attribute:
return [
...,
'includes' => $this->whenLoaded('categories', fn() => $this->categories->name),
];

You relationship is wrong, a post belongs to many categories while a category has many posts so change:
class Category extends Model
{
function posts() {
return $this->belongsToMany('App\Post', 'category_post');
}
}
to
class Category extends Model
{
function posts() {
return $this->hasMany('App\Post', 'category_post');
}
}
Now when you load the post you can load the categories also:
$posts = Post::with('categories')->get();

got it.. That was the missing piece. if anyone has a better solution for this it would be much appreciated.
foreach ($this->collection as $item) {
if ($item->relationLoaded('categories')) {
$included = true;
}

Related

L8 - Make a FormRequest from another FormRequest fails

I've a simple code (more o less) like this.
Routes:
Route::post('car', 'CarController#store')->name('insert_car.store');
Route::post('car-italian', 'CarItalianController#store')->name('insert_car-italian.store');
Controllers:
class CarController extends Controller
{
public function store(StoreCarRequest $request)
{
return Car::create($request->validated()) // Calling 'car-italian' route, the code fails here!
}
}
class CarItalianController extends Controller
{
public function store(StoreCarItalianRequest $request)
{
$input_parameters = $request->validated();
$t = [
'model' => $input_parameters['modello'],
'door' => $input_parameters['porte'],
];
return (new CarController)->store(new StoreCarRequest($t));
}
}
Forms Request:
class StoreCarRequest extends FormRequest
{
public function rules()
{
return [
'model' => 'string',
'door' => 'integer'
];
}
}
class StoreCarItalianRequest extends FormRequest
{
public function rules()
{
return [
'modello' => 'string',
'porte' => 'integer'
];
}
}
When I call the route car-italian, It fails with message:
Call to a member function validated() on null
Can someone help me? I spent one full day on it :-/
Thank you
Ok, I've found a solution updating the Controller CarItalianController:
class CarController extends Controller
{
public function store(StoreCarRequest $request)
{
return Car::create($request->validated())
}
}
class CarItalianController extends Controller
{
public function store(StoreCarItalianRequest $request)
{
$input_parameters = $request->validated();
$t = [
'model' => $input_parameters['modello'],
'door' => $input_parameters['porte'],
];
$insertRequest = new StoreCarRequest();
$insertRequest->setValidator(Validator::make($t, $insertRequest->rules()));
return (new CarController)->store($insertRequest);
}
}

Eager Loading vs Lazy Loading with API Resource

Could you please suggest which option I should use for the following example.
I'm currently building an API to get Places, Place model contains User and Location models.
Relationships are set like that:
class Place extends Model
{
protected $guarded = [];
public function createdBy()
{
return $this->belongsTo(User::class, 'created_by', 'id');
}
public function location()
{
return $this->belongsTo(Location::class);
}
}
Option 1: Lazy Loading:
public function getPlaces()
{
return Place::all()->get();
}
class PlaceResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'createdBy' => (new UserResource($this->createdBy)),
'location' => (new LocationResource($this->location)),
];
}
}
Option 2: Eager Loading:
public function getPlaces()
{
return Place::with([
'createdBy',
'location',
])
->get();
}
class PlaceResource extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'createdBy' => (new UserResource($this->createdBy)),
'location' => (new LocationResource($this->location)),
];
}
}
Logically thinking with the Eager Loading option data should loads quicker, right? I have tested both options for about 100 places but the loading time seems to be the same.
What's the right way (option to use) to load data quicker?
It is better to Eager Load those relationships when dealing with a result set like that. Since you are iterating through each record in that result set and accessing the relationship this would cause a N+1 issue otherwise. Since it is 2 relationships being accessed it would be (N*2) + 1 .

Laravel Algolia Scout, whereIn on relationships

I am working on a Laravel project. I am using Scout based on Algolia. But I struggling to apply whereIn on the relationships. I have 2 models as follow.
Place.php
class Place extends Model
{
use Searchable, Localizable;
protected $with = [
'images',
'phones',
'emails',
'categories'
];
protected $casts = [
'is_featured' => 'boolean'
];
public function categories()
{
return $this->belongsToMany(Category::class, 'place_category');
}
public function searchableAs()
{
return "places_index";
}
public function toSearchableArray()
{
$record = $this->toArray();
$record['_geoloc'] = [
'lat' => $record['latitude'],
'lng' => $record['longitude'],
];
$record['categories'] = $this->categories->map(function ($data) {
return [
'id' => $data['id'],
'en_name' => $data['en_name'],
'mm_name' => $data['mm_name'],
];
})->toArray();
unset($record['created_at'], $record['updated_at'], $record['latitude'], $record['longitude']);
unset($record['images'], $record['phones'], $record['emails']);
return $record;
}
}
Category.php
class Category extends Model
{
use Searchable;
protected $touches = [
'places',
];
public function places()
{
return $this->belongsToMany(Place::class, 'place_category');
}
}
Now, I am searching the Place models/ data filtering by category. As you can see, I have also indexed the categories with places in toSearchableArray method.
I am trying to achieve something like this.
Place::search($keyword)->whereIn('categories', ????);//how can I filter by the Ids here
How can I do that?

Filter and sort in gridview by related field (2 degrees away)

My model Order has the following functions:
public function getAccount()
{
return $this->hasOne(Account::className(), ['id' => 'account_id']);
}
public function getChairName()
{
return $this->account->chairName;
}
The problem is that chairName itself is a related field (Created here in another model Account):
public function getChair0()
{
return $this->hasOne(Category::className(), ['id' => 'chair']);
}
public function getChairName()
{
return $this->chair0->name;
}
In my OrderSearch model I am now trying to search and filter by the name of the chair. In Account the chair is stored with an id that is linked to the model Category.
I have been looking at this link but it didn't help me solve my issue:
http://www.yiiframework.com/wiki/621/filter-sort-by-calculated-related-fields-in-gridview-yii-2-0/
MY OrderSearch model looks like this:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\OrderHistory;
[...]
class OrderHistorySearch extends OrderHistory
{
[...]
public $chairName;
[...]
public function rules()
{
return [
[...]
[['chairName'], 'safe'],
[...]
];
}
[...]
public function scenarios() { [...] }
public function search($params)
{
$query = OrderHistory::find();
$query->joinWith(['employee', 'account', 'item']);
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
[...]
$dataProvider->sort->attributes['chairName'] = [
'asc' => ['account.chair' => SORT_ASC],
'desc' => ['account.chair' => SORT_DESC],
];
[...]
$this->load($params);
if (!$this->validate()) { [...] }
$query->andFilterWhere([ [...] ]);
$query->andFilterWhere(['like', 'category.name', $this->chairName]);
return $dataProvider;
}
}
I reckon my mistake lies somewhere here:
$query->joinWith(['employee', 'account', 'item']);
The problem lies definitely in the fact that I am trying to join a table that is currently not joining.

Including additional custom attributes in an laravel eloquent response

Model
class Profile extends Eloquent {
public function user(){ return $this->belongsTo('User'); }
public function url(){
return URL::Route('profile', array('city_slug' => $this->city_slug, 'slug'=> $this->slug));
}
}
Controller
class ProfileController extends BaseController {
public function show($user_id)
{
$profile = Profile::with('user')->where('user_id', $user_id);
return Response::json($profile, 200);
}
}
I would like the response to include the generated URL from the Profile method url(){}
{
"id" : 1,
"url" : /profile/{city_slug}/{slug},
....
}
Thanks in advance.
I would go at this the other way.
In your Users model...
public function profile()
{
return $this->belongsTo('Profiles','profile_id');
}
Then you can use $profile = User::find(user_id)->profile.
$data = array(
'id' => $profile->id,
'url' => "/profile/".$profile->city_slug."/".profile->slug
);
And then finally
return Repsonse::json($data,200);

Resources