Laravel Eloquent Relationships not work with two pivot table - php

Hello everyone am newly in laravel and i am trying to get data.using state ID please explain me with all Model with relationship each-other with example i have tables like this
1- states table
1- id
2-name
2- cities table
1- id
2-name
3- state_cities pivot table
1-id
2-sate_id
3-city_id
4- locations table
1-id
2-name
5- city_locations pivot table
1-id
2-city_id
3-location_id
6- pincodes table
1-id
2-pincode
7- location_pincodes table
1-id
2-location_id
3-pinecode_id
And this is my Controller
$states_with_cities = $states_with_cities->load(['cities.cityName','location.locationName'])->where('id',1)->get();
$states_with_cities->transform(function($states_with_cities) {
return [
'state_id' => $states_with_cities->id,
'state_name' => $states_with_cities->name,
'cities' => $states_with_cities->cities->map(function($cities,$location) {
return [
'city_id' => $cities->city_id,
'city_name' => $cities->cityName->name,
'location' => $location->locationName->map(function($locationName) use($location) {
return [
'location_id' => $location->location_id,
'location_name' => $locationName->locationName->name
];
})
];
}),
];
});
and that is error which is am geting
"message": "Trying to get property of non-object",
"exception": "ErrorException",
"file": "D:\\xampp\\htdocs\\samudaay-backend\\app\\Http\\Controllers\\API\\PincodeController.php",
"line": 32,

$states_with_cities = $states_with_cities->load(['cities.cityName','location.locationName'])->where('id',1)->get();
$states_with_cities->transform(function($states_with_cities) {
return [
'state_id' => $states_with_cities->id,
'state_name' => $states_with_cities->name,
'cities' => $states_with_cities->cities->map(function($cities,$location) {
// Location is the 'key' of the object in the collection. So it probably will be something like '0' or '1'.
return [
'city_id' => $cities->city_id,
'city_name' => $cities->cityName->name,
'location' => $location->locationName->map(function($locationName) use($location) {
//What you actually do here is: 0->locationName->map(...). This will result in your error
return [
'location_id' => $location->location_id,
'location_name' => $locationName->locationName->name
];
})
];
}),
];
});
$location in the first map function is the key of the object it is iterating at the moment. (see: https://laravel.com/docs/5.6/collections#method-map)
So on line 32 you are trying to call a property on the key variable (which will probably be '0' or '1' or something.) As that is not an object, it will result in the error you get.
Also, trying to map the locationName property is not going to work as expected. locationName is a property and not an eloquent collection.
You should probably try it like this:
'location' => [
'location_id' => $location->location_id,
'location_name' => $location->name
];
})

Related

Laravel Faker Factory Relationship

I have two factories one for categories and another for products. When I run the factory I want to create x number of products for each category generated. how would I write the code to product this?
the definition for the categories is written as this:
return [
'name' => $this->faker->word,
'slug' => Str::slug($this->faker->unique()->word, '-'),
];
and the definition for the product is written as such:
return [
'category_id' => 1, //instead of 1 the category id used i want to be random
'name' => $this->faker->word,
'slug' => Str::slug($this->faker->unique()->word, '-'),
'description' => $this->faker->paragraph,
'price' => $this->faker->randomFloat(2, 0, 10000),
'is_visible' => 1,
'is_featured' => 1
];
as you can see I hardcoded the category_id, I wasnt too sure how to have it automatically generate and create a product per category that exists. I have the factory for the category written as this, to create 10 items
Category::factory()
->count(10)
->create();
I tried this for trial and error thinking it would work but I get an error that category_id cannot be null .
Product::factory()
->has(Category::factory()->count(2))
->count(20)
->create();
$factory->define(Product::class, function (Faker $faker) {
return [
'category_id' => factory(Category::class), //instead of 1 the category id used i want to be random
'name' => $this->faker->word,
'slug' => Str::slug($this->faker->unique()->word, '-'),
'description' => $this->faker->paragraph,
'price' => $this->faker->randomFloat(2, 0, 10000),
'is_visible' => 1,
'is_featured' => 1
];
});
By setting the attribute to an instance of factory() Laravel will lazily create that model as well and automatically associate it
I am using kind of different syntax, but I think it will work / you can change it
In your Category.php model
public function products() {
return $this->hasMany(Product::class);
}
In seeder
factory(App\Category::class, 10)->create()->each(function($c) {
$c->products()->save(factory(App\Product::class)->make());
}); // each Category will have 1 product
Laravel Database Testing Relationships
You simply need to pass a CategoryFactory to category_id.
return [
'category_id' => Category::factory(),
// ...
];
You can read more about factories here: https://laravel.com/docs/8.x/database-testing#defining-relationships-within-factories
If you want to create multiple products per each created category, you can do something like this:
// CategoryProductSeeder
$categories = Category::factory(50)->create();
$categories->each(function ($category) {
$categories->products()->saveMany(
Product::factory(10)->make()
);
});
this is what worked for me since I'm using laravel 8.
product definition:
return [
'category_id' => Category::factory(),
'name' => $this->faker->word,
'slug' => Str::slug($this->faker->unique()->word, '-'),
'description' => $this->faker->paragraph,
'price' => $this->faker->randomFloat(2, 0, 1000),
'is_visible' => 1,
'is_featured' => 1
];
seeder:
Product::factory()
->has(Category::factory())->count(50)
->create();
created 50 categories and 50 products. 1 category assigned to each product.

Yii2 Load schedule using unclead/yii2-multiple-input

I am using unclead / yii2-multiple-input widget.
I want to generate different number of rows with values from my database.
How can i do this?
I can design my columns in view and edit data manualy after page generated. But miss how to program the number of rows and its values in the view.
My code in view:
<?= $form->field($User, 'User')->widget(MultipleInput::className(), [
'min' => 0,
'max' => 4,
'columns' => [
[
'name' => 'name',
'title' => 'Name',
'type' => 'textInput',
'options' => [
'onchange' => $onchange,
],
],
[
'name' => 'birth',
'type' => \kartik\date\DatePicker::className(),
'title' => 'Birth',
'value' => function($data) {
return $data['day'];
},
'options' => [
'pluginOptions' => [
'format' => 'dd.mm.yyyy',
'todayHighlight' => true
]
]
],
]
])->label(false);
How can I make (for example) 8 rows with different values, and also have the ability to edit/remove/update some of them?
You need to look into the documentation as it says that you need to assign a separate field into the model which will store all the schedule in form of JSON and then provide it back to the field when editing/updating the model.
You have not added the appropriate model to verify how are you creating the field User in your given case above. so, i will try to create a simple example which will help you implement it in your scenario.
For Example.
You have to store a user in the database along with his favorite books.
User
id, name, email
Books
id, name
Create a field/column in you User table with the name schedule of type text, you can write a migration or add manually.
Add it to the rules in the User model as safe.
like below
public function rules() {
return [
....//other rules
[ [ 'schedule'] , 'safe' ]
];
}
Add the widget to the newly created column in ActiveForm
see below code
echo $form->field($model,'schedule')->widget(MultipleInput::class,[
'max' => 4,
'columns' => [
[
'name' => 'book_id',
'type' => 'dropDownList',
'title' => 'Book',
'items' => ArrayHelper::map( Books::find()->asArray()->all (),'id','name'),
],
]
]);
When saving the User model convert the array to JSON string.
like below
if( Yii::$app->request->isPost && $model->load(Yii::$app->request->post()) ){
$model->schedule = \yii\helpers\Json::encode($model->schedule);
$model->save();
}
Override the afterFind() of the User model to covert the json back to the array before loading the form.
like below
public function afterFind() {
parent::afterFind();
$this->schedule = \yii\helpers\Json::decode($this->schedule);
}
Now when saved the schedule field against the current user will have the JSON for the selected rows for the books, as many selected, for example, if I saved three books having ids(1,2,3) then it will have json like below
{
"0": {
"book_id": "1"
},
"2": {
"book_id": "2"
},
"3": {
"book_id": "3"
}
}
The above JSON will be converted to an array in the afterFind() so that the widget loads the saved schedule when you EDIT the record.
Now go to your update page or edit the newly saved model you will see the books loaded automatically.

Yii2 Kartik EditableColumn Dropdown Relation Returns wrong Value

I have an issue with a Gridview using kartik\grid\EditableColumn, after changing the value I am returned the wrong value for the column when it updates. I am returned the dropdown key/main table integer rather than the string contained in a linked table.
I have two tables
Leads - columns id and status_id
Related fields - model, field, related_value, related_value
The relation is based on in this case
model:"Leads",
field:"status_id",
related_id:status_id
I have the following relation in my model
public function getStatus()
{
return $this->hasOne(RelatedFields::className(), ["related_id" => "status_id"])->andOnCondition(["status.field" => "status_id", "status.model"=>"Leads"])->from(["status" => RelatedFields::tableName()]);
}
I also created the following as a test based on this link
public function getStatusValue()
{
return $this->status->related_value;
}
Here is the column code
[
'class' => 'kartik\grid\EditableColumn',
'attribute' => 'status_id',
'value'=>'status.related_value',
//'value' => function($model){ return $model->status->related_value; },
//'value' => function($model){ return $model->StatusValue; },
//'refreshGrid' => true,//Works but not nice
'vAlign'=>'middle',
'hAlign'=>'center',
'pageSummary' => true,
'readonly' => false,
'width'=>'10%',
'filter'=>Html::activeDropDownList($searchModel, 'status', ArrayHelper::map(RelatedFields::Find()->where(['model' =>"Leads","field"=>"status_id"])->all(), 'related_id', 'related_value'),['class' => 'form-control','prompt' => Yii::t('app', '')]),
'editableOptions'=> [
//'attribute'=>'status_id',
//'value'=>'status.related_value',
//'header' => 'profile',
//'format' => Editable::FORMAT_BUTTON,
'inputType' => Editable::INPUT_DROPDOWN_LIST,
'data'=> ArrayHelper::map(RelatedFields::Find()->where(['model' =>"Leads","field"=>"status_id"])->all(), 'related_id', 'related_value'),
]
],
Commented out are a number of lines in my attempts to fix the issue as well as combinations of them, however all result in the wrong value.
If for example I select the related value "New" which has a related_id 1, after the column has been updated I get the value 1 instead of "New".
When the table is first loaded/reloaded the value does show correctly.
I could reload the grid, but this seems wrong just to fix 1% of the data shown on the page.
I your model take a public variable $status_value
create an assigning value method
public function getStatusValue()(){
return $this->status_value= $this->status->related_value;
}
Now in Gridview use getStatusValueenter code heremethod with assigning value as below
use yii\helpers\Url;
$gridColumns = [
[
'class' => 'kartik\grid\EditableColumn',
'attribute' => 'status_value',
'pageSummary' => true,
'readonly' => false,
'value' => function($model){ return $model->statusValue; }, // assign value from getStatusValue method
'editableOptions' => [
'header' => 'status_value',
'inputType' => kartik\editable\Editable::INPUT_TEXT,
'options' => [
'pluginOptions' => [
]
]
],
],
];
If you follow Kartik guide, he suggest to add EditableColumnAction to better handle the editable column:
The EditableColumnAction offers a quick easy way to setup your
controller action for updating, saving and managing the EditableColumn
output from GridView. This action class extends from yii\rest\Action
and hence all properties available with yii\rest\Action are applicable
here. The basic setup of the column involves setting up the controller
action and the EditableColumn.
So you need to add an EditableColumnAction in your controller to handle the update of the model:
public function actions()
{
return ArrayHelper::merge(parent::actions(), [
'edit-lead' => [
'class' => EditableColumnAction::class,
'modelClass' => Leads::class
]
]);
}
In your GridView editable column configuration, include the above
controller action for processing the Editable within editableOptions.
For example
And in your column code you need to add the action to editableOptions property:
'editableOptions' => [
...
'formOptions' => ['action' => ['/leads/edit-lead']]
]
Now, according to the guide, you can add to your action the outputValue property:
'outputValue' => function (Leads $model) {
return $model->status->related_value;
}

laravel eloquent count with a groupby

If I'd have the following table structure
ID name type
1 John Admin
2 Johan Admin
3 Sam User
4 Rick User
How would I use Laravels Eloquent with a groupBy query, but still grab each record, and also sum up per type how many records each type has?
Example result would be as follow
[
'type' => 'Admin',
'records' => 2
'data' => [
0 => [
'name' => 'John'
],
1 => [
'name' => 'Johan'
]
]
],
[
'type' => 'User',
'records' => 2
'data' => [
0 => [
'name' => 'Sam'
],
1 => [
'name' => 'Rick'
]
]
]
Since you are querying all the users you should be doing the grouping after the query instead of grouping in query like:
$users = User::all()->groupBy('type');
Then you can do:
foreach ($allUsers as $type => $users) {
$type; // 'Admin'
$users->count(); // Record Count
foreach ($users as $user) {
$user->name; //
}
}
If you want the data EXACTLY like your example then it would be:
User::all()
->groupBy('type')
->map(function($users, $type) {
return [
'type' => $type,
'records' => $users->count(),
'data' => $users->pluck('name')
];
})
->values();
I think the best way to achieve this is to have a table with types related through hasMany() to your data.
Then you can use standard Eloquent tools without restructuring the data manually:
$data = Type::with('users')->withCount('users')->get();
Also, you can use DB::select('select * from xxx group by yyy') to execute the query.

Laravel Nested Relationship on Factory for Testing

I got a problem, i have to admit i don't find any solution.
I'm actually developping some testing for functionnalities and Factories are blocking me.
First I'm trying to add with factories an Entity called "Tasklist" which contains one or many "sections" which contains one or many "actions".
I have a 3 level deep relationship.
Here are my factories:
$factory->define(\App\V2\Models\Tasklist::class, function (\Faker\Generator $faker) {
return [
'id_course' => \App\V2\Models\Program::all()->random(1)->id,
'id_event' => \App\V2\Models\Stage::all()->random(1)->id,
'id_course_rounds' => \App\V2\Models\ProgramRound::all()->random(1)->id,
'name' => $faker->word,
'display_name' => $faker->word,
'color' => 0,
'key' => str_random(16),
'auto_active' => 1,
'status' => 1,
];
});
$factory->define(\App\V2\Models\TasklistSection::class, function (\Faker\Generator $faker) {
return [
'id_tasklist' => function(){
return factory(\App\V2\Models\Tasklist::class)->create()->id;
},
'number' => 1,
'title' => $faker->word,
'text' => $faker->text(100),
'status' => 1
];
});
$factory->define(\App\V2\Models\TasklistAction::class, function(\Faker\Generator $faker) {
return [
'id_tasklists_section' => factory(\App\V2\Models\TasklistSection::class)->create()->id,
'number' => rand(1, 10),
'title' => $faker->word,
'percent' => $faker->numberBetween(0, 100),
'status' => 1
];
});
In my testing class, i'm trying to generate a tasklist with 1 section with one action. The only way i found actually was something like that:
$task = factory(Tasklist::class, 2)->create()
->each(function($t){
$t->sections()->save(factory(TasklistSection::class)->create()
->each(function($s){
$s->actions()->save(factory(TasklistAction::class)->create());
})
);
});
To this code, if I delete the second each, it works, i got 2 tasklists with each 1 sections. In fact, the each is disturbing me.
I would like to create only one tasklist, with one or several sections with one or several actions on it.
But the each only accept Collection input the save method accepts only model input and not collection.
Does somebody have an idea how to deal with that ?
One approach can be this:
create task with sections and store them in the variable and then loop through each task section and add actions to it like this:
$tasklist = factory(App\Tasklist::class)->create();
$tasklist->sections()->saveMany(factory(App\TasklistSection::class, 3)->make());
foreach ($tasklist->sections as $section){
$section->actions()->saveMany(factory(App\TasklistAction::class, 3)->make());
}
this will work as expected.

Categories