Skip to content

Instantly share code, notes, and snippets.

@edwardyi
Last active May 14, 2022 00:46
Show Gist options
  • Save edwardyi/e441d036fbf248b389bef4c672a1b88d to your computer and use it in GitHub Desktop.
Save edwardyi/e441d036fbf248b389bef4c672a1b88d to your computer and use it in GitHub Desktop.
BatchBase crud for laravel
<?php
namespace App\Traits\Service\Batch;
use App\Traits\Repository\Collectable;
use App\Validate\UtilDate;
use App\Exceptions\Batch\{ItemIdNotFoundException, ItemIdMissingException};
use Exception;
use Illuminate\Support\{Arr, Collection};
/**
* BatchBase
*/
abstract class BatchBase
{
use Collectable;
/**
* @param string $pkName;
*/
public $pkName = '';
/**
* @param object $existingModels;
*/
public $existingModels;
/**
* @param object $dbParentItemModels;
*/
public $dbParentItemModels;
/**
* @param array $availableFields;
*/
public $availableFields;
/**
* getAllowedUserInputColumns
*
* @return array
*/
abstract public function getAllowedUserInputColumns();
/**
* getModelId
*
* @return array
*/
abstract public function getModelId();
/**
* getExistingModelByIds
*
* @param array $ids
*
* @return array
*/
abstract public function getExistingModelByIds(array $ids);
/**
* construct
*/
public function __construct()
{
$this->init();
}
/**
* init
*
* @return void
*/
public function init()
{
$this->availableFields = $this->model->getFillable();
}
/**
* setDbParentItemModels
*
* @param object $dbParentItemModels
*
* @return object
*/
public function setDbParentItemModels($dbParentItemModels)
{
// convert to collection
if (gettype($dbParentItemModels) == 'array') {
$dbParentItemModels = collect($dbParentItemModels);
}
$this->dbParentItemModels = $dbParentItemModels;
}
/**
* getFilteredAllowInput
*
* @return array
*/
public function getFilteredAllowInput($excludes = ['ID'])
{
return array_filter($this->getAllowedUserInputColumns(), function($item) use ($excludes) {
return !in_array($item, $excludes);
});
}
/**
* validateItemUpdateByFilterOptions
*
* @param array $inputItems
* @param array $filterOptions
* @param string $keyBy = 'ID'
*
* @return array
*/
public function validateItemUpdateByFilterOptions($inputItems, $filterOptions, $keyBy = 'ID')
{
$isUpdate = count($filterOptions) == 0;
if ($isUpdate) {
// validate item id not exists
array_filter($inputItems, function($rowItem) use ($keyBy) {
if (!array_key_exists($keyBy, $rowItem)) {
throw new ItemIdMissingException("item明細設定有誤:".$this->pkName);
}
});
}
}
/**
* addValidatedDateToUserInputs
*
* @param array $inputs
* @param array $dateFields
*
* @return array
*/
public function addValidatedDateToUserInputs(array $inputs, array $dateFields)
{
$inputItems = $inputs['ITEMS'];
foreach ($inputItems as $rowIndex => $rowItems) {
foreach ($dateFields as $rowDate) {
if (isset($rowItems[$rowDate])) {
UtilDate::get($rowItems, ['field' => $rowDate, 'hint' => $rowDate.'日期格式有誤']);
}
}
$inputItems[$rowIndex] = $rowItems;
}
$inputs['ITEMS'] = $inputItems;
return $inputs;
}
/**
* addCodeToUserInputs
*
* @param array $data
*
* @return array
*/
public function addCodeToUserInputs(array $data)
{
// count how many records in database
$dbSn = $this->model->get()->count();
$this->setExistingModelsFromInput($data['ITEMS']);
$existingData = $this->existingModels->toArray();
foreach ($data['ITEMS'] as $rowIndex => $rowItem) {
$hasId = Arr::get($rowItem, 'ID', false);
$hasExistingData = $hasId ? Arr::get($existingData, $hasId, false) : false;
// default using existing data or get a new serial number
$dbSnCode = $hasExistingData ? $hasExistingData['CODE'] : SerialNumber::getSnLeftPadFromLength($rowIndex + $dbSn + 1, $this->getSerialNumberLength());
// replace with user CODE from input
if (array_key_exists('CODE', $rowItem)) {
$dbSnCode = $rowItem['CODE'];
}
$data['ITEMS'][$rowIndex]['CODE'] = $dbSnCode;
}
return $data;
}
/**
* setExistingModelsFromInput
*
* @param array $data
*
* @return array
*/
public function setExistingModelsFromInput(array $rawItemInputs)
{
if (!$this->existingModels) {
$collectInputs = collect($rawItemInputs);
$ids = $collectInputs->keyBy('ID')
->filter(function($item, $rowKey) { return $rowKey != ""; })
->keys()
->toArray();
// get existing data by ids
$this->existingModels = $this->getExistingModelByIds($ids)->keyBy('ID');
}
}
/**
* getItemInputs
*
* @param array $rawItemInputs
*
* @return array
*/
public function getItemInputs(array $rawItemInputs)
{
if (!method_exists($this, 'getAllowedUserInputColumns')) {
throw new Exception("[BatchBase] getAllowedUserInputColumns not defined!");
}
if (!method_exists($this, 'getModelId')) {
throw new Exception("[BatchBase] getModelId not defined!");
}
if (!method_exists($this, 'getExistingModelByIds')) {
throw new Exception("[BatchBase] getExistingModelByIds not defined");
}
$cleans = [];
$cleans['insert'] = [];
$cleans['update'] = [];
$cleans['delete'] = [];
// get all ids for existing model
$collectInputs = collect($rawItemInputs);
$this->setExistingModelsFromInput($rawItemInputs);
$existingData = $this->existingModels->toArray();
// build format inputs by fillabe fields start
$model = $this->model;
$formattedInputs = [];
foreach ($this->availableFields as $rowField) {
$collectInputs->map(function($item, $itemIndex) use(&$formattedInputs, $rowField, $model) {
$formattedInputs[$itemIndex][$rowField] = Arr::get($item, $rowField, $model->$rowField);
});
}
unset($rawItemInputs);
unset($collectInputs);
// build format inputs by fillabe fields end
foreach ($formattedInputs as $rowIndex => $rowItemInput) {
$hasId = Arr::get($rowItemInput, 'ID', true);
$rowId = is_null($hasId) ? $this->getModelId() : $hasId;
// merge existing data
$rowExistingData = Arr::get($existingData, $rowId, []);
$isInsert = count($rowExistingData) == 0;
$rowItemInput = array_merge($rowExistingData, $rowItemInput);
$rowAction = ($isInsert === true) ? 'insert' : 'update';
$cleans[$rowAction][$rowIndex] = $this->input($rowItemInput, $this->getAllowedUserInputColumns());
$cleans[$rowAction][$rowIndex]['ID'] = $rowId;
if ($isInsert === true) {
$cleans[$rowAction][$rowIndex]['INSERT_USER'] = $this->getInsertUser();
$cleans[$rowAction][$rowIndex]['INSERT_DATE'] = $this->getCurrentDateFromFormat("Y-m-d H:i:s");
$cleans[$rowAction][$rowIndex]['MODIFY_USER'] = NULL;
$cleans[$rowAction][$rowIndex]['MODIFY_DATE'] = NULL;
} else {
$cleans[$rowAction][$rowIndex]['INSERT_USER'] = $rowItemInput['INSERT_USER'];
$cleans[$rowAction][$rowIndex]['INSERT_DATE'] = $rowItemInput['INSERT_DATE'];
$cleans[$rowAction][$rowIndex]['MODIFY_USER'] = $this->getInsertUser();
$cleans[$rowAction][$rowIndex]['MODIFY_DATE'] = $this->getCurrentDateFromFormat("Y-m-d H:i:s");
}
// sort with key
ksort($cleans[$rowAction][$rowIndex]);
}
// find extra item to delete
if ($this->dbParentItemModels) {
$dbItemIds = $this->dbParentItemModels->keyBy('ID')->keys()->toArray();
$inputItemIds = collect($formattedInputs)->keyBy('ID')->keys()->toArray();
$cleans['delete'] = array_diff($dbItemIds, $inputItemIds);
}
return $cleans;
}
/**
* updateBatch
*
* @param array $data
*
* @return array
*/
public function updateBatch(array $data)
{
$result = [];
$updateData = collect($data)->keyBy('ID');
$this->existingModels->map(function($item, $rowIndex) use ($updateData, &$result) {
$rowItemId = $item->ID;
$isUpdate = $item->update($updateData[$rowItemId]);
$result[$rowItemId] = ['status' => $isUpdate, 'item' => $item->toArray()];
});
return $result;
}
/**
* execute
*
* @param array $cleanInputs
*
* @return array
*/
public function execute(array $cleanInputs)
{
$deleteInputs = $cleanInputs['delete'];
$insertOrUpdateInputs = array_merge($cleanInputs['insert'], $cleanInputs['update']);
$isDeleteResult = false;
$insertOrUpdateResult = false;
// delete for update
if (count($deleteInputs) > 0) {
$isDeleteResult = $this->model->destroy($deleteInputs);
}
if (count($insertOrUpdateInputs) > 0) {
$insertOrUpdateResult = $this->model::insertOnDuplicateKey($insertOrUpdateInputs, array_keys(reset($insertOrUpdateInputs)));
}
return [
'is_delete' => [
'status' => $isDeleteResult,
'data' => $deleteInputs
],
'insert_or_update' => [
'status' => $insertOrUpdateResult,
'data' => $insertOrUpdateInputs
]
];
}
}
<?php
namespace App\Traits\Repository;
use App\Models\Model;
use Illuminate\Support\{Arr, Collection};
/**
* Collectable
*/
trait Collectable
{
/**
* keys
*/
public function keys(Collection $collection, string $keyIndex, $isUnique = true)
{
$keyObj = $collection->keyBy($keyIndex)->keys();
return $isUnique ? $keyObj->unique() : $keyObj;
}
/**
* remove model append
*
* @param object $model
* @param string $appendKey
*
* @return array
*/
public function removeModelAppend(Model $model, string $appendKey)
{
$result = $model->toArray();
unset($result[$appendKey]);
return $result;
}
/**
* removeModelAttributes
*
* @param Model $model
* @param array $attributes = []
* @param bool $isRemove = true
*
* @return array
*/
public function removeModelAttributes(Model $model, array $attributes = [], $isRemove = true)
{
$result = $model->toArray();
if ($isRemove == false) {
return $result;
}
foreach ($attributes as $rowAttribute) {
unset($result[$rowAttribute]);
}
return $result;
}
/**
* clean
*
* @param array $data
* @param string $field
*
* @return array
*/
public function clean(&$data, $field)
{
if (is_null(Arr::get($data, $field))) {
unset($data[$field]);
}
return $data;
}
/**
* input
*
* @param array $data
* @param array $modelFields
*
* @return array
*/
public function input($data, $modelFields)
{
$input = [];
foreach ($data as $rowKey => $rowValue) {
if (in_array($rowKey, $modelFields)) {
$input[$rowKey] = $rowValue;
}
}
return $input;
}
/**
* inputDetailByItemLength
*
* @param array $data
* @param integer $legnth
* @param array $modelFields
*
* @return array
*/
public function inputDetailByItemLength(array $data,int $legnth, $modelFields)
{
$rowDetailInput = [];
for($counter=0; $counter<$legnth; $counter++) {
$rowDetailInput[$counter] = [];
foreach ($modelFields as $rowField) {
$rowDetailInput[$counter][$rowField] = Arr::get($data, sprintf('%s.%s', $rowField, $counter), '');
}
}
return $rowDetailInput;
}
}
<?php
namespace App\Traits\Service\Batch;
use App\Models\ProductRouting as ProductRoutingModel;
use App\Repositories\ProductRoutingRepository;
/**
* App\Traits\Service\Batch\ProductRouting
*/
class ProductRouting extends BatchBase
{
/**
* @param string $pkName;
*/
public $pkName = 'product_routing.id';
/**
* @param object $model;
*/
public $model;
/**
* @param object $productRoutingRepo;
*/
public $productRoutingRepo;
/**
* construct
*/
public function __construct(
ProductRoutingModel $productRouting,
ProductRoutingRepository $productRoutingRepo
) {
$this->model = $productRouting;
$this->productRoutingRepo = $productRoutingRepo;
parent::__construct();
}
/**
* getAllowedUserInputColumns
*
* @return array
*/
public function getAllowedUserInputColumns()
{
return $this->availableFields;
}
/**
* getModelId
*
* @return array
*/
public function getModelId()
{
return $this->productRoutingRepo->getIdNumber($this->model);
}
/**
* getExistingModelByIds
*
* @param array $ids
*
* @return array
*/
public function getExistingModelByIds(array $ids)
{
return $this->productRoutingRepo->whereIn('ID', $ids)->get();
}
/**
* insert
*
* @param array $data
*
* @return bool
*/
public function insert(array $data)
{
return $this->model->insert($data);
}
}
<?php
namespace App\Validate;
use \Illuminate\Support\Arr;
use Carbon\Carbon;
use \Exception;
/**
* UtilDate
*/
class UtilDate
{
/**
* validate
*
* @param string $dateValue
* @param string $hint
*
* @return string
*/
public static function validate(&$dateValue, $hint, $format='Y-m-d')
{
try {
if (is_array($dateValue)) {
foreach ($dateValue as $rowIndex => $rowDate) {
$dateValue[$rowIndex] = Carbon::parse(trim($rowDate))->format($format);
}
} else {
$dateValue = Carbon::parse(trim($dateValue))->format($format);
}
} catch (\Carbon\Exceptions\InvalidFormatException $e) {
throw new Exception($hint);
}
return $dateValue;
}
/**
* get
*
* @param array $dateValue
* @param array $options
*
* @return string|bool
*/
public static function get(array &$data, array $options)
{
$field = Arr::get($options, 'field');
if (!array_key_exists($field, $data)) {
return false;
}
try {
$hint = Arr::get($options, 'hint');
$format = Arr::get($options, 'format', 'Y-m-d');
$defaultHint = is_null($hint) ? "The {$field} date is invalid" : $hint;
if ($data[$field]) {
$data[$field] = self::validate($data[$field], $defaultHint, $format);
}
} catch (Exception $e) {
throw new Exception($e->getMessage());
}
return $data[$field];
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment