From 78641da2b0f0c10ea602ff5ba592290e041585ab Mon Sep 17 00:00:00 2001 From: Mihail Date: Thu, 26 Nov 2015 15:24:41 +0200 Subject: [PATCH] rewrite parser writer and parser controller - add array model validator --- backend/components/base/BaseController.php | 32 ++++++++++++++++++++++++++++++++ backend/controllers/CrossingUploadController.php | 48 +++++++----------------------------------------- backend/controllers/ParserController.php | 118 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------- backend/views/crossing-upload/index.php | 10 ++-------- backend/views/parser/index.php | 8 ++++---- backend/views/templates/parser_massage.php | 14 ++++++++++++++ common/components/PriceWriter.php | 163 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------------------- common/components/parsers/CustomConverter.php | 28 ---------------------------- common/components/parsers/config.php | 1 - 9 files changed, 230 insertions(+), 192 deletions(-) create mode 100644 backend/views/templates/parser_massage.php diff --git a/backend/components/base/BaseController.php b/backend/components/base/BaseController.php index 87d48b2..4f7aab4 100755 --- a/backend/components/base/BaseController.php +++ b/backend/components/base/BaseController.php @@ -137,4 +137,36 @@ class BaseController extends Controller { } } + /** + * @param $mode - int: 0 - fetch from cache, - 1 - put in cache, <2 - delete from cache + * @param $data - array + * @param $configuration - array + * @throws \ErrorException + */ + protected function parserCacheHandler( $mode, &$data = [], &$configuration = [] ){ + switch ( $mode ) { + case 0: + if (Yii::$app->getCache()->get('parser_data') && Yii::$app->getCache()->get('parser_configuration')) { + $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); + $configuration = unserialize(Yii::$app->getCache()->get('parser_configuration')); + } else { + throw new \ErrorException('Ошибка кеша'); + } + break; + + case 1: + Yii::$app->getCache()->set('parser_data', json_encode($data), 1800); + // сохраняем в кеш модель - в ней настройки для дальнейшей обработки данных + Yii::$app->getCache()->set('parser_configuration', serialize($configuration), 1800); + break; + + default: + if( Yii::$app->getCache()->exists('parser_data') ) + Yii::$app->getCache()->delete('parser_data'); + + if( Yii::$app->getCache()->exists('parser_configuration') ) + Yii::$app->getCache()->delete('parser_configuration'); + } + + } } \ No newline at end of file diff --git a/backend/controllers/CrossingUploadController.php b/backend/controllers/CrossingUploadController.php index f6b6167..948defa 100755 --- a/backend/controllers/CrossingUploadController.php +++ b/backend/controllers/CrossingUploadController.php @@ -82,7 +82,7 @@ class CrossingUploadController extends BaseController //запускаем парсинг $data = $model->readFile(); // сохраняем в кеш отпарсенные даные - $this->cacheHandler( 1, $data, $model ); + $this->parserCacheHandler( 1, $data, $model ); } else if (Yii::$app->getCache()->get('parser_data')) { $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); } @@ -128,7 +128,7 @@ class CrossingUploadController extends BaseController $arr = $model->toArray(); // получим данные из кеша - $this->cacheHandler( 0, $data, $configuration ); + $this->parserCacheHandler( 0, $data, $configuration ); // соотнесем отпарсенные данные с соответствием полученным от пользователя // для этого преобразуем массив отпарсенных данных - назначим ключи согласно соответствию @@ -149,18 +149,17 @@ class CrossingUploadController extends BaseController if ( $crosses_model->ManualInsertWithIgnore( $data ) ) { - Yii::$app->session->setFlash( $type_msg, $msg ); - // очистим кеш - $this->cacheHandler( 2 ); + $this->parserCacheHandler( 2 ); + + if ( file_exists($configuration['file_path']) ) + unlink( $configuration['file_path'] ); - if (file_exists($configuration['file_path'])) - unlink($configuration['file_path']); + Yii::$app->session->setFlash( $type_msg, $msg ); return $this->render('index', ['model' => $configuration]); } - } else { // не прошла валидация формы загрузки файлов $errors_str = ''; @@ -212,39 +211,6 @@ class CrossingUploadController extends BaseController } - /** - * @param $mode - int: 0 - fetch from cache, - 1 - put in cache, <2 - delete from cache - * @param $data - array - * @param $configuration - array - * @throws \ErrorException - */ - protected function cacheHandler( $mode, &$data = [], &$configuration = [] ){ - switch ( $mode ) { - case 0: - if (Yii::$app->getCache()->get('parser_data') && Yii::$app->getCache()->get('parser_configuration')) { - $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); - $configuration = unserialize(Yii::$app->getCache()->get('parser_configuration')); - } else { - throw new \ErrorException('Ошибка кеша'); - } - break; - - case 1: - Yii::$app->getCache()->set('parser_data', json_encode($data), 1800); - // сохраняем в кеш модель - в ней настройки для дальнейшей обработки данных - Yii::$app->getCache()->set('parser_configuration', serialize($configuration), 1800); - break; - - default: - if( Yii::$app->getCache()->exists('parser_data') ) - Yii::$app->getCache()->delete('parser_data'); - - if( Yii::$app->getCache()->exists('parser_configuration') ) - Yii::$app->getCache()->delete('parser_configuration'); - } - - } - protected function reverseCrosses ( $data ) { // для доп массива обратных строк diff --git a/backend/controllers/ParserController.php b/backend/controllers/ParserController.php index 4b5cf87..898c0f7 100755 --- a/backend/controllers/ParserController.php +++ b/backend/controllers/ParserController.php @@ -72,59 +72,26 @@ class ParserController extends BaseController public function actionResults($mode = 0) { - $model = new UploadFileParsingForm(['mode' => $mode]); + $model = new UploadFileParsingForm( ['mode' => $mode] ); $data = []; - if ($model->load(Yii::$app->request->post())) { + if ( $model->load(Yii::$app->request->post()) ) { $model->file = UploadedFile::getInstance($model, 'file'); - // первый проход - валидируем, сохраняем файл, ложим в кеш (для ручной загрузки) отпарсенные данные и параметры модели (потом при записи в базу данных они пригодятся) - if ($model->validate()) { - // запишем дату загрузки файла в таблицу файлов поставщика (ImportersFiles) - $files_model = new ImportersFiles(); - // id поставщика получим из конфигурации - $files_model->load(['ImportersFiles' => $model->toArray()]); - try { - $files_model->save(); - } catch (ErrorException $e) { - throw $e; - } - // получим id только что записанной записи - его запишем в название файла - $model->record_id = $files_model->find() - ->where(['importer_id' => $files_model->importer_id]) - ->orderBy(['id' => SORT_DESC]) - ->one() - ->id; - - $file_name = $model->record_id . '.' . $model->file->extension; - - if ($model->mode) { - $model->file_path = Yii::getAlias('@temp_upload') . '/' . $file_name; - } else { - $model->file_path = Yii::getAlias('@manual_upload') . '/' . $file_name; - } - - $model->file->saveAs($model->file_path); + // первый проход - валидируем, + // сохраняем файл, + // ложим в кеш (для ручной загрузки) отпарсенные данные и параметры модели + // (потом при записи в базу данных они пригодятся) + if ( $model->validate() ) { + // сохраним файл и создадим модель - ImportersFiles + $files_model = $this->saveParserFile($model); // для авто загрузки, обработка завершена - if ($model->mode) { + if ( $model->mode ) { $model->success = true; - return $this->render('index', ['model' => $model]); } // === ручная загрузка =========== //запускаем парсинг - // доп. опции для парсера - $options = ['converter_conf' => - ['importer_id' => $files_model->importer_id] - ]; - - if( ! $model->action ) // обработка с кастомным разделителем - $options['$delimiter'] = $model->delimiter; - - $data = $model->readFile( $options ); - // сохраняем в кеш отпарсенные даные - Yii::$app->getCache()->set('parser_data', json_encode($data), 1800); - // сохраняем в кеш модель - в ней настройки для дальнейшей обработки данных - Yii::$app->getCache()->set('parser_configuration', serialize($model), 1800); + $data = $this->parseDataFromFile( $files_model, $model ); } else { // не прошла валидация форма загрузки файлов @@ -136,7 +103,7 @@ class ParserController extends BaseController $model->throwStringErrorException(); } // листаем пагинатором, или повторно вызываем - считываем из кеша отпрасенные данные - } else if (Yii::$app->getCache()->get('parser_data')) { + } else if ( Yii::$app->getCache()->get('parser_data') ) { $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); @@ -180,12 +147,7 @@ class ParserController extends BaseController $arr = $model->toArray(); // получим данные из кеша - if (Yii::$app->getCache()->get('parser_data') && Yii::$app->getCache()->get('parser_configuration')) { - $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); - $configuration = unserialize(Yii::$app->getCache()->get('parser_configuration')); - } else { - throw new \ErrorException('Ошибка кеша'); - } + $this->parserCacheHandler( 0, $data, $configuration ); // соотнесем отпарсенные данные с соответсивем полученным от пользователя // для этого преобразуем массив отпарсенных данных - назначим ключи согласно соответствию @@ -198,14 +160,12 @@ class ParserController extends BaseController $writer->setMode(0); //web-режим if ( $writer->writePriceToDB() ) { - $configuration['success'] = true; - // все прошло успешно - очищаем кеш - Yii::$app->getCache()->delete('parser_data'); - Yii::$app->getCache()->delete('parser_configuration'); + $this->parserCacheHandler( 2 ); if( file_exists($configuration['file_path']) ) unlink($configuration['file_path']); + Yii::$app->session->setFlash( $writer->getValidatedTypeMsg(), $writer->getValidatedMsg() ); return $this->render('index', ['model' => $configuration]); }; @@ -291,5 +251,53 @@ class ParserController extends BaseController } + /** + * сохраняет файл на диск и регистрирует в ImportersFiles + * @param $model - модель с настройками + * @return ImportersFiles + * @throws ErrorException + * @throws \Exception + */ + protected function saveParserFile ($model) + { + $files_model = new ImportersFiles(); + // id поставщика получим из конфигурации + $files_model->load(['ImportersFiles' => $model->toArray()]); + try { + $files_model->save(); + } catch (ErrorException $e) { + throw $e; + } + // получим id только что записанной записи - его запишем в название файла + $model->record_id = $files_model->id; + + $file_name = $model->record_id . '.' . $model->file->extension; + + if ($model->mode) { + $model->file_path = Yii::getAlias('@temp_upload') . '/' . $file_name; + } else { + $model->file_path = Yii::getAlias('@manual_upload') . '/' . $file_name; + } + + $model->file->saveAs($model->file_path); + + return $files_model; + } + protected function parseDataFromFile ( $files_model, $model ) + { + // доп. опции для парсера + $options = ['converter_conf' => + ['importer_id' => $files_model->importer_id] + ]; + + if( ! $model->action ) // обработка с кастомным разделителем + $options['$delimiter'] = $model->delimiter; + + $data = $model->readFile( $options ); + // сохраняем в кеш отпарсенные даные + $this->parserCacheHandler( 1, $data, $model ); + + return $data; + } } diff --git a/backend/views/crossing-upload/index.php b/backend/views/crossing-upload/index.php index 1d07aaf..920bf67 100755 --- a/backend/views/crossing-upload/index.php +++ b/backend/views/crossing-upload/index.php @@ -20,14 +20,8 @@ use yii\helpers\ArrayHelper; session->getFlash('success')) - { - echo Html::tag('p', $msg ,['class'=>'bg-success']); - } elseif ($msg = \Yii::$app->session->getFlash('warning')) - { - echo Html::tag('p', $msg, ['class' => 'bg-warning']); - } + // подключим шаблон сообщения + echo $this->render('../templates/parser_massage'); ?> diff --git a/backend/views/parser/index.php b/backend/views/parser/index.php index 44864c4..337b8b8 100755 --- a/backend/views/parser/index.php +++ b/backend/views/parser/index.php @@ -20,9 +20,6 @@ if ( $model->mode ) { if (!$model->action) { $model->action = 1; } - if ($model->success) { // вернулись после успешной загрузки данного файла - echo Html::tag('h3', 'Файл успешно загружен',['class'=>'bg-success']); - } ?>

Загрузка прайсов поставщиков

@@ -49,7 +46,10 @@ if ( $model->mode ) { 'btn btn-primary']) ?> - + render('../templates/parser_massage'); + ?> diff --git a/backend/views/templates/parser_massage.php b/backend/views/templates/parser_massage.php new file mode 100644 index 0000000..f05a9b0 --- /dev/null +++ b/backend/views/templates/parser_massage.php @@ -0,0 +1,14 @@ +session->getFlash('success') ) { + echo Html::tag('p', $msg, ['class' => 'bg-success']); +} elseif ( $msg = \Yii::$app->session->getFlash('warning') ) { + echo Html::tag('p', $msg, ['class' => 'bg-warning']); +}?> + + + + + diff --git a/common/components/PriceWriter.php b/common/components/PriceWriter.php index 434fd9c..8d4d7e1 100755 --- a/common/components/PriceWriter.php +++ b/common/components/PriceWriter.php @@ -13,6 +13,7 @@ use yii\base\ErrorException; use backend\models\ImportersFiles; use backend\models\Importers; use backend\models\Details; +use common\components\ModelArrayValidator; /** * Class PriceWriter @@ -37,6 +38,17 @@ class PriceWriter */ protected $data; + /** + * @var - сообщение валидатора об ошибках + */ + protected $validated_msg; + /** + * @var - тип сообщения валидатора - success, warning + */ + protected $validated_type_msg; + + + function __construct() { set_time_limit(300); @@ -66,6 +78,21 @@ class PriceWriter $this->data = $data; } + /** + * @return mixed + */ + public function getValidatedMsg() + { + return $this->validated_msg; + } + + /** + * @return mixed + */ + public function getValidatedTypeMsg() + { + return $this->validated_type_msg; + } public function writePriceToDB() @@ -81,44 +108,78 @@ class PriceWriter throw new \ErrorException(implode(', ', $files_model->getErrors())); } - // 2. запишем полученные данные в таблицу товаров (Details) - $details_model = new Details(); + // 2. конвертируем данные // только для ручной загрузки, в авто режиме все делает конвертер при первом же проходе (в процессе парсинга) - if ($this->mode == 0) { - // преобразуем числовые значения - foreach ($this->data as &$row) { - if (isset($row['PRICE'])) - $row['PRICE'] = \Yii::$app->converter->convertTo('float', $row['PRICE']); - - if (isset($row['BOX'])) - $row['BOX'] = \Yii::$app->converter->convertTo('integer', $row['BOX']); - // присвоим полный артикул - - if (isset($row['ARTICLE'])) { - - $row['FULL_ARTICLE'] = $row['ARTICLE']; - - if ((int)$this->configuration['delete_prefix']) { - $row = \Yii::$app->converter->convertTo('Article', $row, ['importer_id' => $this->configuration['importer_id']]); - } else { - if (isset($row['ARTICLE'])) - $row['ARTICLE'] = \Yii::$app->converter->convertTo('Article', $row['ARTICLE']); - } - } + if ( $this->mode == 0 ) { + // преобразуем значения перед записью в БД + $this->convertDataByConfiguration(); - if (isset($row['ADD_BOX'])) - $row['ADD_BOX'] = \Yii::$app->converter->convertTo('integer', $row['ADD_BOX']); + } + //3. провалидируем полученные данные моделью - Details + $details_model = $this->validateByDetailsModel(); - // проверим все ли обязательные колонки были указаны пользователем - $details_model->load(['Details' => $row]); - if (!$details_model->validate()) - $details_model->throwStringErrorException(key($this->data)); + //4. дополним данные значением импортера и даты обновления цены + $this->data = CustomArrayHelper::addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]); + //5. запишем данные в связанные таблицы + $this->writePriceInTransaction($details_model, $files_model, $update_date); + + return true; + } + + public function deletePriceFromDB() + { + $importer_id = ''; + $update_date = ''; + + if (isset($this->configuration['importer_id'])) + $importer_id = $this->configuration['importer_id']; + + if (isset($this->configuration['update_date'])) + $update_date = $this->configuration['update_date']; + + if (!$importer_id) { + throw new \ErrorException('Не указан поставщик прайса для удаления'); + } elseif (!$update_date) { + throw new \ErrorException('Не указана дата загрузки прайса для удаления'); + } + + $this->deletePriceInTransaction( $importer_id, $update_date ); + + return true; + } + + /** + * ковертирует отпарсенные данные конвертером по конфигурации + */ + protected function convertDataByConfiguration () + { + foreach ($this->data as &$row) { + if (isset($row['PRICE'])) + $row['PRICE'] = \Yii::$app->converter->convertTo('float', $row['PRICE']); + + if (isset($row['BOX'])) + $row['BOX'] = \Yii::$app->converter->convertTo('integer', $row['BOX']); + // присвоим полный артикул + + if (isset($row['ARTICLE'])) { + + $row['FULL_ARTICLE'] = $row['ARTICLE']; + + if ((int)$this->configuration['delete_prefix']) { + $row = \Yii::$app->converter->convertTo('Article', $row, ['importer_id' => $this->configuration['importer_id']]); + } else { + if (isset($row['ARTICLE'])) + $row['ARTICLE'] = \Yii::$app->converter->convertTo('Article', $row['ARTICLE']); + } } + + if (isset($row['ADD_BOX'])) + $row['ADD_BOX'] = \Yii::$app->converter->convertTo('integer', $row['ADD_BOX']); } + } - // дополним данные значением импортера и даты обновления цены - $this->data = CustomArrayHelper::addColumns($this->data, ['IMPORT_ID' => $this->configuration['importer_id'], 'timestamp' => $update_date]); + protected function writePriceInTransaction($details_model, $files_model, $update_date){ $transaction = \Yii::$app->db->beginTransaction(); try { @@ -150,28 +211,9 @@ class PriceWriter throw new \ErrorException($e->getMessage()); } - - return true; } - public function deletePriceFromDB() - { - - $importer_id = ''; - $update_date = ''; - - if (isset($this->configuration['importer_id'])) - $importer_id = $this->configuration['importer_id']; - - if (isset($this->configuration['update_date'])) - $update_date = $this->configuration['update_date']; - - if (!$importer_id) { - throw new \ErrorException('Не указан поставщик прайса для удаления'); - } elseif (!$update_date) { - throw new \ErrorException('Не указана дата загрузки прайса для удаления'); - } - + protected function deletePriceInTransaction( $importer_id, $update_date ){ $transaction = \Yii::$app->db->beginTransaction(); try { // 1. удалим из таблицы файлов поставщика (ImportersFiles) @@ -184,8 +226,8 @@ class PriceWriter $last_upload_time = ImportersFiles::find()->where(['importer_id' => $importer_id])->orderBy(['time_start' => SORT_DESC])->one()->time_start; - // 2. удалим прайс из таблицы товаров (Details) - $details_model = new Details(); + // 2. удалим прайс из таблицы товаров (Details) + $details_model = new Details(); $conditions = "import_id = {$importer_id} AND timestamp ='$update_date'"; $details_model->manualDelete( $conditions ); @@ -204,8 +246,19 @@ class PriceWriter $transaction->rollBack(); throw new \ErrorException($e->getMessage()); } - - return true; } + protected function validateByDetailsModel(){ + + $details_model = new Details(); + + $model_validator = new ModelArrayValidator( $details_model ); + $this->data = $model_validator->validate( $this->data ); + $this->validated_msg = $model_validator->getMassage(); + $this->validated_type_msg = $model_validator->hasError() ? 'warning' : 'success'; + + $model_validator->close(); + + return $details_model; + } } \ No newline at end of file diff --git a/common/components/parsers/CustomConverter.php b/common/components/parsers/CustomConverter.php index 853f58a..717a708 100755 --- a/common/components/parsers/CustomConverter.php +++ b/common/components/parsers/CustomConverter.php @@ -23,37 +23,9 @@ class CustomConverter extends Converter // присвоим полный артикул $row['FULL_ARTICLE'] = $row['ARTICLE']; - $details_model = new Details(); - // проверим все ли обязательные колонки были указаны пользователем -// $details_model->load(['Details' => $row]); -// -// if (!$details_model->validate()) { -// $errors = ''; -// foreach ($details_model->errors as $key => $arr_errors) { -// $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors); -// } -// throw new \ErrorException($errors); -// } - - return $row; } - public static function convertToCrosses( array $row ) - { - $details_model = new DetailsCrosses(); - // проверим все ли обязательные колонки были указаны пользователем - $details_model->load(['DetailsCrosses' => $row]); - - if (!$details_model->validate()) { - $errors = ''; - foreach ($details_model->errors as $key => $arr_errors) { - $errors .= "Аттрибут $key - " . implode(' , ', $arr_errors); - } - throw new \ErrorException($errors); - } - return $row; - } public function ConvertToMultiply( array $row ) { diff --git a/common/components/parsers/config.php b/common/components/parsers/config.php index ee95464..ef57b2a 100755 --- a/common/components/parsers/config.php +++ b/common/components/parsers/config.php @@ -45,7 +45,6 @@ 'hasKey' => 1, 'configuration' => [ "brand" => ['BRAND', 'CROSS_BRAND'], - // "crosses" => [], ] ], 'basic_column' => [ -- libgit2 0.21.4