Laravel : Migrations & Seeding for production data - php

My application needs a pre registered data set to work. So i need to insert them in the database when i set up the application.
Laravel propose two mechanisms :
Database migrations : "They allow a team to modify the database schema and stay up to date on the current schema state."
Database seeding : "Laravel also includes a simple way to seed your database with test data using seed classes."
When I read this description, none of these solutions seems to be adapted.
A similar question has been asked on stackoverflow and answered. The answer proposes to use the a database seeder to populate the database by detecting the current environment :
<?php
class DatabaseSeeder extends Seeder {
public function run()
{
Eloquent::unguard();
if (App::environment() === 'production')
{
$this->call('ProductionSeeder');
}
else
{
$this->call('StagingSeeder');
}
}
}
Of course, this solution works. But i am not sure that it is the right way to do this, because by inserting data using seeders you are losing all the advantages provided by the migration mechanism (database upgrate, rollback...)
I want to know what is the best practice in this case.

Laravel development is about freedom. So, if you need to seed your production database and think DatabaseSeeder is the best place to do so, why not?
Okay, seeder is mainly to be used with test data, but you'll see some folks using it as you are.
I see this important kind of seed as part of my migration, since this is something that cannot be out of my database tables and artisan migrate is ran everytime I deploy a new version of my application, so I just do
php artisan migrate:make seed_models_table
And create my seedind stuff in it:
public function up()
{
$models = array(
array('name' => '...'),
);
DB::table('models')->insert($models);
}

I've often found myself wondering what the right answer to this is. Personally, I'd steer clear of using seeding to populate required rows in the database as you'll have to put a load of conditional logic in to ensure that you don't attempt to populate something that's already there. (Deleting and recreating the data is very inadvisable as you could end up with key mismatches and if you're using cascading deletes you may accidentally wipe a load of your database by mistake! ;-)
I put the 'seeding' of rows into the migration script as the chances are, the data will need to be there as part of the rollout process.
It's worth noting that you should use the DB class instead of Eloquent models to populate this data as your class structure could change over time which will then prevent you from re-creating the database from scratch (without rewriting history and changing you migration files, which I'm sure is a bad thing.)
I'd tend to go with something like this:
public function up()
{
DB::beginTransaction();
Schema::create(
'town',
function (Blueprint $table) {
$table->increments('id');
$table->string('name');
$table->timestamps();
}
);
DB::table('town')
->insert(
array(
array('London'),
array('Paris'),
array('New York')
)
);
Schema::create(
'location',
function (Blueprint $table) {
$table->increments('id');
$table->integer('town_id')->unsigned()->index();
$table->float('lat');
$table->float('long');
$table->timestamps();
$table->foreign('town_id')->references('id')->on('town')->onDelete('cascade');
}
);
DB::commit();
}
This then allows me to 'seed' the town table easily when I first create it, and won't interfere with any additions made to it at run-time.

This is what I use in production.
Since I run migration on each deployment
artisan migrate
I create a seeder (just to keep seeding data out of migration for easy access later) and then run that seeder along with the migration
class YourTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
//migrate your table // Example
Schema::create('test_table', function(Blueprint $table)
{
$table->increments('id');
$table->timestamps();
$table->softDeletes();
});
//seed this table
$seeder = new YourTableSeeder();
$seeder->run();
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('test_table');
}
}
I do not add this seed call to seeds/DatabaseSeeder.php to avoid running it twice on a new installation.

The Artisan Command Solution
Create a new artisan command
php artisan make:command UpsertConfigurationTables
Paste this into the newly generated file: UpsertConfigurationTables.php
<?php
namespace App\Console\Commands;
use Exception;
use Illuminate\Console\Command;
class UpsertConfigurationTables extends Command
{
/**
* The name and signature of the console command.
*
* #var string
*/
protected $signature = 'upsert:configuration';
/**
* The console command description.
*
* #var string
*/
protected $description = 'Upserts the configuration tables.';
/**
* The models we want to upsert configuration data for
*
* #var array
*/
private $_models = [
'App\ExampleModel'
];
/**
* Create a new command instance.
*
* #return void
*/
public function __construct()
{
parent::__construct();
}
/**
* Execute the console command.
*
* #return mixed
*/
public function handle()
{
foreach ($this->_models as $model) {
// check that class exists
if (!class_exists($model)) {
throw new Exception('Configuration seed failed. Model does not exist.');
}
// check that seed data exists
if (!defined($model . '::CONFIGURATION_DATA')) {
throw new Exception('Configuration seed failed. Data does not exist.');
}
/**
* seed each record
*/
foreach ($model::CONFIGURATION_DATA as $row) {
$record = $this->_getRecord($model, $row['id']);
foreach ($row as $key => $value) {
$this->_upsertRecord($record, $row);
}
}
}
}
/**
* _fetchRecord - fetches a record if it exists, otherwise instantiates a new model
*
* #param string $model - the model
* #param integer $id - the model ID
*
* #return object - model instantiation
*/
private function _getRecord ($model, $id)
{
if ($this->_isSoftDeletable($model)) {
$record = $model::withTrashed()->find($id);
} else {
$record = $model::find($id);
}
return $record ? $record : new $model;
}
/**
* _upsertRecord - upsert a database record
*
* #param object $record - the record
* #param array $row - the row of update data
*
* #return object
*/
private function _upsertRecord ($record, $row)
{
foreach ($row as $key => $value) {
if ($key === 'deleted_at' && $this->_isSoftDeletable($record)) {
if ($record->trashed() && !$value) {
$record->restore();
} else if (!$record->trashed() && $value) {
$record->delete();
}
} else {
$record->$key = $value;
}
}
return $record->save();
}
/**
* _isSoftDeletable - Determines if a model is soft-deletable
*
* #param string $model - the model in question
*
* #return boolean
*/
private function _isSoftDeletable ($model)
{
$uses = array_merge(class_uses($model), class_uses(get_parent_class($model)));
return in_array('Illuminate\Database\Eloquent\SoftDeletes', $uses);
}
}
Populate $_models with the Eloquent models you want to seed.
Define the seed rows in the model: const CONFIGURATION_DATA
<?php
namespace App;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class ExampleModel extends Model
{
use SoftDeletes;
const CONFIG_VALUE_ONE = 1;
const CONFIG_VALUE_TWO = 2;
const CONFIGURATION_DATA = [
[
'id' => self::CONFIG_VALUE_ONE,
'col1' => 'val1',
'col2' => 'val2',
'deleted_at' => false
],
[
'id' => self::CONFIG_VALUE_TWO,
'col1' => 'val1',
'col2' => 'val2',
'deleted_at' => true
],
];
}
Add the command to your Laravel Forge deployment script (or any other CI deployment script): php artisan upsert:configuration
Other noteworthy things:
Upsert Functionality: If you ever want to alter any of the seeded rows, simply update them in your model and it was update your database values next time you deploy. It will never create duplicate rows.
Soft-Deletable Models: Note that you define deletions by setting deleted_at to true or false. The Artisan command will handle calling the correct method to delete or recover your record.
Problems with other mentioned solutions:
Seeder: Running seeders in production is an abuse of the seeders. My concern would be that an engineer in the future would alter the seeders thinking that it's harmless since the documentation states that they are designed to seed test data.
Migrations: Seeding data in a migration is strange and an abuse of the purpose of the migration. It also doesn't let you update these values once your migration has been run.

Related

Laravel Query Builder hard coded data is not inserted with post method

I am trying to insert hard coded data with QueryBuilder insertGetId() method.
my code is-
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class StudentController extends Controller
{
public function addStudent()
{
$foreign_key = DB::table('students')->insertGetId([
'id' => 'stu-000002',
'name' => 'Ahsan',
'email' => 'ahsan#example.net',
]);
echo $foreign_key;
}
}
My migration file is-
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('students', function (Blueprint $table) {
$table->string('id', 30)->primary();
$table->string('name', 100);
$table->string('email', 100)->unique();
$table->timestamp('created_at')->useCurrent();
$table->timestamp('updated_at')->useCurrent();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('students');
}
};
My route is -
Route::post('/add-student', [StudentController::class, 'addStudent']);
But result is -
Symfony \ Component \ HttpKernel \ Exception \ MethodNotAllowedHttpException
The GET method is not supported for this route. Supported methods: POST.
But when I try to insert data with get method like-
Route::get('/add-student', [StudentController::class, 'addStudent']);
Data has been inserted . But primary key has been retrived 0 as primary key is custom string.
How can I solve this problem. Thank you.
run this command in terminal:
php artisan route:cache
So what this command does is, it registers new routes or gives an error if there is something wrong with your route file.
There are two problems with what you're doing:
Problem 1:
The first is the MethodNotAllowedException. I guess you're trying to use a GET request on a POST URL. This won't work, because Laravel blocks the 'wrong' method.
Use POST when you have data to submit (or if you really want to stick to the 'use post when saving'-creed use a form). Use GET when you want to access an URL.
Problem 2
According to the API (This one) insertGetId returns an integer which is the ID. Since your ID's are strings, you can't use that method.
A solution to that problem would be to change the code like this:
public function addStudent()
{
$student_id = 'stu-000002'
$insert = DB::table('students')->insert([
'id' => $student_id,
'name' => 'Ahsan',
'email' => 'ahsan#example.net',
]);
if ( ! $insert ) {
return false;
}
echo $student_id;
}
The insert method returns a boolean. Leveraging that you can check whether or not the entry was inserted. If it is, your ID should be good.

Can we execute seeder without truncating table or is there any way to achieve this

we are working on a project where I would like to insert one row to database using seeder but when I have executed that seeder it truncates table and insert seeds record. What I want is it should insert a new record only without truncating existing data.
Can anyone help to get this?
This seeder feature is available in mostly all MVC like Laravel and Yii2 that we are using.
use yii\db\Migration;
class m200118_113041_create_table_admin_master extends Migration
{
public function Safeup()
{
$seeder = new \tebazil\yii2seeder\Seeder();
$generator = $seeder->getGeneratorConfigurator();
$faker = $generator->getFakerConfigurator();
$seeder->table('admin_master')->columns([
'email'=>$faker->email,
'password'=>rand(1, 999999),
'created_date'=> date('Y-m-d H:i:s'),
])->rowQuantity(30);
$seeder->refill();
}
public function Safedown()
{
// $this->dropTable('{{%admin_master}}');
}
}
Here above is the example of my migration in Yii2
you can create a new migration file and put insert query in that easily
php artisan make:migration insert_somename_table
than inside migration file
<?php
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
use DB;
class InsertSomenameTable extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
DB::('somename')->insert(array('key1' => 'value1', 'key2' => 'value2'));
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
DB::('somename')->where('key1', '=', 'value1')->delete();
}
}
and execute
php artisan migrate
You can do this in two ways
1.Update your seeder
Consider your table name is 'categories' and you already have 12 categories.
You should start your new category id from 13 in the seeder array.
Your seeder look like this
public function run()
{
$data = array(
['id' => 13, 'category' => 'Category1', 'status' => 1],
['id' => 24, 'category' => 'category2', 'status' => 1]
);
DB::table('categories')->insert($data);
}
2. Execute queries
Write one method and execute insert queries

Laravel Tasks - Storing when last ran

I've made CRON Job using Laravel's task scheduling. But what I need is to store somewhere when that task was last ran,
Does anyone have any methods of how they store that and also, If Laravel outputs anything that can tell you when it was last ran?
Thanks
Not possible directly, however it is possible if you cache a date-time string on each run (either at the beginning or end of your script).
Cache::rememberForever('name_of_artisan_task', function () {
return now()->toDateTimeString();
});
The example shows using the Cache facade's ::rememberForever method to create a key/value of the last time the task was ran. As the name suggests, this is saved forever.
You can easily retrieve this date and time using the cache() helper:
cache('name_of_artisan_task');
The con with this method is that if your cache is cleared, you will not longer have this stored.
Using a cache is not a safe way to do this, as #thisiskelvin hinted, clearing the cache will remove the data (which should happen on each deployment) but he didn't provide an alternative
So here is one if you need this date reliably (if you use it to know the interval to run an export for instance)
In which case I recommend creating a model php artisan make:model ScheduleRuns -m
<?php
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
/**
* #property string $task
*/
class ScheduleRuns extends Model
{
public const UPDATED_AT = false;
public $timestamps = true;
protected $attributes = [
'task' => '',
];
protected $fillable = [
'task',
'created_at',
];
}
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('schedule_runs', function (Blueprint $table) {
$table->id();
$table->string('task');
$table->timestamp('created_at')->nullable();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::dropIfExists('schedule_runs');
}
};
Then use schedule hooks to create it (or do it within the task if you want to avoid possible seconds differences)
$schedule->command('export:users')
->weekly()->onSuccess(fn () => ScheduleRuns::create(['task' => 'export:users']))
And to retrieve the latest run
ScheduleRuns::query()->where('task', 'export:users')->latest();
Just write log each time the task was run, or you can push it into database.
<?php
namespace App\Console\Commands\Tasks;
use Illuminate\Console\Command;
class ScheduledTask extends Command
{
public function handle()
{
//
// ...handle you task
//
$file = 'logs/jobs/' . __CLASS__ . '.log';
$message = 'Executed at: ' . date('Y-m-d H:i:s', time());
file_put_contents(storage_path($file), $message, FILE_APPEND);
}
}

Laravel Base table or view not found

I'm very confused, i try to find what's wrong but i don't find it..
My migration file:
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
class CreateClientProjectTable extends Migration {
/**
* Run the migrations.
*
* #return void
*/
public function up()
{
Schema::create('client_project', function(Blueprint $table)
{
$table->increments('id');
$table->integer('client_id');
$table->integer('project_id');
$table->timestamps();
});
}
/**
* Reverse the migrations.
*
* #return void
*/
public function down()
{
Schema::drop('client_project');
}
}
First of all, i check table is created, and it is.
Then, the route who calls to the controller is this: (is prefixed by admin)
Route::post('projectsclients/postUpload', ['uses' => 'AdminController#storeProjectsClients', 'as' => 'admin.projectsclients.store']);
The functions looks like here:
$client_project = new Client_Project();
$client_project->client_id = DB::table('clients')->orderby('id','DESC')->take(1)->get();
$client_project->project_id = DB::table('projects')->orderby('id','DESC')->take(1)->get();
$client_project->save();
And the error:
Base table or view not found: 1146 Table 'web.client__projects'
doesn't exist
The problem is my table is client_project not client__projects.
Where i have to fix this?
Thanks a lot, any help will be appreciated.
You shouldn't be breaking up class name with _ for one. It should be named ClientProject. Generally if there is a problem with the table you would edit the modal and add a
ClientProject.php
public $table = 'client_project';
You are not following Laravel naming conventions. To solve this particular issue yo can explicitly define tabe name. In class Client_Projects definition add this:
protected $table = 'client_project';
But to learn about naming conventions I suggest reading related section in documents here https://laravel.com/docs/5.4/eloquent#eloquent-model-conventions

How can I write migrations to insert records using phinx?

I'm using phinx to handle the migration on a new project, now I need to create a new table and insert some rows to it, I have:
$tableStatus = $this->table('status');
$tableStatus->addColumn('code', 'string');
$tableStatus->addColumn('description', 'string');
$tableStatus->save();
This add the new table but I couldn't find at the documentation how to insert rows, but it seems possible:
The AbstractMigration Class All Phinx migrations extend from the
AbstractMigration class. This class provides the necessary support to
create your database migrations. Database migrations can transform
your database in many ways such as creating new tables, inserting
rows, adding indexes and modifying columns.
It is possible? How can I do it?
As igrossiter pointed out, there is a method for this, the name of the method is insert
use Phinx\Migration\AbstractMigration;
class NewStatus extends AbstractMigration
{
protected $statusId = 1234; //It'd be nice to use an entity constant instead of magic numbers, but that's up to you.
protected $statusName = 'In Progress';
/**
* Migrate Up.
*/
public function up()
{
$columns = ['id', 'name'];
$data = [[$this->statusId, $this->statusName]];
$table = $this->table('status');
$table->insert($columns, $data);
$table->saveData();
}
/**
* Migrate Down.
*/
public function down()
{
$this->execute('Delete from status where id = ' . $this->statusId);
}
}
Edit as of December 2nd, 2015
This method's signature will change in future stable versions to something like
$data = [
['id' => 1, 'name' => 'foo'],
['id' => 2, 'name' => 'bar']
];
$table = $this->table('status');
$table->insert($data);
More info here
You can do it. Read documentation for more information.
http://docs.phinx.org/en/latest/migrations.html#executing-queries
use Phinx\Migration\AbstractMigration;
class MyNewMigration extends AbstractMigration
{
/**
* Migrate Up.
*/
public function up()
{
// execute()
$count = $this->execute('insert into users(id, name) values (5, "john")');
}
/**
* Migrate Down.
*/
public function down()
{
}
}
Run this command to generate StatusMigration class:
php vendor/bin/phinx create StatusMigration
Edit that file like:
<?php
use Phinx\Migration\AbstractMigration;
class StatusMigration extends AbstractMigration
{
public function change()
{
$this->table('status')
->addColumn('code', 'string')
->addColumn('description', 'string')
->create();
}
}
You can use Phinx's database seeding mechanism to insert rows into tables. First run following command:
php vendor/bin/phinx seed:create StatusSeeder
It will generate StatusSeeder.php file in Phinx's seeds folder.
Edit StatusSeader.php like:
use Phinx\Seed\AbstractSeed;
class StatusSeeder extends AbstractSeed
{
public function run()
{
$data = [
['code' => 'c1', 'description' => 'Some description'],
['code' => 'c2', 'description' => 'Another description'],
];
$this->table('status')
->insert($data)
->save();
}
}
Now, run following commands to create table and seed data:
php vendor/bin/phinx migrate
php vendor/bin/phinx seed:run
This question has a good answer using CakePHP 3's Migrations plugin:
public function up() {
// Save records to the newly created schema
$UsersTable = TableRegistry::get('Users');
$user = $UsersTable->newEntity();
$user->name = 'Joe Bloggs';
$user->email = 'joe#example.com';
$UsersTable->save($user);
}

Categories