Laravel Factory: Manual Increment of Column - php

For the following factory definition, the column order needs to be sequential. There is already a column id that is auto-incremented. The first row's order should start at 1 and each additional row's order should be the next number (1,2,3, etc.)
$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
return [
'user_id' => App\User::inRandomOrder()->first()->id,
'command' => $faker->word,
'content' => $faker->sentence,
'order' => (App\AliasCommand::count()) ?
App\AliasCommand::orderBy('order', 'desc')->first()->order + 1 : 1
];
});
It should be setting the order column to be 1 more than the previous row, however, it results in all rows being assigned 1.

Here's something that might work.
$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
static $order = 1;
return [
'user_id' => App\User::inRandomOrder()->first()->id,
'command' => $faker->word,
'content' => $faker->sentence,
'order' => $order++
];
});
It just keeps a counter internal to that function.
Update:
Laravel 8 introduced new factory classes so this request becomes:
class AliasCommandFactory extends Factory {
private static $order = 1;
protected $model = AliasCommand::class;
public function definition() {
$faker = $this->faker;
return [
'user_id' => User::inRandomOrder()->first()->id,
'command' => $faker->word,
'content' => $faker->sentence,
'order' => self::$order++
];
}
}

The answer by #apokryfos is a good solution if you're sure the factory model generations will only be run in sequential order and you're not concerned with pre-existing data.
However, this can result in incorrect order values if, for example, you want to generate models to be inserted into your test database, where some records already exist.
Using a closure for the column value, we can better automate the sequential order.
$factory->define(App\AliasCommand::class, function (Faker\Generator $faker) {
return [
'user_id' => App\User::inRandomOrder()->first()->id,
'command' => $faker->word,
'content' => $faker->sentence,
'order' => function() {
$max = App\AliasCommand::max('order'); // returns 0 if no records exist.
return $max+1;
}
];
});
You almost had it right in your example, the problem is that you were running the order value execution at the time of defining the factory rather than the above code, which executes at the time the individual model is generated.
By the same principle, you should also enclose the user_id code in a closure, otherwise all of your factory generated models will have the same user ID.

To achieve true autoIncrement rather use this approach:
$__count = App\AliasCommand::count();
$__lastid = $__count ? App\AliasCommand::orderBy('order', 'desc')->first()->id : 0 ;
$factory->define(App\AliasCommand::class,
function(Faker\Generator $faker) use($__lastid){
return [
'user_id' => App\User::inRandomOrder()->first()->id,
'command' => $faker->word,
'content' => $faker->sentence,
'order' => $faker->unique()->numberBetween($min=$__lastid+1, $max=$__lastid+25),
/* +25 (for example here) is the number of records you want to insert
per run.
You can set this value in a config file and get it from there
for both Seeder and Factory ( i.e here ).
*/
];
});

In Laravel 9 (and possibly some earlier versions?), there's a pretty clean way to make this happen when you're creating models (from the docs):
$users = User::factory()
->count(10)
->sequence(fn ($sequence) => ['order' => $sequence->index])
->create();
If you'd like to start with 1 instead of 0:
$users = User::factory()
->count(10)
->sequence(fn ($sequence) => ['order' => $sequence->index + 1])
->create();

The solution also solves already data on table conditions:
class UserFactory extends Factory
{
/**
* #var string
*/
protected $model = User::class;
/**
* #var int
*/
protected static int $id = 0;
/**
* #return array
*/
public function definition()
{
if ( self::$id == 0 ) {
self::$id = User::query()->max("id") ?? 0;
// Initialize the id from database if exists.
// If conditions is necessary otherwise it would return same max id.
}
self::$id++;
return [
"id" => self::$id,
"email" => $this->faker->email,
];
}
}

Related

How to filter a gridview table in yii2?

I have two gridview tables currently, one shows all the data and the other i want it to show only data where id = 2. Is it possible to filter only one table without affecting the other? I know I can filter from the search model but that will affect all tables and i want it to affect only one.
Can i have two dataprovider?
This is the code in my search model:
class JobPlanningSearch extends JobPlanning
{
/**
* #inheritdoc
*/
public function rules()
{
return [
[['id', 'priority', 'employer_id', 'client_id', 'status', 'activity'], 'integer'],
[['job_description', 'impediment', 'date', 'estimated_time', 'due_date'], 'safe'],
];
}
/**
* #inheritdoc
*/
public function scenarios()
{
// bypass scenarios() implementation in the parent class
return Model::scenarios();
}
/**
* Creates data provider instance with search query applied
*
* #param array $params
*
* #return ActiveDataProvider
*/
public function search($params)
{
$query = JobPlanning::find();
// add conditions that should always apply here
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
$this->load($params);
if (!$this->validate()) {
// uncomment the following line if you do not want to return any records when validation fails
// $query->where('0=1');
return $dataProvider;
}
// grid filtering conditions
$query->andFilterWhere([
'id' => $this->id,
'priority' => $this->priority,
'client_id' => $this->client_id,
'employer_id' => $this->employer_id,
'estimated_time' => $this->estimated_time,
'status' => $this->status,
'activity' => $this->activity,
//'actual' => $this->actual,
//'actual' => 1,
]);
$query->andFilterWhere(['like', 'job_description', $this->job_description]);
$query->andFilterWhere(['like', 'activity', $this->activity]);
return $dataProvider;
}
You need two dataproviders, like this:
$searchModelOne = new JobPlanningSearch();
$dataProviderOne = $searchModelOne->search(Yii::$app->request->queryParams);
$dataProviderOne->pagination->pageParam = 'dp-one-page'; //set page param for first dataprovider
$searchModelTwo = new JobPlanningSearch();
searchModelTwo->id = 2; // set id = 2 in second dataprovider
$dataProviderTwo = $searchModelTwo->search(Yii::$app->request->queryParams);
$dataProviderTwo->pagination->pageParam = 'dp-two-page'; //set page param for second dataprovider
You need to use two searchModels, one for each GridView and of course two data providers.
Also you need to change the formName attribute of one of the searchModels to avoid filtering to affects both grids.
And the in the controller, when passing parameters to the search() method of the modified searchModel, pass the params in the new name you assigned to formName in this searchModel.
You will have to manipulate the DataProvider in the controller.
$searchModel = new JobPlanningSearch();
$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
$dataProvider->query->andWhere(['=','id',2]);
remove it from the search function in JobPlanningSearch.
$query->andFilterWhere([
/* 'id' => $this->id, */ // you must remove it from here
'priority' => $this->priority,
'client_id' => $this->client_id,
'employer_id' => $this->employer_id,
'estimated_time' => $this->estimated_time,
...
]);
In this case, the ID will always be 2.
But if you want to have a default value (the first time) and allow the user to change it, you should skip step 2. And add a condition in the controller:
...
if(count(Yii::$app->request->queryParams) == 0){
$dataProvider->query->andWhere(['=','id',2]);
}
This is just an example, you should check that the ID field has not been sent in the queryparams.

Laravel 8.x, make fake model reationships?

I'm trying to run a faker factory for relationships, but the field always returns NULL. How do fake a model relationship without hitting the database?
I have a Map factory with a one-to-one relationship to a parent Event table. I need to fake this relationship for unit testing:
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'event' => 'faker.' . join('_', $this->faker->words),
'category' => $this->faker->word,
'sub_category' => $this->faker->word,
'priority' => $this->faker->randomElement(['normal', 'high']),
'event' => Event::factory()->makeOne(),
];
}
This returns a fake model, but event is null, from the debugger:
result = {array} [5]
event = "faker.eum_voluptatibus_aut"
category = "libero"
sub_category = "aut"
priority = "high"
event = null
I tried using states, but the same thing happens:
public function disabled()
{
return $this->state([
'event' => Event::factory()->makeOne(['enabled' => false]),
]);
}
The object is returned with an empty event value. I need a faker object I can transverse down into: if ($object->event->enabled) [...]. How do I generate fake model relationships?
If you are using Laravel 8.x you must consider using methods used on the docs, it must look like that :
/**
* Define the model's default state.
*
* #return array
*/
public function definition()
{
return [
'event' => 'faker.' . join('_', $this->faker->words),
'category' => $this->faker->word,
'sub_category' => $this->faker->word,
'priority' => $this->faker->randomElement(['normal', 'high']),
'event_id' => Event::factory(),
];
}
/**
* Indicate that the map is disabled.
*
* #return \Illuminate\Database\Eloquent\Factories\Factory
*/
public function disabled()
{
return $this->state([
'event_id' => Event::factory()->create(['enabled' => false]),
]);
}
The only solution I found, so far, is to manually set the event key myself in my tests. It's not the ideal or elegant solution.
$fieldMap = Map::factory()->makeOne();
$fieldMap->event = Event::factory(['enabled' => false])->makeOne();
I don't like this approach. Why can't I define factories within factories?

laravel database seeder adding add foreign key id randomly to seeds

I am trying to create seeders for testing purposes. I have users that belongs to a room via a room id, these rooms are created via a room seeder, in my users seeder, I create a user and update the room_id attribute like this,
factory(App\User::class, 150)->create([
'host' => false,
'room_id' => App\Room::inRandomOrder()->first()->id
]);
My problem is that all users generated here, all get the same room id, how can a truly get a random room id from the database and use it in my seeder?
I had the same problem with seeding. The problem is that, you are overriding the factory's default model attributes by passing an array of "values". You are passing the value of App\Room::inRandomOrder()->first()->id to the create method. So you would have all users with the same room_id.
To solve this issue, in laravel 8, you can move the 'room_id' => Room::inRandomOrder()->first()->id to your UsersFactory definition:
class UsersFactory {
...
public function definition()
{
return [
'room_id' => Room::inRandomOrder()->first()->id
];
}
...
}
And create users like this,
App\User::factory()->count(150)->create([
'host' => false
]);
In older version of laravel, define your factory as below:
$factory->define(App\User::class, function ($faker) use ($factory) {
return [
'room_id' => Room::inRandomOrder()->first()->id
];
});
And create users like this,
factory(App\User::class, 150)->create([
'host' => false,
]);
Try:
App\Room::all()->random()->id
/**
* Run the database seeds.
*
* #return void
*/
public function run()
{
$users = factory(\App\User::class, 150)->create([
'host' => false,
'room_id' => $this->getRandomRoomId()
]);
}
private function getRandomRoomId() {
$room = \App\Room::inRandomOrder()->first();
return $room->id;
}
Try this one. It works for me. Hopefully it works for you.
Try this one. Also, make sure that you have multiple auto incremented room entries in the room table.
$factory->define(App\User::class, function ($faker) use ($factory) {
return [
'host' => false,
'room_id' => $factory->create(App\Room::class)->id
];
});

Passing a factory into another in Laravel 5

I am trying to generate a test on Laravel.
What I was trying is to create a fictitious position name, then add 10 people for this position.
PositionsFactory.php
$factory->define(App\Position::class, function (Faker $faker) {
return [
'p_id' => $faker->unique()->randomNumber($nbDigits = 8),
'name' => $faker->word,
'org' => $faker->word,
'user_id' => 1
];
});
Here is my EmployeeFactory.php
$factory->define(App\Employee::class, function (Faker $faker) {
return [
'FirstName' => $faker->name,
'LastName' => $faker->lastName,
'pid' => $position->p_id,
'org'=> $position->org,
'user_id' => 1,
];
});
Well here is one my my trials but it did not work
for ($i=0; $i < 5; $i++ ){
$position = factory('App\Position')->create();
factory('App\Employee',10)->create(
'pid' => $position->pid,
'org' => $position->org
);
}
I am trying to loop for 5 times and for each loop I want to create 10 employees with the same position Id. But obviously I am missing something.
I tried adding $position in the Employee factory, which works great.
$factory->define(App\Employee::class, function (Faker $faker) {
$position = factory('App\Position')->create();
return [
'FirstName' => $faker->name,
'LastName' => $faker->lastName,
'pid' => $position->p_id,
'org'=> $position->org,
'user_id' => 1,
];
});
Is there a way to make something like,
$factory('App\Position',5)->create($factory('App\Employee',10));
Maybe I am missing something with call back but kinda lost. Any help would be appreciated. Thank you!
I think you might be looking for the each method which can be called after create:
// Generate 5 positions and execute a callback
// function for each position created
factory(App\Position::class, 5)->create()->each(function ($position) {
// In the callback, generate 10 employees
// and manually override the foreign key
factory(App\Employee::class, 10)->create([
'pid' => $position->id
]);
});
Further information on each and handling relationships: https://laravel.com/docs/5.6/database-testing#relationships.
Hope it helps!
You can create them separatly and loop through collections.
$positions = factory('App\Position', 3)->make();
foreach ($positions as $position){
$employes = factory('App\Employee', 3)->make();
foreach ($employes as $employee){
$employee->p_id = $position->id;
//etc.. watever you want to connect
}
}
now you have 1 collection of positions and 1 collection of employes devided of the positions
note that the make method does not save to database you need to manually save them
you could also change your factory as is stated in the documentation https://laravel.com/docs/5.6/database-testing#using-factories
yours would look like:
$factory->define(App\Employee::class, function (Faker $faker) {
return [
'FirstName' => $faker->name,
'LastName' => $faker->lastName,
'org'=> $position->org,
'user_id' => 1,
'pid' => function () {
return factory('App\Position')->create()->id;
}
];
});
This will create a position for each user the factory creates.
You could also use existing eloquent models instead if you have existing positions.

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