ldc-legaulois/database/migrations/LeGaulois2016Migration.php
2023-06-26 16:02:08 +02:00

286 lines
10 KiB
PHP

<?php
namespace Database\migrations;
use App\Exceptions\AbortRowImportException;
use Exception;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Support\Facades\DB;
use ReflectionFunction;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Console\Terminal;
/**
* Migration Intructions
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
* Examples
*[
* 'content_ocb' => [
* 'model' => OCBContent::class,
* 'bind' => [
* 'id' => 'ocb_id',
* 'picture' => 'ocb_img',
* 'content' => 'ocb_content',
* 'status' => 'ocb_is_published',
* 'type' => [
* 'apply' => function ($value) {
* switch ($value) {
* case 'VOLAILLE':
* return OCBType::poultry();
* case 'LAPIN':
* return OCBType::rabbit();
* }
* },
* 'name' => 'ocb_type',
* 'default' => ''
* ],
* 'order' => 'ocb_position',
* 'created_at' => [
* 'name' => '',
* 'default' => date('Y-m-d H:i:s')
* ],
* 'updated_at' => [
* 'name' => '',
* 'default' => date('Y-m-d H:i:s')
* ]
* ]
* ],
* 'Recipe ratings' => [
* 'table' => 'recipe',
* 'comment' => 'Add ratings to recipes',
* 'model' => Rating::class,
* 'bind' => [
* 'anyprop' => [
* 'apply' => function ($recipe_id) {
* $recipe_rating = $this->oldDB->table('recipe_rating')
* ->select('recipe_rating_value')
* ->where('recipe_rating_recipe_id', '=', $recipe_id)
* ->get();
* $count = $recipe_rating->count();
* $sum = $recipe_rating->sum(function ($obj) {
* return $obj->recipe_rating_value;
* });
* $this->newDB->table('ratings')
* ->insert([
* 'total' => $sum,
* 'count' => $count,
* 'rating' => $count ? floatval($sum / $count) : 0,
* 'recipes_id' => $recipe_id,
* 'created_at' => date('Y-m-d H:i:s'),
* 'updated_at' => date('Y-m-d H:i:s'),
* ]);
* throw new AbortRowImportException();
* },
* 'name' => 'recipe_id'
* ]
* ]
* ],
*]
*
* @param array $instructions
* @return void
*/
/**
* Helps to migration from legaulois2016 to legaulois2020
* @author Jérémy Munsch <jeremy.munsch@ldc.fr>
*/
abstract class LeGaulois2016Migration extends Migration
{
/** @var ConsoleOutput */
private $output = null;
/** @var ConsoleSectionOutput */
private $outputSection = null;
/** @var \Illuminate\Database\ConnectionInterface */
private $oldDB = null;
/** @var \Illuminate\Database\ConnectionInterface */
private $newDB = null;
/** @var array */
private $instructions = null;
public function __construct()
{
$oldDB = config(sprintf('database.connections.%s', config('database.oldDB')));
$newDB = config(sprintf('database.connections.%s', config('database.newDB')));
if (!$oldDB) {
throw new Exception(sprintf('Unknown database connection %s for newDB', config('database.newDB')));
}
if (!$newDB) {
throw new Exception(sprintf('Unknown database connection %s for oldDB', config('database.oldDB')));
}
$this->oldDB = DB::connection(config('database.oldDB'));
$this->newDB = DB::connection(config('database.newDB'));
}
public function up()
{
$this->instructions = $this->instruct();
$this->processData();
}
public function down()
{
// Do nothing
}
public function __get(string $name)
{
if ($name === 'oldDB') {
return $this->oldDB;
}
if ($name === 'newDB') {
return $this->newDB;
}
}
/**
* Set migration instructions to execute on "up" only
* @param array $instructions
* see Instructions in \App\Database\Migrations\LeGaulois2016Migration
*/
abstract public function instruct(): array;
/**
* Core execution on instruction
*
* @return void
*/
private function processData()
{
$getOldData = function ($keyOrKeys, $array) {
if (is_array($keyOrKeys)) {
$o = [];
foreach ($keyOrKeys as $key) {
$o[] = strval($array[$key]);
}
return implode('_', $o);
}
return $array[$keyOrKeys] ?? null;
};
foreach ($this->instructions as $oldTableName => $modelData) {
$migrationName = $oldTableName;
$oldTableName = $modelData['table'] ?? $oldTableName;
$rowNbrs = $this->oldDB->table($oldTableName)->count();
$rowInserted = 0;
$this->consoleLog($this->renderProgress($migrationName, $rowInserted, $rowNbrs));
$i = 0;
$page = 1;
$perPage = env('MIGRATIONPAGINATION', 300);
do {
$results = $this->oldDB->table($oldTableName)->paginate($perPage, ['*'], 'page', $page);
$i += $perPage;
$page++;
foreach ($results as $oldData) {
$model = null;
try { // Try to get exisiting model ID (to avoid SQL Integrity violations doing delete and create)
$model = $modelData['model']::findOrFail(
$getOldData($modelData['bind']['id'] ?? 0, (array)$oldData)
);
} catch (ModelNotFoundException $e) {
$model = new $modelData['model']();
}
try { // bind each property to it's data and save
foreach ($modelData['bind'] as $modelAttr => $oldModelAttr) {
if (is_array($oldModelAttr)) {
if (array_key_exists('apply', $oldModelAttr)) {
$argC = (new ReflectionFunction($oldModelAttr['apply']))->getNumberOfParameters();
$argV = [
$getOldData($oldModelAttr['name'], (array)$oldData)
];
if ($argC > 1) {
$argV[] = $model;
}
$model[$modelAttr] =
call_user_func_array($oldModelAttr['apply'], $argV) ??
$oldModelAttr['default'] ??
null;
} else {
$model[$modelAttr] =
$getOldData($oldModelAttr['name'], (array)$oldData) ??
$oldModelAttr['default'] ??
null;
}
} else {
$model[$modelAttr] = ((array)$oldData)[$oldModelAttr];
}
}
$model->save();
} catch (AbortRowImportException $e) {
// DO nothing
} finally {
++$rowInserted;
if (rand(0, 15) == 10) { // prevent screen ficklering
$this->consoleLog($this->renderProgress($migrationName, $rowInserted, $rowNbrs));
}
}
}
} while ($i <= $rowNbrs);
if (array_key_exists('comment', $modelData)) {
$migrationName .= sprintf(' <%s> ', $modelData['comment']);
}
$terminal = new Terminal();
$consoleLength = $terminal->getWidth();
$r = round(80 * 100 / $consoleLength) - strlen($migrationName);
$r = $r >= 0 ? $r : 0;
$this->consoleLog(sprintf('%s%smigrated', $migrationName, str_repeat(' ', $r)));
$this->consoleLog("", true); // Cariage return
}
}
/**
* Prints a message to the console
*
* @param string $message
* @param boolean $newLine
* @return void
*/
private function consoleLog(string $message, bool $newLine = false)
{
if (!$this->output) {
$this->output = new ConsoleOutput();
}
if ($newLine) {
$this->outputSection = null;
$this->output->write(sprintf("<info>%s</info>", $message));
} else {
if (!$this->outputSection) {
$this->outputSection = $this->output->section();
}
$this->outputSection->overwrite(sprintf('<info>%s</info>', $message));
}
}
/**
* Render the progressbar to a string
*
* @param string $oldTableName
* @param integer $count
* @param integer $total
* @return string
*/
private function renderProgress(string $oldTableName, int $count, int $total)
{
$total = !$total ? 1 : $total;
$oldTableName = sprintf('%s ', $oldTableName);
$percent = $count * 100 / $total;
$percentText = sprintf(' [%d of %d]', $count, $total);
$terminal = new Terminal();
$consoleLength = $terminal->getWidth();
$bar = $consoleLength - strlen($oldTableName) - strlen($percentText);
$bar = $bar < 0 ? 0 : $bar;
$prog = round($percent / 100 * $bar);
$rep = $bar - $prog;
return $oldTableName . str_repeat('#', $prog) . str_repeat('_', $rep >= 0 ? $rep : 0) . $percentText;
}
}