Commit 8cb5acbf30799eac04cf8dff13eb5e92e30ac181
1 parent
8663be10
add exaples template for csv files
Showing
5 changed files
with
389 additions
and
0 deletions
Show diff stats
1 | +<?php | |
2 | +namespace backend\controllers; | |
3 | + | |
4 | +use backend\models\UploadFileParsingForm; | |
5 | +use Yii; | |
6 | +use yii\base\ErrorException; | |
7 | +use yii\data\ArrayDataProvider; | |
8 | +use yii\helpers\VarDumper; | |
9 | +use yii\multiparser\DynamicFormHelper; | |
10 | +use yii\web\Controller; | |
11 | +use yii\web\UploadedFile; | |
12 | + | |
13 | + | |
14 | +/** | |
15 | + * Site controller | |
16 | + */ | |
17 | +class ParserController extends Controller | |
18 | +{ | |
19 | + /** | |
20 | + * @var - string | |
21 | + * file parsing extension | |
22 | + */ | |
23 | + protected $file_extension; | |
24 | + | |
25 | + public function actionIndex($mode = 0) | |
26 | + { | |
27 | + $model = new UploadFileParsingForm(); | |
28 | + return $this->render('index', ['model' => $model]); | |
29 | + } | |
30 | + | |
31 | + public function actionRead() | |
32 | + { | |
33 | + $model = new UploadFileParsingForm(); | |
34 | + $data = []; | |
35 | + $mode = ''; | |
36 | + if ($model->load(\Yii::$app->request->post())) { | |
37 | + if (!$model->file_type) { | |
38 | + $model->file = UploadedFile::getInstance($model, 'file'); | |
39 | + } | |
40 | + if ($model->validate()) { | |
41 | + // get the extension of user chosen file | |
42 | + $this->file_extension = $this->getFileExtensionFromModel($model); | |
43 | + | |
44 | + if ($model->file_type) { | |
45 | + $model->file_path = dirname(dirname(__DIR__)) . '/tests/_data/template.' . $this->file_extension; | |
46 | + $mode = 'template'; | |
47 | + } else { | |
48 | + $mode = 'custom'; | |
49 | + $model->file_path = dirname(dirname(__DIR__)) . '/tests/_data/custom_template.' . $this->file_extension; | |
50 | + $model->file->saveAs($model->file_path); | |
51 | + } | |
52 | + | |
53 | + // run parsing | |
54 | + $data = $model->readFile(['mode' => $mode]); | |
55 | + | |
56 | + if ($mode == 'custom' && file_exists($model->file_path)) { | |
57 | + unlink($model->file_path); | |
58 | + } | |
59 | + // safe parse data to cache | |
60 | + Yii::$app->getCache()->set('parser_data', json_encode($data), 300); | |
61 | + | |
62 | + } else { | |
63 | + // handle with error validation form | |
64 | + $errors_str = 'Error upload form'; | |
65 | + foreach ($model->getErrors() as $error) { | |
66 | + $errors_str .= ' ' . implode(array_values($error)); | |
67 | + } | |
68 | + | |
69 | + throw new ErrorException($errors_str); | |
70 | + } | |
71 | + | |
72 | + } elseif (Yii::$app->getCache()->get('parser_data')) { | |
73 | + // it's a get request, so retrive data from cache | |
74 | + $data = json_decode(Yii::$app->getCache()->get('parser_data'), true); | |
75 | + } | |
76 | + | |
77 | + return $this->renderResultView($data, $mode); | |
78 | + } | |
79 | + | |
80 | + public function getFileExtensionFromModel($model) | |
81 | + { | |
82 | + switch ($model->file_type) { | |
83 | + case 0: | |
84 | + return $model->file->extension; | |
85 | + case 1: | |
86 | + return 'csv'; | |
87 | + case 2: | |
88 | + return 'xml'; | |
89 | + case 3: | |
90 | + return 'xlsx'; | |
91 | + default: | |
92 | + return 'csv'; | |
93 | + } | |
94 | + | |
95 | + } | |
96 | + | |
97 | + public function renderResultView($data ) | |
98 | + { | |
99 | + $provider = new ArrayDataProvider([ | |
100 | + 'allModels' => $data, | |
101 | + 'pagination' => [ | |
102 | + 'pageSize' => 10, | |
103 | + ], | |
104 | + ]); | |
105 | + // если отпарсенные данные - ассоциативный массив, то пользователю нечего выбирать | |
106 | + $assoc_data_arr = $this->is_assoc($data[0]); | |
107 | + | |
108 | + if ( $assoc_data_arr ) { | |
109 | + | |
110 | + // $mode == 'template' or xml file | |
111 | + // парсинг с файла по шаблону | |
112 | + // согласно конфигурационного файла у нас колонкам назначены ключи | |
113 | + // то есть результат - ассоциативный массив, у пользователя нечего спрашивать | |
114 | + // данные отконвертированы согласно настройкам и готовы к записи в БД (или к дальнейшей обработке) | |
115 | + | |
116 | + return $this->render('results', | |
117 | + ['model' => $data, | |
118 | + // список колонок для выбора | |
119 | + 'dataProvider' => $provider]); | |
120 | + | |
121 | + } else { | |
122 | + // $mode == 'custom' and not xml | |
123 | + // для произвольного файла создадим страницу предпросмотра | |
124 | + // с возможностью выбора соответсвий колонок с отпарсенными данными | |
125 | + //колонки для выбора возьмем из конфигурационного файла - опция - 'basic_column' | |
126 | + | |
127 | + // создадим динамическую модель на столько реквизитов сколько колонок в отпарсенном файле | |
128 | + // в ней пользователь произведет свой выбор | |
129 | + $last_index = end(array_flip($data[0])); | |
130 | + $header_counts = $last_index + 1; // - количество колонок выбора формы предпросмотра | |
131 | + $header_model = DynamicFormHelper::CreateDynamicModel($header_counts); | |
132 | + | |
133 | + // колонки для выбора возьмем из конфигурационного файла | |
134 | + $basicColumns = Yii::$app->multiparser->getConfiguration($this->file_extension, 'basic_column');; | |
135 | + | |
136 | + return $this->render('results', | |
137 | + ['model' => $data, | |
138 | + 'header_model' => $header_model, | |
139 | + // список колонок для выбора | |
140 | + 'basic_column' => $basicColumns, | |
141 | + 'dataProvider' => $provider]); | |
142 | + } | |
143 | + | |
144 | + } | |
145 | + | |
146 | + private function is_assoc(array $array) | |
147 | + { | |
148 | + // Keys of the array | |
149 | + $keys = array_keys($array); | |
150 | + | |
151 | + // If the array keys of the keys match the keys, then the array must | |
152 | + // not be associative (e.g. the keys array looked like {0:0, 1:1...}). | |
153 | + return array_keys($keys) !== $keys; | |
154 | + } | |
155 | + | |
156 | +} | ... | ... |
1 | +<?php | |
2 | +namespace backend\models; | |
3 | + | |
4 | +use yii\base\ErrorException; | |
5 | +use yii\base\Model; | |
6 | +use yii\web\UploadedFile; | |
7 | +use Yii; | |
8 | +use common\components\CustomVarDamp; | |
9 | + | |
10 | +/** | |
11 | + * UploadForm is the model behind the upload form. | |
12 | + */ | |
13 | +class UploadFileParsingForm extends Model | |
14 | +{ | |
15 | + /** | |
16 | + * @var UploadedFile file attribute | |
17 | + */ | |
18 | + // chosen file | |
19 | + public $file; | |
20 | + // file path after save | |
21 | + public $file_path; | |
22 | + // 0 - custom file (user should choose in $file field) | |
23 | + // 1 - csv template file from data/template.csv | |
24 | + // 2 - xml template file from data/template.xml | |
25 | + // 3 - xlsx template file from data/template.xlsx | |
26 | + public $file_type = 0; | |
27 | + | |
28 | + | |
29 | + public function rules() | |
30 | + { | |
31 | + $client_func = <<< JS | |
32 | + function(attribute, value) { | |
33 | + return $('input[name=UploadFileParsingForm[file_type]]').val() == '0'; | |
34 | + } | |
35 | +JS; | |
36 | + return [ | |
37 | + ['file_type', 'in', 'range' => range( 0, 3 ) ], | |
38 | + ['file', 'required', 'when' => function(){ | |
39 | + return !$this->file_type; | |
40 | + } , 'whenClient' => $client_func], | |
41 | + [['file'], 'file', 'extensions' => ['csv', 'xlsx', 'xml'], 'checkExtensionByMimeType' => false ], | |
42 | + ['file_path', 'safe'], | |
43 | + | |
44 | + ]; | |
45 | + } | |
46 | + | |
47 | + public function attributeLabels() | |
48 | + { | |
49 | + return [ | |
50 | + 'file' => Yii::t('app', 'Custom file'), | |
51 | + ]; | |
52 | + } | |
53 | + | |
54 | + public function readFile( $options = [] ){ | |
55 | + | |
56 | + $data = Yii::$app->multiparser->parse( $this->file_path, $options ); | |
57 | + | |
58 | + if( !is_array( $data ) || count($data) == 0 ){ | |
59 | + throw new ErrorException("Reading error from file - {$this->file_path}"); | |
60 | + } | |
61 | + | |
62 | + if( !$this->file_path && file_exists( $this->file_path ) ) | |
63 | + unlink( $this->file_path ); | |
64 | + | |
65 | + return $data; | |
66 | + } | |
67 | + | |
68 | + | |
69 | + | |
70 | + | |
71 | +} | |
0 | 72 | \ No newline at end of file | ... | ... |
1 | +<?php | |
2 | +return [ | |
3 | + 'csv' => | |
4 | + ['custom' => | |
5 | + ['class' => 'yii\multiparser\CsvParser', | |
6 | + 'converter_conf' => [ | |
7 | + 'class' => 'yii\multiparser\Converter', | |
8 | + 'configuration' => ["encode" => []], | |
9 | + ] | |
10 | + ], | |
11 | + 'template' => | |
12 | + ['class' => 'yii\multiparser\CsvParser', | |
13 | + 'keys' => [ | |
14 | + 0 => 'Description', | |
15 | + 1 => 'Article', | |
16 | + 2 => 'Price', | |
17 | + 3 => 'Brand', | |
18 | + 4 => 'Count', | |
19 | + ], | |
20 | + 'converter_conf' => [ | |
21 | + 'class' => 'yii\multiparser\Converter', | |
22 | + 'configuration' => ["encode" => 'Description', | |
23 | + "string" => ['Description', 'Brand'], | |
24 | + "float" => 'Price', | |
25 | + "integer" => 'Count' | |
26 | + ] | |
27 | + ],], | |
28 | + | |
29 | + 'basic_column' => [ | |
30 | + Null => 'null', | |
31 | + "Description" => 'Название', | |
32 | + "Article" => 'Артикул', | |
33 | + "Price" => 'Цена', | |
34 | + "Brand" => 'Производитель', | |
35 | + "Count" => 'Количество', | |
36 | + ], | |
37 | + ], | |
38 | + 'xml' => | |
39 | + ['custom' => | |
40 | + ['class' => 'yii\multiparser\XmlParser', | |
41 | + 'converter_conf' => [ | |
42 | + 'class' => 'yii\multiparser\Converter', | |
43 | + 'configuration' => ["encode" => []], | |
44 | + ] | |
45 | + ], | |
46 | + 'template' => | |
47 | + ['class' => 'yii\multiparser\XmlParser', | |
48 | + 'node' => 'Товар', | |
49 | + 'has_header_row' => false, | |
50 | + 'keys' => [ | |
51 | + "BRAND" => 'Производитель', | |
52 | + "ARTICLE" => 'Код', | |
53 | + "PRICE" => 'Розница', | |
54 | + "DESCR" => 'Наименование', | |
55 | + "BOX" => 'Колво', | |
56 | + "ADD_BOX" => 'Ожидаемое', | |
57 | + "GROUP" => 'Группа' | |
58 | + ], | |
59 | + 'converter_conf' => [ | |
60 | + 'class' => 'yii\multiparser\Converter', | |
61 | + 'configuration' => [ | |
62 | + 'converter_conf' => [ | |
63 | + 'class' => 'yii\multiparser\Converter', | |
64 | + 'configuration' => ["encode" => 'DESCR', | |
65 | + "string" => ['DESCR', 'BRAND'], | |
66 | + "float" => 'PRICE', | |
67 | + "integer" => ['BOX', 'ADD_BOX'], | |
68 | + ], | |
69 | + ], | |
70 | + ], | |
71 | + ], | |
72 | + ], | |
73 | + 'basic_column' => [ | |
74 | + Null => 'null', | |
75 | + "BRAND" => 'Производитель', | |
76 | + "ARTICLE" => 'Код', | |
77 | + "PRICE" => 'Розница', | |
78 | + "DESCR" => 'Наименование', | |
79 | + "BOX" => 'Колво', | |
80 | + "ADD_BOX" => 'Ожидаемое', | |
81 | + "GROUP" => 'Группа' | |
82 | + ], | |
83 | + ], | |
84 | + 'xlsx' => | |
85 | + ['web' => | |
86 | + ['class' => 'common\components\parsers\XlsxParser', | |
87 | + // 'path_for_extract_files' => \Yii::getAlias('@temp_upload') . '/xlsx/', | |
88 | + //'auto_detect_first_line' => true, | |
89 | + //'has_header_row' => true, | |
90 | + 'active_sheet' => 1, | |
91 | + 'converter_conf' => [ | |
92 | + 'class' => 'common\components\parsers\CustomConverter', | |
93 | + 'configuration' => ["string" => []], | |
94 | + ] | |
95 | + ], | |
96 | + ] | |
97 | +]; | |
98 | + | ... | ... |
1 | +<?php | |
2 | +use yii\widgets\ActiveForm; | |
3 | +use yii\helpers\Html; | |
4 | +use yii\helpers\ArrayHelper; | |
5 | + | |
6 | +?> | |
7 | +<div class="row"> | |
8 | + <div class="col-lg-5"> | |
9 | + <?php $form = ActiveForm::begin(['options' => ['enctype' => 'multipart/form-data',],'action'=>['parser/read']]); | |
10 | + ?> | |
11 | + <h3>Choose file to parse</h3> | |
12 | + | |
13 | + <?= $form->field($model, 'file')->fileInput() ?> | |
14 | + | |
15 | + <?= $form->field($model, 'file_type')->radioList([0 => 'Custom file', 1 => 'csv template', 2 => 'xml template', 3 => 'xlsx template'])->label(false); | |
16 | + ?> | |
17 | + | |
18 | + <div class="form-group"> | |
19 | + <?= Html::submitButton('Read', ['class' => 'btn btn-primary']) ?> | |
20 | + </div> | |
21 | + | |
22 | + <?php ActiveForm::end(); | |
23 | + ?> | |
24 | + </div> | |
25 | +</div> | |
26 | + | ... | ... |
1 | +<?php | |
2 | + | |
3 | +use yii\helpers\Html; | |
4 | +use yii\multiparser\DynamicFormHelper; | |
5 | +use yii\widgets\ActiveForm; | |
6 | + | |
7 | + | |
8 | +/* @var $this yii\web\View */ | |
9 | +/* @var $searchModel backend\models\CatalogSearch */ | |
10 | +/* @var $dataProvider yii\data\ActiveDataProvider */ | |
11 | + | |
12 | +$this->title = 'Results'; | |
13 | +$this->params['breadcrumbs'][] = $this->title; | |
14 | +?> | |
15 | +<div class="catalog-index"> | |
16 | + | |
17 | + <h1><?= Html::encode($this->title) ?></h1> | |
18 | + <?php | |
19 | + | |
20 | + $form = ActiveForm::begin(['action' => 'write']); | |
21 | + | |
22 | + if (empty( $header_model )) { | |
23 | + // выведем просто массив без колонок выбора | |
24 | + echo \yii\grid\GridView::widget([ | |
25 | + 'dataProvider' => $dataProvider, | |
26 | + // 'layout'=>"{pager}\n{items}", | |
27 | + | |
28 | + ]); | |
29 | + | |
30 | + } else { | |
31 | + echo DynamicFormHelper::CreateGridWithDropDownListHeader($dataProvider, $form, $header_model, $basic_column); | |
32 | + } | |
33 | + ?> | |
34 | + | |
35 | + <?php ActiveForm::end() ?> | |
36 | + <?= Html::a('Return', ['parser/index'], ['class' => 'btn btn-primary', 'name' => 'Return',]) ?> | |
37 | + | |
38 | +</div> | |
0 | 39 | \ No newline at end of file | ... | ... |