How to Combine Some Relations in Laravel - php

On my project, Picture model has some "one to many" relations to create featured images. Those relations:
public function featuredByPosts()
{
return $this->hasMany('App\Post', 'featured_image_id');
}
public function featuredByProduct()
{
return $this->hasMany('App\Products', 'featured_image_id');
}
public function featuredByPages()
{
return $this->hasMany('App\Page', 'featured_image_id');
}
One of the inverse relations is like so:
public function featured_image()
{
return $this->belongsTo('App\Picture', 'featured_image_id');
}
When I get the pictures with those relations, each picture in collection has featured_by_posts, featured_by_products and featured_by_pages keys and related content as values of those keys.
What I want to do is to create a new key named "featured_by" directly in each picture in the collection and move current featured_by_* relations into new key by modifing their keys like so:
From this:
$picture = [
"id" => "1",
"name" => "someName",
"featured_by_posts" => [array of posts],
"featured_by_pages" => [array of pages]
]
To this:
$picture= [
"id" => "1",
"name" => "someName",
"featured_by" => [
"posts" => (values of featured_by_posts),
"pages" => (values of featured_by_pages)
]
]
I don't know if it can be done while getting data from database. That's why I tried to add the codes down below in index function on my API controller to produce formatted item of picture.
$relations = ["tags", "posts", "products", "featuredByPosts", "featuredByProducts", "featuredByPages"];
$pictures = Picture::with($relations)->get();
foreach ($pictures as $picture) {
$featureds = ["posts", "products", "pages"];
$key = "featured_by";
$picture[$key] = [];
foreach ($featureds as $featured) {
$oldKey = "{$key}_{$featured}";
$picture[$key][$featured] = $picture[$oldKey]; //This line produces the error
unset($picture[$oldKey]);
}
}
//ERROR: Indirect modification of overloaded element of App\Picture has no effect.
I don't understand what that means since the think. I searched this error and found some answers, but I couldn't make it work. So I hope someone can help. Thanks.

You should use the eloquent API resources:
https://laravel.com/docs/7.x/eloquent-resources
namespace App\Http\Resources;
use Illuminate\Http\Resources\Json\JsonResource;
class PictureResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request): array
{
return [
'id' => $this->id,
'name' => $this->name,
'featured_by' => [
'posts' => $this->featuredByPost,
'products' => $this->featuredByProduct,
],
];
}
}
Your controller:
return new PictureResource(Picture::find(1));

Related

Get data from array depend on database integer

I have a model of Posts with status column. I wonder, what would be be practice in Laravel to retrieve from something like 0 or 1(stored integers in DB) and instead of that show "displayed" or "hidden"? Not inside of the blade temples but when doing something like:
return response()->json(['posts' => $posts])
"status" of $posts would be not "0" but "displayed"?
You can make it in Posts Model:
const displayed = 1;
const hidden = 0;
public static function status()
{
return [
self::displayed => 'displayed',
self::hidden => 'hidden',
];
}
And retrieve it.
$post = [
"status" => Posts::status()[$request->status]
];

Laravel error: Property [name] does not exist on this collection instance

I'm trying to get a partner for each order using Laravel resource collections. But this throws up an error:
Property [name] does not exist on this collection instance
I get partners this way
Order_product.php
//...
class Order_product extends Model
{
protected $fillable = ['order_id', 'product_id', 'quantity', 'price'];
public function partner()
{
return $this->hasManyThrough(
'App\Partner', 'App\Order',
'partner_id', 'id', 'order_id');
//orders partners order_products
}
//...
Resources\Order_product.php
class Order_product extends JsonResource
{
public function toArray($request)
{
return [
'id' => $this->id,
'order_id' => $this->order_id,
'product_id' => $this->product_id,
'quantity' => $this->quantity,
'price' => $this->price,
'status' => $this->order->status,
'product_name' => $this->prod->name,
//return error
'partner_name' => $this->partner->name,
];
/*
//this method return:
//Invalid argument supplied for foreach() {"exception":"[object]...
$partners = [];
foreach($this->collection as $partner) {
array_push($partners, [
// 'partner_name' => $this->partner->name
]);
}
return $partners;
*/
}
}
Each order has one partner name. In the future, I will group them, but now I just need to output the partner_name
is relations when you use hasMany or hasManyThrough it returns you a collection, so you should use it in foreach or use with index
return [
'product_name' => $this->prod->first()->name, //first array in collection using first()
];
OR
return [
'product_name' => $this->prod[0]->name, //first array in collection using index
];
or you can write this code in foreach!
As you are using hasManyThrough or hasMany laravel relationship returns Illuminate\Database\Eloquent\Collection Instance.
If you want to get name you have to have one Model instance.
Solution 1: $this->parthner->first()->name
Solution 2: See this hasOneThough
public function partner(){
return $this->hasOneThrough(
'App\Partner', 'App\Order',
'partner_id', 'id', 'order_id');
}
Depens on your app logic
Hope this helps you

Laravel relationships hasMany updateOrCreate array

I have User model which has relationships hasMany with UserPosition eloquent model:
User model
public function positions()
{
return $this->hasMany(UserPosition::class);
}
How I can use updateOrCreate method when from request come array of data positions?
Code
$positions = [
"doctor",
"developer"
];
$user->positions()->each(function($position) use ($positions, $user) {
$id = $user->id;
foreach ($positions as $name) {
$position->updateOrCreate(['user_id' => $id, 'name' => $name], [
'name' => $name
]);
}
});
Note: In this example user doesn't has any positions on the table of database
But my code not work. Why?
You are iterating on the existing positions of a user, meaning that if a user has no positions the iteration will never happen. You can iterate along the positions you need to make:
$positions = collect([
"doctor",
"developer"
]);
$positions->each(function($position) use ($user) {
$user->positions()->updateOrCreate(['name' => $position], [
'name' => $position
]);
}
});
It doesn't work because your running your updateOrCreate() method inside an each iteration which never runs because as you stated in your note "this example user doesn't has any positions on the table of database"
Here your trying to loop through your currently existing positions attached to your user model:
$user->positions()->each()
But that won't run because the user doesn't have any positions at the moment.
I'm guessing you are trying to update the the user's positions, where it is possible that the user already has one or more of those positions associated to him and you don't want to create duplicates, for that you can do this:
$positions = [
"doctor",
"developer"
];
foreach($positions as $position) {
UserPosition::firstOrCreate(['user_id' => $user->id, 'name' => $position]);
}

Return many object in my json response using resource

I'm kinda new to Laravel and I hope someone we'll be able to give me some help.
I apologize for my english
So I'm trying to develop an application with some friends to manage our food by sending alert when the peremption date is near.
I'm developing the API, the actual structure is this way:
A user,
A product,
A basket containing the user_id, the product_id and of course the peremption date.
So now when I make a call to get the User 'stock' on my API I wish I could get something like this:
{
'id' : 1,
'peremption_date': XX-XX-XX,
'product' : {
'id' : 3,
'name': bblablabala,
'brand' : blablabala
},
'id' : 2,
'peremption_date': XX-XX-XX,
'product' : {
'id' : 4,
'name': bblablabala,
'brand' : blablabala
},
}
So I took a look on resources and saw that if I define the right relations, this could do the stuff for my output.
I'll link you my actual class declarations and their resources:
User:
//user.php
class User extends Authenticatable
{
use Notifiable, HasApiTokens;
protected $fillable = [
'name', 'email', 'password',
];
protected $hidden = [
'password', 'remember_token',
];
public function baskets()
{
return $this->hasMany(Basket::class);
}
}
Product:
//Product.php
class Product extends Model
{
protected $table = 'products';
protected $fillable = ['code_barre', 'product_name', 'generic_name', 'brand', 'quantity'];
public function basket()
{
return $this->belongsToMany(Basket::class);
}
}
//productResource.php
class ProductResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'code_barre' => $this->code_barre,
'product_name' => $this->product_name,
'generic_name' => $this->generic_name,
'brand' => $this->brand,
'quantity' => $this->quantity,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
];
}
}
Basket:
//Basket.php
class Basket extends Model
{
protected $table = 'baskets';
protected $fillable = ['user_id', 'product_id', 'dlc_date'];
public function user()
{
return $this->belongsTo(User::class);
}
public function product()
{
return $this->hasOne(Product::class);
}
}
//BasketResource.php
class BasketResource extends JsonResource
{
/**
* Transform the resource into an array.
*
* #param \Illuminate\Http\Request $request
* #return array
*/
public function toArray($request)
{
return [
'id' => $this->id,
'dlc_date' => (string) $this->dlc_date,
'created_at' => (string) $this->created_at,
'updated_at' => (string) $this->updated_at,
'product' => $this->product
];
}
}
So when I try to store a new basket in my store method:
//BasketController.php
public function store(Request $request)
{
$this->product->storeProduct($request->input('code_barre'));
$att = DB::table('products')
->where('code_barre', '=', $request->input('code_barre'))
->first();
$basket = Basket::create([
'user_id' => $request->user()->id,
'product_id' => $att->id,
'dlc_date' => $request->input('dlc_date')
]);
return new BasketResource($basket);
}
I get the following error (this one)
saying than products.id_basket does not exist and its right, it's not supposed to exist. This is Basket who have a product_id. so I know this is coming from the relationship I declared but I can't figure how to do it right.
Can someone come and save me ???
Thanks a lot, I hope you understood me !
Wish you a good day
As I look at your Basket model, it seems you have to change your:
public function product()
{
return $this->hasOne(Product::class);
}
to:
public function product()
{
return $this->belongsTo(Product::class);
}
Because you have product_id in your baskets table. To use hasOne() relation, you will need to remove product_id from baskets table and add basket_id to products table, because hasOne() relation is something like hasMany(), only calling ->first() instead of ->get()

Yii2 GridView with ArrrayDataProvider search

I need help with search model for ArrayDataProvider. Let's say i have an array:
$cities = [
['city' => "Chicago", 'year' => 1984],
['city' => "Washington", 'year' => 2001],
['city' => Manchester", 'year' => 1997],
//and so on...
];
I create an ArrayDataProvider:
$provider = new \yii\data\ArrayDataProvider([
'allModels' => $catalog,
'sort' => [
'attributes' => ['city', 'year'],
],
]);
Then I create a GridView:
echo \yii\grid\GridView::widget([
'dataProvider' => $provider,
'filterModel' => (new LibrarySearchModel()),
'columns' => $columns,
'showHeader' => true,
'summary' => false,
]);
All works fine, but i need a filtering in GridView. There is no option to use ActiveDataProvider and I cant find any tutorial how to filter a data in ArrayDataProvider.
Can someone help me with code for filter model or recomend the docs for my case?
This is example of how to use ArrayDataProvider with filters in the GridView.
Let's create simple action.
public function actionExample()
{
$data = new \app\models\Data();
$provider = $data->search(Yii::$app->request->get());
return $this->render('example', [
'provider' => $provider,
'filter' => $data,
]);
}
This is classic Yii 2 approach to the GridView so I will not explain it (you can find details in the Guide linked above).
Now the view.
<?php
echo \yii\grid\GridView::widget([
'dataProvider' => $provider,
'filterModel' => $filter,
'columns' => [
'name',
'code',
],
]);
Again, nothing different from the ActiveDataProvider approach. As you can see here we are expecting two columns: name and code - these will be defined below.
Data model.
Prepare the model that will handle the data source. Explanation is given in the comments.
<?php
namespace app\models;
use yii\base\Model;
/**
* Our data model extends yii\base\Model class so we can get easy to use and yet
* powerful Yii 2 validation mechanism.
*/
class Data extends Model
{
/**
* We plan to get two columns in our grid that can be filtered.
* Add more if required. You don't have to add all of them.
*/
public $name;
public $code;
/**
* Here we can define validation rules for each filtered column.
* See http://www.yiiframework.com/doc-2.0/guide-input-validation.html
* for more information about validation.
*/
public function rules()
{
return [
[['name', 'code'], 'string'],
// our columns are just simple string, nothing fancy
];
}
/**
* In this example we keep this special property to know if columns should be
* filtered or not. See search() method below.
*/
private $_filtered = false;
/**
* This method returns ArrayDataProvider.
* Filtered and sorted if required.
*/
public function search($params)
{
/**
* $params is the array of GET parameters passed in the actionExample().
* These are being loaded and validated.
* If validation is successful _filtered property is set to true to prepare
* data source. If not - data source is displayed without any filtering.
*/
if ($this->load($params) && $this->validate()) {
$this->_filtered = true;
}
return new \yii\data\ArrayDataProvider([
// ArrayDataProvider here takes the actual data source
'allModels' => $this->getData(),
'sort' => [
// we want our columns to be sortable:
'attributes' => ['name', 'code'],
],
]);
}
/**
* Here we are preparing the data source and applying the filters
* if _filtered property is set to true.
*/
protected function getData()
{
$data = [
['name' => 'Paul', 'code' => 'abc'],
['name' => 'John', 'code' => 'ade'],
['name' => 'Rick', 'code' => 'dbn'],
];
if ($this->_filtered) {
$data = array_filter($data, function ($value) {
$conditions = [true];
if (!empty($this->name)) {
$conditions[] = strpos($value['name'], $this->name) !== false;
}
if (!empty($this->code)) {
$conditions[] = strpos($value['code'], $this->code) !== false;
}
return array_product($conditions);
});
}
return $data;
}
}
The filtering in this example is handled by the array_filter function. Both columns are filtered "database LIKE"-style - if column value contains the searched string the data array row is not removed from the source.
To make it work like and conditions in ActiveDataProvider we put boolean result of every column check in the $conditions array and return product of that array in array_filter.
array_product($conditions) is equivalent of writing $conditions[0] && $conditions[1] && $conditions[2] && ...
This all results in the filterable and sortable GridView widget with two columns.

Categories