How to add rows with unique column to the database? - laravel

I have a table with unique column, it consists of 8 characters of random English letters and numbers.
I need to be able to seed the table by adding more data to the existing data.
I am supposed to enter a given amount of rows.
I tried using Factories and Seeders but then realized it's not working as expected:
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
class Code extends Model
{
protected $table = 'codes';
}
protected $fillable = [
'code',
'is_valid'
];
Factory:
public function definition()
{
return [
'code' => $this->faker->regexify('[A-Z0-9]{8}')
];
}
Then I am supposed to add data by running:
$codes = Code::factory()->count(10000)->create();
and after that, if I run it multiple times I might end up with constraint violation:
// add some more data
$codes = Code::factory()->count(10000)->create();
$codes = Code::factory()->count(10000)->create();
What is an efficient way to be able to add given amount of rows and make sure they're unique?
All other questions I've seen here are similar, but not working for my case.

Change in your factory definition this line from this: 'code' => $this->faker->regexify('[A-Z0-9]{8}') to: $faker->unique()->regexify('[A-Z0-9]{8}');.

In your factory you can use the unique() faker modifier : https://fakerphp.github.io/#modifiers
public function definition()
{
return [
'code' => $this->faker->unique()->regexify('[A-Z0-9]{8}')
];
}
Edit: when running the factory with existing data, you can check if the faked data already exists in the database like:
public function definition()
{
do {
$fakeCode = $this->faker->unique()->regexify('[A-Z0-9]{8}');
} while (DB::table('codes')->where('code', $fakeCode)->exists());
return [
'code' => $fakeCode
];
}

Related

Eloquent's First or Create does not consider the mutator before checking if the record exists?

Imagine that I have the following model:
class Link extends Model
{
protected $fillable = ['path', 'secret'];
public function setSecretAttribute($value)
{
$this->attributes['secret'] = sha1($value);
}
}
And the following code:
$link = Link::firstOrCreate(['path' => $someValue, 'secret' => $anotherValue]);
Is there something wrong with my code or "firstOrCreate" ignore the mutator when it will check if the registry already exists? If it ignores, I just have to add sha1 encryption to "anotherValue" to get the behavior I expect. But my question is, wouldn't that be redundant?
Not a lot of detail in this question, but if path is a unique identifier this is easily done with the second argument to Model::firstOrCreate().
If the model can not be found in the database, a record will be inserted with the attributes resulting from merging the first array argument with the optional second array argument.
So your code would look like this:
$link = Link::firstOrCreate(
['path' => $someValue],
['secret' => $anotherValue]
);
If both values are required to uniquely identify the record, you can simply do a conditional check:
$link = Link::where('path', $someValue)
->where('secret', sha1($anotherValue))
->first();
if (!$link) {
Link::create(['path' => $someValue, 'secret' => $anotherValue]);
}
Note that mutators apply only to database writes done via Eloquent methods. When you are reading the database, you must manually hash the value, as I've done.
To avoid manually hashing the value, the best option would be a query scope on the model like so:
class Link extends Model
{
protected $fillable = ['path', 'secret'];
public function setSecretAttribute($value)
{
$this->attributes['secret'] = sha1($value);
}
public function scopeSecretHashOf($query, $secret)
{
return $query->where('secret', '=', sha1($secret));
}
}
Which you could then use like this:
$link = Link::where('path', $someValue)
->whereSecretHashOf($anotherValue)
->first();
if (!$link) {
Link::create(['path' => $someValue, 'secret' => $anotherValue]);
}
In theory you could intercept and rewrite the query with a custom grammar but it would likely be more effort than it's worth.

Factory & Seeder for Pivot Table

I am trying to create a factory and seeder for a pivot table. I am trying to do so without creating a model just for the pivot table. However, in doing so, I am getting an error. Before I show the error, here are my files:
// CategoryNewsFactory.php
use App\Model;
use Faker\Generator as Faker;
$factory->define(Model::class, function (Faker $faker) {
DB::table('category_news')->insert(
[
'category_id' => factory(App\Category::class)->create()->id,
'news_id' => factory(App\News::class)->create()->id,
]
);
});
// CategoriesNewsSeeder.php
public function run()
{
factory(App\Model::class, 35)->create();
}
And here is my error message:
Error
Class 'App\Model' not found
at C:\laragon\www\startup-reporter\vendor\laravel\framework\src\Illuminate\Database\Eloquent\FactoryBuilder.php:232
228| if ($this->amount < 1) {
229| return (new $this->class)->newCollection();
230| }
231|
> 232| $instances = (new $this->class)->newCollection(array_map(function () use ($attributes) {
233| return $this->makeInstance($attributes);
234| }, range(1, $this->amount)));
235|
236| $this->callAfterMaking($instances);
1 C:\laragon\www\startup-reporter\vendor\laravel\framework\src\Illuminate\Database\Eloquent\FactoryBuilder.php:169
Illuminate\Database\Eloquent\FactoryBuilder::make([])
2 C:\laragon\www\startup-reporter\database\seeds\CategoriesNewsTableSeeder.php:14
Illuminate\Database\Eloquent\FactoryBuilder::create()
So I am wondering, what am I doing wrong and how can I fix it?
Thanks.
Firstly, you can create the model for your pivot table as far as Model class (in your case) doesn't exist at all. Secondly, you can attach/sync one model to another instead of creating a pivot record manually. Finally, you can use the following code inside factory(App\Category::class)
$factory->afterCreating(Category::class, function (Category $category, $faker) {
$category->news()->save(factory(News::class)->make());
});

Query Inside a redis model in yii2

I have created a redis model, which should store statistics like this:
<?php
namespace app\models;
use Yii;
use yii\base\Model;
use \yii\redis\ActiveRecord;
use \yii\redis\ActiveQuery;
class StatsModel extends ActiveRecord
{
public function attributes()
{
return ['id', 'hits', 'user', 'ua', 'ip','os'];
}
public function rules()
{
return [
['user', 'required'],
['user','string'],
['ip', 'required'],
['ip', 'integer'],
['hits', 'integer'],
['ua','string'],
['os','integer']
];
}
public static function total_user_hits($username)
{
$query = new ActiveQuery($this);
$query->find()->where('user = '.$user)->all();
}
public static function getDb()
{
return \Yii::$app->db_redis;
}
}
Now, i'm trying to make a static function, which i can use, to count all thi hits value for specific user in redis. I'm creating an $query = new ActiveQuery($this); each time in the function, but can how can initiliase just one copy of the query to always use it? If i do it like class property:
public $query = new ActiveQuery($this);
I get error expression is not allowed as field default value
You should not reuse existing query object (unless you want to make query with the same conditions) - ActiveQuery is mutable, it means that previous queries may change its state:
$query = new ActiveQuery(StatsModel::class);
$result1 = $query->andWhere('user = 1')->all(); // 1 result
$result2 = $query->andWhere('user = 2')->all(); // no results
Second query will not return anything, since it will create condition like WHERE user = 1 AND user = 2 which is always false.
If you're afraid about performance, you should not. Creating ActiveQuery object has negligible overhead. Creating objects in PHP is relatively cheap and ActiveQuery is quite lightweight - the most time consuming thing will be actual query to redis/db.

How to make shortest code?

I use the following code to insert multi array to database:
foreach($request->category as $k => $v){
$category[] = array(
"category_id" => $v,
"announcement_id" => $announcement->id
);
}
AnnouncementCategory::insert($category);
So, input data is POST array $request->category.
I need to refactoring this code
I tried this code:
$announcement->categories()->attach($request->category);
In model Announcement I have:
public function categories()
{
return $this->hasMany("App\AnnouncementCategory", "announcement_id", "id");
}
If you define in your Announcement model relationship like this:
public function categories()
{
return $this->belongsToMany(AnnouncementCategory::class);
}
you can do it like this:
$announcement->categories()->attach($request->category);
EDIT
I see you updated your question and added categories relationship. But looking at your code, AnnounceCategory is rather pivot table, so you should use belongsToMany as I showed instead of hasMany
You can do it in one line if the request matches the columns:
AnnouncementCategory::insert($request->all());
Then in your AnnouncementCategory model, make sure you declare the protected $fillable array where you specify which field could be populated.

Laravel - defining the right relationship

User Table: has zone_id field in it.
Zones table: has world_id field in it.
Each user can be in one zone, for example, zone_id = 1
Each zone belongs to one world, for example - world_id = 5
My desired output is returning user zone and world info.
This is how I can make it without any relationship set:
$zone = Zone::find($user->zone_Id);
$world = World::find($zone->world_id);
$data = $user;
$data['zone'] = $zone;
$data['zone']['world'] = $world;
My question is.. I'm sure relationship can be used for a cleaner code, but I'm not sure how to set it up.
Should I stick with the current code or define a relationship?
If the answer for 1 is define a relationship, Any help of what's the right relationship between these 3 models?
Solution 1:
`public function getZone(Request $request)
{
$token = $request->input('token');
$user = JWTAuth::toUser($token);
// Simplest example using relationships
$userWithZone = User::with('zone.world')->find($user->id); // You'll get the `zone` relationship info here, too
return $userWithZone;
}`
Error: returns "Call to a member function getQuery() on null"
Here's an example of how you can achieve this with Eloquent Relationships.
// User.php
class User
{
public function zone()
{
return $this->belongsTo(Zone::class);
}
}
// Zone.php
class Zone
{
public function world()
{
return $this->belongsTo(World::class);
}
}
Simplest example using relationships
$user = User::with('zone.world')->find($id); // You'll get the `zone` relationship info here, too
You can get more complex with your relationships if you want
$user = User::with(['zone' => function($query) {
$query->with('world')
->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
or even...
$user = User::with(['zone' => function($query) {
$query->with(['world' => function($query2) {
$query2->select('id', 'world_name');
}])->select('id', 'zone_name', 'world_id');
})->select('username', 'zone_id')->find($id);
Your resulting $user will look something like:
'user' => [ // This is a Collection
'username',
'email',
'zone_id',
'zone' => [ // This is a Collection
'id',
'zone_name',
'world_id',
'world' => [ // This is a Collection
'id',
'world_name'
]
]
];

Resources