Skip to content

Instantly share code, notes, and snippets.

@kmuenkel
Last active September 25, 2023 16:18
Show Gist options
  • Save kmuenkel/9a05a88677c9a4982ecfd813a7eb3713 to your computer and use it in GitHub Desktop.
Save kmuenkel/9a05a88677c9a4982ecfd813a7eb3713 to your computer and use it in GitHub Desktop.
<?php
namespace Database\Seeders;
use Illuminate\Support\Arr;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\{DB, Schema};
/**
* @property string|Model $model
* @property string $csvName
* @property string $resourceRootPath
* @property string[] $fieldMap
*/
trait CsvSeeder
{
protected function getModel(): Model
{
return is_string($this->model) ? app($this->model) : $this->model;
}
protected function getCsvName(): string
{
return property_exists($this, 'csvName') ? $this->csvName : $this->getModel()->getTable() . '.csv';
}
protected function getResourcePath(): string
{
return trim(property_exists($this, 'resourceRootPath') ? $this->resourceRootPath : __DIR__ . '/../seeds', '/');
}
/**
* @return false|resource
*/
protected function getHandle()
{
$path = '/' . $this->getResourcePath() . '/' . $this->getCsvName();
return fopen($path, 'r');
}
public function run(): void
{
$handle = $this->getHandle();
$headers = fgetcsv($handle);
try {
$this->disableStrictMode();
$this->openTableForSeeding();
$this->performSeed($handle, $headers);
} finally {
fclose($handle);
$this->closeTableForSeeding();
}
}
/**
* @param resource $handle
*/
protected function performSeed($handle, array $headers): void
{
$rowNum = 0;
while (!$this->rowIsEmpty($cells = fgetcsv($handle, 0, ',', '"', '"'))) {
if (!$this->hasPopulatedCells($cells)) { //This could come up if the row is all commas
break;
}
$row = $this->mutateRow(array_combine($headers, $cells));
$this->saveRow($row, $rowNum++);
}
}
protected function disableStrictMode(): void
{
$connectionConfig = config('database.connections.' . config('database.default'));
$connectionConfig['strict'] = false; //Allow for "0000-00-00" date values
config(['database.connections.' . config('database.default') => $connectionConfig]);
}
protected function openTableForSeeding(): void
{
$this->getModel()::unguard();
Schema::disableForeignKeyConstraints();
}
protected function closeTableForSeeding(): void
{
Schema::enableForeignKeyConstraints();
$this->getModel()::reguard();
}
protected function hasPopulatedCells($cells): bool
{
return (bool)array_filter($cells, fn ($cell) => $cell !== '');
}
protected function rowIsEmpty($cells): bool
{
return in_array($cells, [false, [null]], true);
}
/**
* Allow for final row alteration to be performed by the child class.
*
* @param array $row
* @return array
*/
protected function mutateRow(array $row): array
{
return $row;
}
/**
* @param array $row
* @param int $rowNum
* @return void
*/
protected function saveRow(array $row, int $rowNum): void
{
//Preempt type failures stemming from how CSVs don't typically differentiate between empty strings and nulls
array_walk($row, fn (&$value) => $value = in_array($value, ['NULL', ''], true) ? null : $value);
// $this->getModel()->timestamps && Arr::forget($row, [$this->model::CREATED_AT, $this->model::UPDATED_AT]);
// in_array(SoftDeletes::class, class_uses_recursive($this->model)) && Arr::forget($row, [$this->model->getDeletedAtColumn()]);
$builder = DB::table($this->getModel()->getTable()); //Circumvent any mutators that may inhibit the raw data
$keyName = $this->getModel()->getKeyName(); //We only need the Model configs, not its behavior
$key = Arr::get($row, $keyName);
$exists = $key && $builder->where($keyName, $key)->exists();
$exists ? $builder->where($keyName, $key)->update($row) : $builder->insert($row);
}
}
<?php
namespace Database\Seeders;
use DB;
use Schema;
use stdClass;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\QueryException;
trait ResetIndexes
{
/**
* @param Model $model
*/
public function reset(Model $model)
{
$constraints = !app()->runningUnitTests() ? DB::table('INFORMATION_SCHEMA.KEY_COLUMN_USAGE')
->select(['TABLE_NAME', 'COLUMN_NAME'])
->where([
'REFERENCED_TABLE_SCHEMA' => $model->getConnection()->getDatabaseName(),
'REFERENCED_TABLE_NAME' => $model->getTable(),
'REFERENCED_COLUMN_NAME' => $model->getKeyName()
])
->get() : collect();
if (!($adjustment = $model->min($model->getKeyName()) - 1)) {
return;
}
$adjustmentValue = DB::raw('`'.$model->getKeyName()."` - $adjustment");
try {
!app()->runningUnitTests() && Schema::disableForeignKeyConstraints();
DB::table($model->getTable())->update([$model->getKeyName() => $adjustmentValue]);
} finally {
!app()->runningUnitTests() && Schema::enableForeignKeyConstraints();
}
$aiValue = $model->max($model->getKeyName()) + 1;
try {
DB::statement("ALTER TABLE " . $model->getTable() . " AUTO_INCREMENT = $aiValue");
} catch (QueryException $exception) {
// If this DB user doesn't have permission to do this, just don't worry about it
}
try {
!app()->runningUnitTests() && Schema::disableForeignKeyConstraints();
$constraints->each(function (stdClass $result) use ($adjustment) {
$adjustmentValue = DB::raw('`' . $result->COLUMN_NAME . "` - $adjustment");
DB::table($result->TABLE_NAME)->update([$result->COLUMN_NAME => $adjustmentValue]);
});
} finally {
!app()->runningUnitTests() && Schema::enableForeignKeyConstraints();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment