Dynamic dropping of table column in laravel - laravel

I am trying to add or delete a column in table2 when a record is inserted in table1(groups-id, title, rateperkg). The name of the new table2-column name will be same as title field inserted.
I was able to add the column successfully but could perform deleting. It shows undefined value but I defined it. Please help me.
Below have screenshot:
Table2 - pricings(id, product_id)
Table1 - groups(id, title)
The groups title should get inserted as new column name in pricings table and deleting.

In your first screenshot you use the use in the callback/function, you also need to add this to the latest screenshot. Take a look at how PHP scopes work, http://php.net/manual/en/language.variables.scope.php.
You can use this with this code:
Schema::table('pricings', function (Blueprint $table) use ($subgroup) {
$table->dropcolumn($subgroup);
});
This will set the scope for $subgroup in the callback/function you defined.
See http://php.net/manual/en/functions.anonymous.php for this example: #3
// Inherit $message
$example = function () use ($message) {
var_dump($message);
};
$example();

Related

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

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 = '{}';
}
}

Updating the data of an existing column and copying data with migrations

Is it possible to add a new column, copy some data to this new column and update the data in another column with a Laravel migration?
I have a table something like this;
id
item
price
1
ItemOne
100.00
2
ItemTwo
200.00
Now what I need to do is,
add a new column, old_price to this table
copy the values in the price column to newly added old_price column
multiply the values in the price column with 5 and update the same column
The new table should look something like this;
id
item
price
old_price
1
ItemOne
500.00
100.00
2
ItemTwo
1000.00
200.00
Is it possible to to achieve this with a migration or a seed or something?
This change needs to be done on an application that is already in production so dropping and re-creating the tables is not an option for me here.
Also the old_price column is created just to keep a reference of the current values of the price column. It will not be updated after this and probably will be removed in an later update if everything is going right with the new prices. So I don't need to use any model events to update the column afterwards.
Any help you can give me on this is really appreciated. Thanks.
Create a new migration.
Version 1. create automatically by command:
php artisan make:migration add_price_old_to_products_table
Version 2. create manually something like this:
2021_08_18_163618_add_price_old_to_products_table.php
Manage the content by following the 3 steps in the code:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
class AddPriceOldToProductsTable extends Migration
{
public function up()
{
// 1. creating a new column
Schema::table('products', function (Blueprint $table) {
// this is just an example, you can change this
// NOTE: make sure that the type is the same as "price" column
// for avoiding type conflicts
$table->decimal('price_old', 10, 2)->nullable();
});
// 2. copying the existing column values into new one
DB::statement("UPDATE products SET price_old = price");
// 3. update the old/existing column
// CHANGE YOUR "price" COLUMN HERE...
}
public function down()
{
Schema::table('products', function (Blueprint $table) {
$table->dropColumn('price_old');
});
}
}
Run that migration for creating a new column:
php artisan migrate

Wrong ID returned from local Eloquent scope

In my Laravel project I've got a model (and an underlying table) for lessons. Now I'm trying to write a local scope for returning all lessons that have been finished by a particular user. The definition of "finished" is that there exists a row in a table named "lesson_results" with this lesson's ID and the users ID.
My scope currently looks like this:
public function scopeFinished($query, User $user)
{
return $query->join('lesson_results', function($join) use($user)
{
$join->on('lesson_results.lesson_id', '=', 'lessons.id')
->where("user_id", $user->id);
});
}
This kinda works. When I do a Lesson::finished($user)->get() I get out the correct lessons for that user. However the lessons in the collection returned all have the wrong ID's! The ID's I see are the ID's from the lesson_results table. So when I check $lesson->id from one of the returned items I don't get the ID from that lesson, but the ID from the corresponding row in the lesson_results table.
I've checked in mysql and the full query sent from Laravel is the following
select * from `lessons` inner join `lesson_results` on `lesson_results`.`lesson_id` = `lessons`.`id` and `user_id` = 53
This query DO return two columns named id (the one from the lessons table and the one from the lesson_results table) and it seems Laravel is using the wrong one for the result returned.
I don't know if I'm going about this the wrong way or if it's a bug somewhere?
This is on Laravel 7.6.1.
edit: Ok, I think I actually solved it now. Not really sure though if it's a real solution or just a workaround. I added a select() call so the return row now is
return $query->select('lessons.*')->join('lesson_results', function($join) use($user)
...which makes it only return the stuff from the lessons table. But should that really be needed?
One of the same column names will be covered by the other.
Solution 1:
Specify the table with the column, and alias the other table's column if it has same column name.
Lesson::finished($user)->select('lessons.*', 'lesson_results.id AS lesson_result_id', 'lesson_results.column1', 'lesson_results.column2',...)->get();
Solution 2:
Or you can use Eloquent-Builder eager-loading whereHas,(Assuming you have build the relationship between model Lesson and model LessonResult)
public function scopeFinished($query, User $user)
{
return $query->whereHas('lessonResults', function($query) use($user)
{
$query->where("user_id", $user->id);
});
}
So you can get lesson like this:
Lesson::finished($user)->get();

Get column names in Laravel as ordered on the Table

I am using the following method to get the column names of the database tables of my Laravel (v5.6.24) project. I am using mysql and it was working fine as expected. But from this week the columns names are showing as ordered by name. Previously it was showing names as in order as the actual table.
How can i get the column names in same order as the table?
/*
* Get Table Column Names
*/
public function getTableColumns()
{
return $this->getConnection()->getSchemaBuilder()->getColumnListing($this->getTable());
}
Might help someone: this is a slightly modified version of #Jonas Staudenmeir answer - but tweaked to be reusable. Just feed it the table name and it will spit out an array of the table fields.
private function getTableColumns($table_name)
{
return DB::select(
(new \Illuminate\Database\Schema\Grammars\MySqlGrammar)->compileColumnListing()
.' order by ordinal_position',
[env('DB_DATABASE'), $table_name]
);
}
You'll have to order the columns by ordinal_position:
public function getTableColumns()
{
return $this->getConnection()->select(
(new \Illuminate\Database\Schema\Grammars\MySqlGrammar)->compileColumnListing()
.' order by ordinal_position',
[$this->getConnection()->getDatabaseName(), $this->getTable()]
);
}

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';");

Resources