I'm trying to seed a MySql table using Laravel seeder class. The problem is that it is not assigning the id field (which is an incremental) value as I set. In the seeder class I got:
public function run()
{
Patropi\Entidade::create([
'id' => '0',
'nome' => 'entidade 0',
'cpfcnpj' => '12345678901'
]);
Patropi\Fornecedor::create([
'id' => '0',
'prioridade' => '0'
]);
}
On the database, the Fornecedor table id is a foreign key which references the Entidade id, that's why I want to have the same Id on both. The problem is, when it inserts on Entidade it doesn't put it as id = 0 but instead it gives the value of the last incremental + 1. How to force laravel to insert 0 there instead? Thanks in advance.
The reason why it happens is Guarding Attributes. The attributes listed in $guarded array are protected from changes, what means Laravel will ignore all your attempts to modify them. Sometimes this approach is also known as Mass Assignment Protection.
By defualt, Laravel protects "id", so you can't and don't need to set them manually, however there's a way how to stop this default behavior. They key is in unguard() method.
Here's how you can fix your code:
public function run()
{
Patropi\Entidade::unguard();
Patropi\Entidade::create([
'id' => '0',
'nome' => 'entidade 0',
'cpfcnpj' => '12345678901'
]);
Patropi\Fornecedor::create([
'id' => '0',
'prioridade' => '0'
]);
}
Storing NULL or 0 in the auto-incrementing field is one of the triggers to assign the next incremental value.
From the MySQL docs:
You can also explicitly assign 0 to the column to generate sequence numbers.
If you want to disable this functionality, you can turn on the NO_AUTO_VALUE_ON_ZERO sql mode. This will change MySQL so that only assigning NULL will assign the next incremental value, and will allow you to store 0 in the field. From the docs:
NO_AUTO_VALUE_ON_ZERO affects handling of AUTO_INCREMENT columns. Normally, you generate the next sequence number for the column by inserting either NULL or 0 into it. NO_AUTO_VALUE_ON_ZERO suppresses this behavior for 0 so that only NULL generates the next sequence number.
This mode can be useful if 0 has been stored in a table's AUTO_INCREMENT column. (Storing 0 is not a recommended practice, by the way.)
If you don't explicitly need the id to be 0, then I would not try to assign 0. If the id doesn't matter, then you can do this:
public function run()
{
$entidade = Patropi\Entidade::create([
'nome' => 'entidade 0',
'cpfcnpj' => '12345678901'
]);
Patropi\Fornecedor::create([
'id' => $entidade->id,
'prioridade' => '0'
]);
}
Or, if you have the Eloquent relationships setup:
public function run()
{
$entidade = Patropi\Entidade::create([
'nome' => 'entidade 0',
'cpfcnpj' => '12345678901'
]);
// assumes fornecedor() defines a hasone/hasmany relationship.
$entidade->fornecedor()->create([
'prioridade' => '0'
]);
}
Related
I have this table that contains my site settings:
I would want to update all records at once with data that comes from a form.
The data looks like this:
$data = [
"brand" => "bbb"
"mail" => "kontakt#aaa.pl"
"phone" => "111"
"site-name" => "test"
];
Now I would like to update that with key of the associative array and with it value.
I tried:
DB::table('settings')->update($data);
But there is an error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'brand' in 'field list' (SQL: update `settings` set `brand` = bbb, `mail` = kontakt#aaa.pl, `phone` = 111, `site-name` = test)
Obviously it thinks that brand is a column name.
So I transformed the $data to this array:
$data = [
0 => [
"name" => "brand"
"value" => "bbb"
]
1 => [
"name" => "mail"
"value" => "kontakt#aaa.pl"
]
2 => [
"name" => "phone"
"value" => "111"
]
3 => [
"name" => "site-name"
"value" => "test"
]
];
and now the error is:
SQLSTATE[42S22]: Column not found: 1054 Unknown column '0' in 'field list' (SQL: update `settings` set `0` = {"name":"brand","value":"bbb"}, `1` = {"name":"mail","value":"kontakt#aaa.pl"}, `2` = {"name":"phone","value":"111"}, `3` = {"name":"site-name","value":"test"})
So now it thinks that index of each row in array is column name and at this place i have no idea how to do this...
Can anyone help me please?
I came to this solution although i think it looks ugly and there should be a better way to do this, if no better answers will be given i will mark my answer as correct
$data = [
"brand" => "bbb"
"mail" => "kontakt#aaa.pl"
"phone" => "111"
"site-name" => "test"
];
foreach($data as $key=>$d) {
DB::table('settings')->where('name','=',$key)->update(['value' => $d]);
}
Your table looks something like you can only take a set of data and update it with time. Why because there are no foreign key relations.
if that is the case then why not have brand_name, mail, phone and site_name for settings columns,
Your migrations
public function up()
{
Schema::create('settings', function (Blueprint $table) {
$table->string('brand_name');
$table->string('mail');
$table->string('phone');
$table->string('site_name');
$table->timestamps();
});
}
In your model?
protected $fillable = ['brand_name', 'mail', 'phone', 'site_name'];
or
protected $guarded = [];
In your controller
public function method(Request $request)
{
// You can also abstract this in to a custom request class
$request->validate([
'brand_name' => 'required',
'mail' => 'required',
'phone' => 'required',
'site_name' => 'required',
]);
// Add data if record doesn't exist, update when it does
// To use the validated method on request all needed fields must be required
Settings::updateOrCreate($request->validated())
}
Should in case I was wrong you can still check out Laravel Mass Update
https://laravel.com/docs/8.x/eloquent#mass-updates
Laravel Upserts
https://laravel.com/docs/8.x/eloquent#upserts
You can use upsert to update multiple records
First transform data to add column name using collection
$data=collect($data)->transform(function ($value,$name){
return ["name"=>$name,"value"=>$value];
})->values()->toArray();
and here i used model
Setting::upsert($data,["name"],["value"]);
To understand Upserts
Upsert perform multiple "upserts" in a single query
first argument consists of the values to insert or update
second argument lists the column(s) that uniquely identify records within the associated table.
third and final argument is an array of the columns that should be updated if a matching record already exists in the database.
Also important point
All databases systems except SQL Server require the columns in the
second argument provided to the upsert method to have a "primary" or
"unique" index.
It means in your mysql table settings column name to be unique index or else it will insert as new row
Ref:https://laravel.com/docs/8.x/eloquent#upserts
I have 1 table named status with 3 conditions, namely completed, postponed, and canceled. I want to change these 3 conditions into a number, for example finish = 1, postponed and canceled = 0. What I ask is how do I convert the data into a number in laravel (php)
Assuming you have that in a model Status and that the fields are stored as booleans you could do something like
class Status
{
protected $casts = [
'completed' => 'integer',
'postponed' => 'integer',
'canceled' => 'integer',
];
}
This will work simply because (int) true is 1 and (int) false is 0. For a more general solution you could define accessors as described here: https://laravel.com/docs/7.x/eloquent-mutators#defining-an-accessor
I have created a yii2 (v2.0.6) migration for a simple MySQL (v5.6.21) table. Everything works, except that I cannot figure out how to AUTO_INCREMENT the primary key. The problem seems to be that I am using a small integer rather than the more standard long integer datatype. Here is my migration code:
$this->createTable('{{%status}}', [
'id' => $this->smallInteger(8)->unique(),
//'id' => $this->primaryKey(11),
'description' => $this->string(20),
]);
$this->addPrimaryKey('','status','id');
I could get around the problem by using the ->primaryKey() method, which is commented out in line 3 above, but then yii creates a long integer datatype, and I am trying to avoid that. Any insight into the problem will be much appreciated.
If it is critical to have that column type, you can always change it:
$this->createTable('{{%status}}', [
'id' => $this->primaryKey(11),
'description' => $this->string(20),
]);
$this->alterColumn('{{%status}}', 'id', $this->smallInteger(8).' NOT NULL AUTO_INCREMENT');
(I've tested this with MySQL - it works)
However, like #scaisEdge says, it's usually not worth the troble.
Why not a simply primaryKey?, the format for integer(8) , integer(11) or primary key is always the same is always an integer long
then or you need a small int (max 5 digit) or you can use the normal $this->primaryKey() because
SMALLINT is for storage of 2 byte (value -32768 32767) an then
smallInteger(8) is not coherent. the numer 8 is for output not for store format. If you want 8 digit you need at least
INT of 4 byte -2147483648 2147483647 or more
$this->createTable('posts', [
'post_id' => "bigint(20) unsigned NOT NULL AUTO_INCREMENT",
'loc_id' => $this->integer(10)->unsigned()->notNull(),
"PRIMARY KEY (`post_id`,`loc_id`)",
], 'ENGINE=InnoDB DEFAULT CHARSET=utf8');
This works for me
$this->createTable('new_table',[
'id' => Schema::TYPE_PK.' NOT NULL AUTO_INCREMENT',
'name' => Schema::TYPE_STRING,
'age' => Schema::TYPE_INTEGER
]);
However you can simply use the below style and Yii will replace the 'pk' type of id based on your DBMS. for MYSQL it will be int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
$this->createTable('new_table',[
'id' => 'pk',
'name' => Schema::TYPE_STRING,
'age' => Schema::TYPE_INTEGER
]);
Another (imo more readable) approach is:
$this
->integer()
->unsigned()
->notNull()
->append('AUTO_INCREMENT');
I am using PhPUnit, FactoryMuffin and Faker for testing in Laravel, with a PostgreSQL db. In the previous version of FactoryMuffin (Zizaco\FactoryMuff) I could assign null values to columns both in the static factory array and when calling FactoryMuff::create.
However this no longer works - if I use the following define:
FactoryMuffin::define('MyModel', array(
'name' => 'word',
'empty' => null,
'another' => 'word'
));
when I call FactoryMuffin::create instead of passing NULL to the SQL INSERT statement it leaves the value blank, so I get:
INSERT INTO my_table ("name", "empty", "another") VALUES ('Ralph', , 'Someone');
which PGSQL doesn't allow. The same thing happens using
FactoryMuffin::create('MyModel', array('empty' => null));
Any ideas how to get round this, beyond instantiating the model and then assigning null to the field?
Since FactoryMuffin 2.1 (and 3.*) you can take advantage of the callback functionality, e.g.:
FactoryMuffin::define('MyModel', array(
'name' => 'word',
'empty' => null,
'another' => 'word'
))->setCallback(function ($object, $saved) {
$object->empty = null;
});
In FactoryMuffin 2.1 the callback is set as the third parameter of the define:
FactoryMuffin::define('MyModel', array(
'name' => 'word',
'empty' => null,
'another' => 'word'
), function ($object, $saved) {
$object->empty = null;
});
For storing user-defined bookmarks on some site, I have a table with the composite key:
CREATE TABLE bookmarks (
user_id int not null,
book_id int not null,
page_id int,
...
);
CREATE UNIQUE INDEX ON bookmarks(user_id, book_id, page_id);
Note, that page_id can be NULL, and user_id and book_id can not. When page_id is null, the bookmark is set for the whole book, otherwise - for certain page.
Corresponding ActiveRecord class defines some relations:
public function relations() {
return array(
"user" => array(self::BELONGS_TO, "User", "user_id"),
"book" => array(self::BELONGS_TO, "Book", "book_id"),
"page" => array(self::BELONGS_TO, "Page", "page_id"),
);
}
and a primaryKey() method::
public function primaryKey() {
return array("user_id", "book_id", "orig_id");
}
Now I want to get all bookmarks for the whole book for some user. So, I do:
$bookmarks = Bookmark::model()->findAll(array(
"condition" => "t.user_id = :user_id AND t.page_id IS NULL",
"params" => array(":user_id" => 1),
));
It works great, returning 4 records, but obviously, I want to use some related data from books table:
$bookmarks = Bookmark::model()->findAll(array(
"with" => "book",
"condition" => "t.user_id = :user_id AND t.page_id IS NULL",
"params" => array(":user_id" => 1),
));
and now I get 0 records (count($bookmarks) == 0), although the generated SQL statement selects all needed data, it is just not recognised by CActiveRecord class. Another weird thing is, that when I try to fetch all page bookmarks, everything is okay:
$bookmarks = Bookmark::model()->findAll(array(
"with" => "book",
"condition" => "t.user_id = :user_id AND t.page_id IS NOT NULL",
"params" => array(":user_id" => 1),
));
What am I doing wrong? How to make expression in the second example return some data? PHP 5.4.0, Yii 1.1.8, PostgreSQL 9.1.4, +32°C outside.
Your issue may be solved in this way:
Add a surrogate PK to the Bookmark table (e.g. auto-incremental sequence).
Remove primaryKey() function.
You may also use more convenient code:
public function relations()
{
return array(
//...
'wholeBook' => array(self::BELONGS_TO, 'Book', 'book_id', 'on'=>"page_id IS NULL", 'joinType'=>'INNER JOIN'),
//...
);
}
Then in the controller just:
$bookmarks = Bookmark::model()->with('wholeBook')->findAllByAttributes(array('user_id'=>1));
As a matter of fact, with UNIQUE key instead of PRIMARY and primaryKey() you use hack to evade impossibility of using NULL column in composite PKs.
ActiveRecord is a sort of ORM, so any logic in SQL must be translated into AR (Yii automatically loads DB schemas), and it must be correct logic.
If I were you I'd normalize SQL to something like this:
Because of those are two different types of bookmarks with different relations. You should join them only in view logic, not in relation structure. IMHO