How to set a value in a null JSON column with Laravel's Eloquent? - laravel

I'm using Laravel 7.
I want to update a JSON column using Laravel's Eloquent. The problem is that if the value of the column is null the column won't be updated.
This is how the code looks like:
Model::update(['jsonColumnName->jsonColumnKey' => 'value']);
This is the SQL that it would generate:
UPDATE model
SET jsonColumnName = JSON_SET(jsonColumnName, '$.jsonColumnKey', 'value');
According to the documentation of JSON_SET, it will take the first parameter as the JSON document that it will modify. In this case, that value would be null since jsonColumnName is currently null. Because of that it returns null since it has nothing to modify and it ends doing nothing.
If I manually set the value of the column to {} and run the showed code, it works. But I guess that you are not expected to do that (right?).

You should make new alter table migration and change json column to have default value {}.
First you need to check if there is already installed dbal with
composer require doctrine/dbal
Then make new migration with code in up() method:
Schema::table('table_name', function (Blueprint $table) {
$table->json('column_name')->nullable()->default(null)->change();
});
Don't forget to backup database before work on it.
With NULL value you can also check if that field is empty.
Another way, on framework level is to set logic about this issue into model's observer.
For example:
public function saving(EntityModel $entityModel)
{
if (is_null($entityModel->json_column)) {
$entityModel->json_column = '{}';
}
}

Related

Laravel mutators on specific situations

As I understand, mutators change your attribute on save function (create or update). Can I make mutator just for atrribute display to users, but leave default values on save function ?
In Eloquent Model when you do this:
public function getSomeNameAttribute($value)
{
return ucfirst($value);
}
That ^ will mutate the value for showing it later in views and it won't affect what is stored in DB table.
And also if you use a setter setSomeNameAttribute($value) you can pull the actual value from db table with:
$model->getOriginal('attribute_name'); // Laravel V. ^5.*
or
$model->getRawOriginal('attribute_name'); // Laravel V. ^8.*

Laravel fills attributes in model in migration even though they are not fillable

I have two entities:
Person, which has workspace_id column
PersonRevision, which does not have workspace_id column
In a migration I have this:
$persons = Person::all();
foreach ($persons as $person) {
$revision = new PersonRevision;
$revision->fill($person->getAttributes());
$revision->save();
}
PersonRevision does not have workspace_id in fillable property. But for some reason I got an error: column "workspace_id" of relation "person_revisions" does not exist.
Besides, if I run the migration second time (by just typing php artisan migrate second time) it works fine.
What could be the reason? I know I can manually list attributes which need to be filled, or use array_except but this is inconvenient and does not answer why that happens.
Could you check what the actual sql statement is that's executed? To me it seems like you have a database which does not contain workspace_id yet, but your model does expect it to be there. Even when not filling the workspace_id the query generated from your eloquent model will probably try to update it with the value null, which it can't do if it's not there.
Because of issues like this it's best not to use eloquent models in database migrations, instead use raw sql.
Laravel SeedCommand handle() function runs with Model::ungarded() which forces all props to be fillable. When unguarded is true it auto fills regardless of fillable array.
public function isFillable($key)
{
if (static::$unguarded) {
return true;
}
[...]
}
Simply add this atop your seeder file Model::reguard();

How to get the default MySql values in `firstOrNew` in Laravel?

If I use firstOrCreate then the default values are given for the model that I set up in the database. But when I use firstOrNew the values are not given and are instead given out as NULL. Is there any way to fix this aside from using firstOrCreate?
This is because firstOrNew will just create a new instance of your model when it doesn't get a result from the database. Therefore it does not get the default values from the database.
So I guess you have at least two options here. The first one would be the one you already mentioned using firstOrCreate (this is not something I would recommend because this could lead to incorrect state in your database). Another option would be to add 'Accessorson your model with which will return either the value retrieved from the database or when it'snull` it will return the default value you define.
public function getMyFieldAttribute($myField)
{
if ($myField === null) {
return 'my-default-value';
}
return $myField;
}
More info on accessors can be found in the documentation: https://laravel.com/docs/5.2/eloquent-mutators#accessors-and-mutators

How to change enum type column in laravel migration?

I am using Laravel 5.1 and I have a table called packages with this structure:
id int(11)
weight decimal(10,2)
weight_unit enum('Kg.', 'Gm.')
I would like to change the weight_unit enum to:
weight_unit enum('Grams','Kgs.','Pounds')
For this I create the following migration:
public function up()
{
Schema::table('packages', function ($table) {
$table->enum('weight_unit', array('Grams','Kgs.','Pounds'))->nullable()->change();
});
}
But when I run the migration I receive an error:
Unknown database type enum requested, Doctrine\DBAL\Platforms\MySqlPlatform
may not support it.
How can I change this enum?
Use the DB::statement method:
DB::statement("ALTER TABLE packages MODIFY COLUMN weight_unit ENUM('Grams', 'Kgs', 'Pounds')");
This worked for me when adding a new enum value to the modified enum column.
Add the following to the up() method:
DB::statement("ALTER TABLE packages MODIFY weight_unit ENUM('Grams', 'Kgs', 'Pounds', 'new value') NOT NULL");
Then in the down() method you can revert the change that was made:
DB::statement("ALTER TABLE packages MODIFY weight_unit ENUM('Grams', 'Kgs', 'Pounds') NOT NULL");
Note: before the enum value is removed it needs to be changed to another enum value that will be retained.
$table->enum('level', ['easy', 'hard']);
You can add custom constructor to migration and explain to Doctrine that enum should be treated like string.
public function __construct(\Doctrine\DBAL\Migrations\Version $version)
{
parent::__construct($version);
$this->platform->registerDoctrineTypeMapping('enum', 'string');
}
In case you dont want to lose your data and update it with the new values I came up with this solution:
// Include old and new enum values
DB::statement("ALTER TABLE packages MODIFY COLUMN weight_unit ENUM('Kg.', 'Gm.', 'Grams', 'Kgs', 'Pounds')");
// Replace Kg. with Kgs
Packages::where('weight_unit', 'Kg.')->update(['weight_unit' => 'Kgs']);
// Replace Gm. with Grams
Packages::where('weight_unit', 'Gm.')->update(['weight_unit' => 'Grams']);
// Delete old values
DB::statement("ALTER TABLE packages MODIFY COLUMN weight_unit ENUM('Grams', 'Kgs', 'Pounds')");
This way you can replace your old values with the new ones.
add this before change() call :
DB::getDoctrineSchemaManager()->getDatabasePlatform()->registerDoctrineTypeMapping('enum', 'string');
I think that is fixed on Laravel 10 by adding support for native column modifying.
https://github.com/laravel/framework/pull/45487
So from Laravel 10 you can do this:
Schema::table('jobs', function (Blueprint $table) {
$table->enum('type', ['contract', 'permanent', 'partial'])->change();
});
I tried the same migration on fresh Laravel 9.55.0 and 10.0.2 application:
laravel-9.52.0.jpg
laravel-10.0.2.jpg
I am able to solve this by removing and adding constraints. This has made sure that my existing data is also is intact.
DB::statement("ALTER TABLE purchases DROP CONSTRAINT purchases_ref_check");
$types = ['single', 'monthly', 'biannual', 'amount', 'other'];
$result = join( ', ', array_map(function( $value ){ return sprintf("'%s'::character varying", $value); }, $types) );
DB::statement("ALTER TABLE purchases add CONSTRAINT purchases_ref_check CHECK (ref::text = ANY (ARRAY[$result]::text[]))");
with default value. add this in up():
\DB::statement("ALTER TABLE `patient_appointments` CHANGE `status` `status` ENUM('pending','wait','approved', 'consulted') CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'pending';");

Set default to NULL with laravel migration

I am adding a field to a table in a migration that I wish to allow to be NULL but also I wish for it to default to NULL. What do I place in the default method? I fear that putting "NULL" in will attempt to place a string of NULLin which I obviously don't want. Please help :)
Schema::table('item_categories', function(Blueprint $table)
{
$table->integer('parent_item_category_id')->unsigned()->nullable()->default($what_to_put here);
});
When you use the nullable() method on a field, that field will default to NULL.
$table->integer('parent_item_category_id')->nullable();
To make the column "nullable", you may use the nullable method:
$table->string('email')->nullable();

Resources