Array to string conversion error in migration - laravel-5

In my laravel 5.8 I set json field :
Schema::create('vote_categories', function (Blueprint $table) {
$table->increments('id');
$table->string('meta_description', 255)->nullable();
$table->json('meta_keywords')->nullable();
$table->timestamp('created_at')->useCurrent();
and some init data in seeder :
DB::table( 'vote_categories' )->insert([
'id' => 1,
'name' => 'Classic literature',
'slug' => 'classic-literature',
'active' => true,
'in_subscriptions' => true,
'meta_description' => '',
'meta_keywords' => ['Classic literature'],
]);
and in model :
class VoteCategory extends MyAppModel
{
protected $table = 'vote_categories';
protected $primaryKey = 'id';
public $timestamps = false;
protected $casts = [
'meta_keywords' => 'array'
];
But running migration I got error :
$ php artisan migrate
Migration table created successfully.
...
Migrating: 2018_07_13_051201_create_vote_categories_table
ErrorException : Array to string conversion
at /mnt/_work_sdb8/wwwroot/lar/votes/vendor/laravel/framework/src/Illuminate/Support/Str.php:353
349|
350| $result = array_shift($segments);
351|
352| foreach ($segments as $segment) {
> 353| $result .= (array_shift($replace) ?? $search).$segment;
354| }
355|
356| return $result;
357| }
Exception trace:
1 Illuminate\Foundation\Bootstrap\HandleExceptions::handleError("Array to string conversion", "/mnt/_work_sdb8/wwwroot/lar/votes/vendor/laravel/framework/src/Illuminate/Support/Str.php")
/mnt/_work_sdb8/wwwroot/lar/votes/vendor/laravel/framework/src/Illuminate/Support/Str.php:353
2 Illuminate\Support\Str::replaceArray("?", [], "insert into `vt2_vote_categories` (`id`, `name`, `slug`, `active`, `in_subscriptions`, `meta_description`, `meta_keywords`) values (?, ?, ?, ?, ?, ?, ?)")
/mnt/_work_sdb8/wwwroot/lar/votes/vendor/laravel/framework/src/Illuminate/Database/QueryException.php:56
Please use the argument -v to see more details.
Why error ? I supposed that $casts array must be used in ->insert methods, but it looks not like so.
How to fix it ?
Thanks!

You are trying to insert an array in the JSON column datatype, hence the error, try changing it in json before inserting:
DB::table( 'vote_categories' )->insert([
'id' => 1,
'name' => 'Classic literature',
'slug' => 'classic-literature',
'active' => true,
'in_subscriptions' => true,
'meta_description' => '',
'meta_keywords' => json_encode(['Classic literature']),
]);

Related

Get Raw SQL of Insert Statement

I am looking for a way to get the correct SQL queries for an INSERT statement. I'm having to export this data for use in another (non-laravel) system. The post at How to get the raw SQL for a Laravel delete/update/insert statement? got me part of the way there but my queries are still parameterized:
Post::all()->each(function($post)
{
$builder = DB::table('posts');
$insertStatement = $builder->getGrammar()->compileInsert($builder->select(['created_at', 'title']), [
'created_at' => $post->created_at,
'title' => $post->title
]);
Storage::disk('sql')->append('posts-latest.sql', $insertStatement);
dump($insertStatement);
}
this results in...
insert into `posts` (`created_at`, `title`) values (?, ?)
So I've managed to set the fields to be updated but how to swap out the parameters for real values?
You can do this:
Post::all()->each(function($post){
$builder = DB::table('posts');
$grammar = $builder->getGrammar();
$values = [
'created_at' => $post->created_at,
'title' => $post->title
];
$table = $grammar->wrapTable($builder->from);
if (!is_array(reset($values))) {
$values = [$values];
}
$columns = $grammar->columnize(array_keys(reset($values)));
$parameters = collect($values)->map(function ($record) use ($grammar) {
$record = array_map(function($rec){
$rec = str_replace("'", "''", $rec);
return "'$rec'";
},array_values($record));
return '('.implode(', ', $record).')';
})->implode(', ');
$insertStatement = "insert into $table ($columns) values $parameters";
// $insertStatement should contains everything you need for this post
});
I ended up discovering DB::pretend which will generate the query without running it. Then it's a case of substitution. It seems that there is no way to get the raw SQL without substitution due to the use of parameters.
Post::all()->each(function($post)
{
$builder = DB::table('posts');
$query = DB::pretend(function() use ($builder, $post)
{
return $builder->insert([
'created_at' => $post->created_at,
'title' => $post->title,
'content' => $post->content,
'featured_image_link' => $post->featured_image_link,
'slug' => $post->slug
]);
});
$bindings = [];
collect($query[0]['bindings'])->each(function($binding) use (&$bindings)
{
$binding = str_replace("'", "\\'", $binding);
$bindings[] = "'$binding'";
});
$insertStatement = Str::replaceArray('?', $bindings, $query[0]['query']);
Storage::disk('sql')->append('posts-latest.sql', $insertStatement.';');
});

Avoid duplication in doctor_id field with where clause in laravel 5.4

this is my doctors id
this is my user id
I can't insert anything when I put unique in the validation of doctor_id. When there's no unique it works fine.
I want to avoid duplicate doctor_id where auth::id == 1. Any advice is appreciated.
public function store(Request $request, $id)
{
$auth = Auth::id();
$constraints = [
'doctor_id' => 'required|unique',
'day1' => 'required|max:20',
'day2'=> 'required|max:60',
'day3' => 'required|max:60'
];
$input = [
'users_id' => $auth,
'doctor_id' => $id,
'day1' => $request['day1'],
'day2' => $request['day2'],
'day3' => $request['day3'],
'day4' => $request['day4'],
...
'day27' => $request['day27'],
'status' => '1'
];
$this->validate($request, $constraints);
Itinerary::create($input);
$added = array('added'=> 'Added Doctor to Itinerary Successful!!');
return redirect()->back()->with($added);
Have you tried this? (assuming your table is named itineraries):
'doctor_id' => 'unique:itineraries'
According to Laravel Doc, you should add the table name, and column name if possible:
unique:table,column,except,idColumn

Yii2 hasMany without link fields

User model has follow relation:
public function getWorkload() : ActiveQuery
{
return $this->hasMany(ScheduleWorkload::className(), ['staff_id' => 'id']);
}
Find method:
$staffs = User::find()
->alias('u')
->joinWith(['workload as uw' => function($q) {
$q->select(['uw.staff_id', 'uw.date', 'uw.time_ranges']);
}], true)
->select([
'u.id',
'CONCAT(u.first_name, \' \', u.last_name) as name',
'u.first_name',
'u.last_name',
'u.undelivered_messages',
])
->where(['u.is_staff' => 1])
->asArray()
->all()
;
I need get data without uw.staff_id in result set? Is it possible without post-processing?
UPDATE:
Result set that I have
I need "workload" as array parameter but do not use post-processing and just exclude "staff_id" from result set.
Raw sql:
SELECT `u`.`id`, `u`.`undelivered_messages`
FROM `user` `u`
LEFT JOIN `schedule_workload` `uw` ON `u`.`id` = `uw`.`staff_id`
WHERE `u`.`is_staff`=1
You can't do in Yii without any post-processing because ActiveQuery will search for foreign key in nested query result to build nested arrays for joined relation.
The most convenient way for you is to use ArrayHelper:
$staffs = User::find()
->alias('u')
->joinWith(['workload')
->where(['u.is_staff' => 1])
->all();
return \yii\helpers\ArrayHelper::toArray($staffs, [
User::className() => [
'id',
'first_name',
'last_name',
'name' => function ($user) {
return $user->first_name . ' ' . $user->last_name
},
'undelivered_messages',
'workload',
],
ScheduleWorkload::className() => [
'date',
'time_ranges',
'comment'
]
], true);

How to seed timestamps on laravel 4.1?

Good day,
I was having an error "Object of class DateTime could not be converted to string" when Im trying to seed my database.
here is my migration code:
public function up()
{
Schema::create('tblinventory', function(Blueprint $table) {
$table->increments('id');
$table->integer('itemId');
$table->enum('status', array('active','inactive'))->default(null)->nullable();
$table->float('purchasePrice');
$table->float('sellingPrice');
$table->date('expirationDate');
$table->float('ReceivedQuantity');
$table->float('soldQuantity');
$table->timestamps();
});
}
and my seeder:
<?php
class InventoryTableSeeder extends Seeder {
public function run()
{
// Uncomment the below to wipe the table clean before populating
DB::table('tblinventory')->truncate();
$insert = [
[
'itemId' => '1',
'status' => 'inactive',
'ReceivedQuantity'=>'100',
'SoldQuantity'=>'93',
'sellingPrice'=>'4.5',
'purchasePrice'=>'3.5',
'created_at' => new DateTime,
'expirationDate'=>date('2015-02-22')
],
[
'itemId' => '1',
'status' => 'inactive',
'ReceivedQuantity'=>'300',
'SoldQuantity'=>'300',
'sellingPrice'=>'4.75',
'purchasePrice'=>'3.65',
'expirationDate'=>date('2015-02-22')
],
[
'itemId' => '2',
'status' => 'inactive',
'ReceivedQuantity'=>'100',
'SoldQuantity'=>'93',
'sellingPrice'=>'3.5',
'purchasePrice'=>'2.5',
'expirationDate'=>date('2014-07-22')
],
[
'itemId' => '3',
'status' => 'inactive',
'ReceivedQuantity'=>'100',
'SoldQuantity'=>'93',
'sellingPrice'=>'12.5',
'purchasePrice'=>'10.5',
'expirationDate'=>date('2017-01-02')
],
[
'itemId' => '3',
'status' => 'inactive',
'ReceivedQuantity'=>'100',
'SoldQuantity'=>'100',
'sellingPrice'=>'14.5',
'purchasePrice'=>'13.5',
'expirationDate'=>date('2017-07-22')
],
[
'itemId' => '4',
'status' => 'inactive',
'ReceivedQuantity'=>'100',
'SoldQuantity'=>'93',
'sellingPrice'=>'24.5',
'purchasePrice'=>'23.5',
'expirationDate'=>date('2015-07-22')
]
];
DB::table('tblinventory')->insert($insert);
// Uncomment the below to run the seeder
// DB::table('inventories')->insert($inventories);
}
}
I get the error when I put 'created_at'=> new DateTime. How can I fix this? thank you!
Try to create your dates using Carbon (Laravel uses it internally):
'expirationDate' => \Carbon\Carbon::createFromDate(2014,07,22)->toDateTimeString()
or
'created_at' => \Carbon\Carbon::now()->toDateTimeString()
I would recommend using PHP Faker if you want to randomize your seeds for mock data. Otherwise you can just use
date('Y-m-d H:i:s');
Using Faker
https://github.com/fzaninotto/Faker
Add to composer.json
"fzaninotto/faker" : "dev-master",
Include the Namespace
use Faker\Factory as Faker;
Initialize Faker
$faker = Faker::create();
Start Faking Stuff
$faker->dateTime();
I am a little late to the party here but I wanted to give another option that others may find useful.
If you have already created your models using Eloquent, then there is another option to have Eloquent fill those fields for you automatically by using the orm. Assuming your btlinventory has a model name of Inventory:
foreach($insert as $row ){
$inventory = new Inventory;
$inventory->fill($row);
$inventory->save();
}
insert is a query builder method so by itself it will not handle any Eloquent tasks, however, you can always chain query builder methods off of an Eloquent object and then it would work. If you use Inventory::create($array); and still have issues then I hear this may get fixed by explicitly stating public $timestamps = true; in your model.

Fillable list ignored while inserting related model

I am using Ardent and I faced strange behaviour of ignoring $fillable list while inserting/updating related models.
I have the following models defined:
class User extends LaravelBook\Ardent\Ardent
{
protected $table = 'users';
public static $relationsData = [
'contacts' => [self::HAS_MANY, 'Contact'],
];
}
class Contact extends LaravelBook\Ardent\Ardent
{
protected $table = 'user_contacts';
protected $guarded = ['*'];
protected $fillable = [
'user_id',
'type',
'value'
];
public static $relationsData = [
'user' => [self::BELONGS_TO, 'User'],
];
}
Now I am trying to add new contact to user:
$user->contacts()->create([
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
]);
... and I got SQL insert error:
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'unknown_field' in 'field list' (SQL: insert into `user_contacts` (`type`, `value`, `unknown_field`, `user_id`, `updated_at`, `created_at`) values (?, ?, ?, ?, ?, ?)) (Bindings: array ( 0 => 'some type', 1 => 'some value', 2 => 'unknown value', 3 => 2, 4 => '1384854899', 5 => '1384854899', ))
In the same time this is working fine:
UserContact::create([
'user_id' => 2,
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
]);
I didn't get any SQL errors and 'unknown_field' was just ignored.
Any ideas why $fillable fields could be ignored while working via builder?!
I don't understand why the HasManyOrOne relationship intentionally ignores fillable. It seems really counter intuitive. Either way, I think this should work for you.
$user->contacts()->save(Contact::create([ ... ]));
It seems I found the reason of this behaviour. This is explicitly implemented in HasOneOrMany abstract class.
abstract class HasOneOrMany extends Relation {
...
/**
* Create a new instance of the related model.
*
* #param array $attributes
* #return \Illuminate\Database\Eloquent\Model
*/
public function create(array $attributes)
{
$foreign = array(
$this->getPlainForeignKey() => $this->parent->getKey()
);
// Here we will set the raw attributes to avoid hitting the "fill" method so
// that we do not have to worry about a mass accessor rules blocking sets
// on the models. Otherwise, some of these attributes will not get set.
$instance = $this->related->newInstance();
$instance->setRawAttributes(array_merge($attributes, $foreign));
$instance->save();
return $instance;
}
...
}
I am still looking for the suffitient solution to control this behaviour.
As stated in the offical documentation:
To get started, set the fillable or guarded properties on your model.
You have set both. You should remove the following line: protected $guarded = ['*'];
Fortunately this will be fixed in version 4.2: https://github.com/laravel/framework/pull/2846
Added to all this, you can also filter the attributes manually:
$input = [
'user_id' => 2,
'type' => 'some type',
'value' => 'some value',
'unknown_field' => 'unknown value'
];
$fillable = $user->contacts()->getRelated()->fillableFromArray($input);
$user->contacts()->create($fillable);
Keeping in mind that the example are using Eloquent\Model\fillableFromArray() method, which is protected, so it will be necessary, for example, replicate it:
class BaseModel extends Eloquent
{
public function fillableFromArray(array $attributes)
{
return parent::fillableFromArray($attributes);
}
}
Use protected $guarded = array(); instead of protected $guarded = ['*'];
by using [*] you're telling laravel to guard all entities from autohydration / mass assignment!
array() sets this $guarded list to null.
The fillable property specifies which attributes should be mass-assignable. This can be set at the class or instance level.
The inverse of fillable is guarded, and serves as a "black-list" instead of a "white-list":
Read more at Laravel documentation on mass assignment
The update methods are not on the model level, and won't respect the $fillable fields.
You could filter the input data by using Input::only['fillable fields here']

Resources