Failed Conditions
Pull Request — 4.0 (#4815)
by chihiro
05:48
created

CsvExportService   A

Complexity

Total Complexity 41

Size/Duplication

Total Lines 434
Duplicated Lines 11.06 %

Coupling/Cohesion

Components 2
Dependencies 14

Test Coverage

Coverage 72%

Importance

Changes 0
Metric Value
dl 48
loc 434
ccs 90
cts 125
cp 0.72
rs 9.1199
c 0
b 0
f 0
wmc 41
lcom 2
cbo 14

22 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 23 1
A setConfig() 0 4 1
A setCsvRepository() 0 4 1
A setCsvTypeRepository() 0 4 1
A setOrderRepository() 0 4 1
A setCustomerRepository() 0 4 1
A setProductRepository() 0 4 1
A setEntityManager() 0 4 1
A getEntityManager() 0 4 1
A setExportQueryBuilder() 0 4 1
A initCsvType() 0 17 2
A getCsvs() 0 4 1
A exportHeader() 0 15 4
B exportData() 0 26 6
B getData() 0 36 7
A getConvertEncodingCallback() 0 10 1
A fopen() 0 6 3
A fputcsv() 0 8 2
A fclose() 0 7 2
A getOrderQueryBuilder() 16 16 1
A getCustomerQueryBuilder() 16 16 1
A getProductQueryBuilder() 16 16 1

How to fix   Duplicated Code    Complexity   

Duplicated Code

Duplicate code is one of the most pungent code smells. A rule that is often used is to re-structure code once it is duplicated in three or more places.

Common duplication problems, and corresponding solutions are:

Complex Class

 Tip:   Before tackling complexity, make sure that you eliminate any duplication first. This often can reduce the size of classes significantly.

Complex classes like CsvExportService often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use CsvExportService, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
/*
4
 * This file is part of EC-CUBE
5
 *
6
 * Copyright(c) EC-CUBE CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.ec-cube.co.jp/
9
 *
10
 * For the full copyright and license information, please view the LICENSE
11
 * file that was distributed with this source code.
12
 */
13
14
namespace Eccube\Service;
15
16
use Doctrine\Common\Util\ClassUtils;
17
use Doctrine\ORM\EntityManagerInterface;
18
use Doctrine\ORM\QueryBuilder;
19
use Eccube\Common\EccubeConfig;
20
use Eccube\Entity\Csv;
21
use Eccube\Entity\Master\CsvType;
22
use Eccube\Form\Type\Admin\SearchCustomerType;
23
use Eccube\Form\Type\Admin\SearchOrderType;
24
use Eccube\Form\Type\Admin\SearchProductType;
25
use Eccube\Repository\CsvRepository;
26
use Eccube\Repository\CustomerRepository;
27
use Eccube\Repository\Master\CsvTypeRepository;
28
use Eccube\Repository\OrderRepository;
29
use Eccube\Repository\ProductRepository;
30
use Eccube\Repository\ShippingRepository;
31
use Eccube\Util\FormUtil;
32
use Knp\Component\Pager\PaginatorInterface;
33
use Symfony\Component\Form\FormFactoryInterface;
34
use Symfony\Component\HttpFoundation\Request;
35
36
class CsvExportService
37
{
38
    /**
39
     * @var resource
40
     */
41
    protected $fp;
42
43
    /**
44
     * @var boolean
45
     */
46
    protected $closed = false;
47
48
    /**
49
     * @var \Closure
50
     */
51
    protected $convertEncodingCallBack;
52
53
    /**
54
     * @var EntityManagerInterface
55
     */
56
    protected $entityManager;
57
58
    /**
59
     * @var QueryBuilder;
60
     */
61
    protected $qb;
62
63
    /**
64
     * @var EccubeConfig
65
     */
66
    protected $eccubeConfig;
67
68
    /**
69
     * @var CsvType
70
     */
71
    protected $CsvType;
72
73
    /**
74
     * @var Csv[]
75
     */
76
    protected $Csvs;
77
78
    /**
79
     * @var CsvRepository
80
     */
81
    protected $csvRepository;
82
83
    /**
84
     * @var CsvTypeRepository
85
     */
86
    protected $csvTypeRepository;
87
88
    /**
89
     * @var OrderRepository
90
     */
91
    protected $orderRepository;
92
93
    /**
94
     * @var ShippingRepository
95
     */
96
    protected $shippingRepository;
97
98
    /**
99
     * @var CustomerRepository
100
     */
101
    protected $customerRepository;
102
103
    /**
104
     * @var ProductRepository
105
     */
106
    protected $productRepository;
107
108
    /**
109
     * @var FormFactoryInterface
110
     */
111
    protected $formFactory;
112
113
    /** @var PaginatorInterface */
114
    protected $paginator;
115
116
    /**
117
     * CsvExportService constructor.
118
     *
119
     * @param EntityManagerInterface $entityManager
120
     * @param CsvRepository $csvRepository
121
     * @param CsvTypeRepository $csvTypeRepository
122 76
     * @param OrderRepository $orderRepository
123
     * @param ShippingRepository $shippingRepository
124
     * @param CustomerRepository $customerRepository
125
     * @param ProductRepository $productRepository
126
     * @param EccubeConfig $eccubeConfig
127
     * @param FormFactoryInterface $formFactory
128
     * @param PaginatorInterface $paginator
129
     */
130
    public function __construct(
131
        EntityManagerInterface $entityManager,
132
        CsvRepository $csvRepository,
133 76
        CsvTypeRepository $csvTypeRepository,
134 76
        OrderRepository $orderRepository,
135 76
        ShippingRepository $shippingRepository,
136 76
        CustomerRepository $customerRepository,
137 76
        ProductRepository $productRepository,
138 76
        EccubeConfig $eccubeConfig,
139 76
        FormFactoryInterface $formFactory,
140 76
        PaginatorInterface $paginator
141 76
    ) {
142
        $this->entityManager = $entityManager;
143
        $this->csvRepository = $csvRepository;
144
        $this->csvTypeRepository = $csvTypeRepository;
145
        $this->orderRepository = $orderRepository;
146
        $this->shippingRepository = $shippingRepository;
147
        $this->customerRepository = $customerRepository;
148
        $this->eccubeConfig = $eccubeConfig;
149
        $this->productRepository = $productRepository;
150
        $this->formFactory = $formFactory;
151
        $this->paginator = $paginator;
152
    }
153
154
    /**
155
     * @param $config
156
     */
157
    public function setConfig($config)
158
    {
159
        $this->eccubeConfig = $config;
160
    }
161
162
    /**
163
     * @param CsvRepository $csvRepository
164
     */
165
    public function setCsvRepository(CsvRepository $csvRepository)
166
    {
167
        $this->csvRepository = $csvRepository;
168
    }
169
170
    /**
171
     * @param CsvTypeRepository $csvTypeRepository
172
     */
173
    public function setCsvTypeRepository(CsvTypeRepository $csvTypeRepository)
174
    {
175
        $this->csvTypeRepository = $csvTypeRepository;
176
    }
177
178
    /**
179
     * @param OrderRepository $orderRepository
180
     */
181
    public function setOrderRepository(OrderRepository $orderRepository)
182
    {
183
        $this->orderRepository = $orderRepository;
184
    }
185
186
    /**
187
     * @param CustomerRepository $customerRepository
188
     */
189
    public function setCustomerRepository(CustomerRepository $customerRepository)
190
    {
191
        $this->customerRepository = $customerRepository;
192
    }
193
194
    /**
195
     * @param ProductRepository $productRepository
196
     */
197
    public function setProductRepository(ProductRepository $productRepository)
198
    {
199
        $this->productRepository = $productRepository;
200
    }
201
202
    /**
203
     * @param EntityManagerInterface $entityManager
204
     */
205
    public function setEntityManager(EntityManagerInterface $entityManager)
206
    {
207
        $this->entityManager = $entityManager;
208
    }
209
210
    /**
211 4
     * @return EntityManagerInterface
212
     */
213 4
    public function getEntityManager()
214
    {
215
        return $this->entityManager;
216
    }
217
218
    /**
219
     * @param QueryBuilder $qb
220
     */
221 5
    public function setExportQueryBuilder(QueryBuilder $qb)
222
    {
223 5
        $this->qb = $qb;
224
    }
225
226 5
    /**
227
     * Csv種別からServiceの初期化を行う.
228
     *
229
     * @param $CsvType|integer
230 5
     */
231
    public function initCsvType($CsvType)
232
    {
233
        if ($CsvType instanceof CsvType) {
234 5
            $this->CsvType = $CsvType;
235
        } else {
236 5
            $this->CsvType = $this->csvTypeRepository->find($CsvType);
237
        }
238
239
        $criteria = [
240
            'CsvType' => $CsvType,
241
            'enabled' => true,
242 4
        ];
243
        $orderBy = [
244 4
            'sort_no' => 'ASC',
245
        ];
246
        $this->Csvs = $this->csvRepository->findBy($criteria, $orderBy);
247
    }
248
249
    /**
250
     * @return Csv[]
251 4
     */
252
    public function getCsvs()
253 4
    {
254
        return $this->Csvs;
255
    }
256
257 4
    /**
258 4
     * ヘッダ行を出力する.
259 4
     * このメソッドを使う場合は, 事前にinitCsvType($CsvType)で初期化しておく必要がある.
260
     */
261
    public function exportHeader()
262 4
    {
263 4
        if (is_null($this->CsvType) || is_null($this->Csvs)) {
264 4
            throw new \LogicException('init csv type incomplete.');
265
        }
266
267
        $row = [];
268
        foreach ($this->Csvs as $Csv) {
269
            $row[] = $Csv->getDispName();
270
        }
271
272
        $this->fopen();
273 4
        $this->fputcsv($row);
274
        $this->fclose();
275 4
    }
276
277
    /**
278
     * クエリビルダにもとづいてデータ行を出力する.
279 4
     * このメソッドを使う場合は, 事前にsetExportQueryBuilder($qb)で出力対象のクエリビルダをわたしておく必要がある.
280
     *
281 4
     * @param \Closure $closure
282 4
     */
283 4
    public function exportData(\Closure $closure)
284 4
    {
285 4
        if (is_null($this->qb) || is_null($this->entityManager)) {
286 4
            throw new \LogicException('query builder not set.');
287
        }
288
289 4
        $this->fopen();
290
291
        $page = 1;
292
        $limit = 100;
293
        while ($results = $this->paginator->paginate($this->qb, $page, $limit)) {
294
            if (!$results->valid()) {
295
                break;
296
            }
297
298
            foreach ($results as $result) {
0 ignored issues
show
Bug introduced by
The expression $results of type object<Knp\Component\Pag...on\PaginationInterface> is not traversable.
Loading history...
299
                $closure($result, $this);
300 4
                flush();
301
            }
302
303 4
            $this->entityManager->clear();
304 4
            $page++;
305 4
        }
306 2
307
        $this->fclose();
308
    }
309
310 4
    /**
311 2
     * CSV出力項目と比較し, 合致するデータを返す.
312
     *
313
     * @param \Eccube\Entity\Csv $Csv
314
     * @param $entity
315 4
     *
316
     * @return string|null
317
     */
318 4
    public function getData(Csv $Csv, $entity)
319 4
    {
320 4
        // エンティティ名が一致するかどうかチェック.
321
        $csvEntityName = str_replace('\\\\', '\\', $Csv->getEntityName());
322 4
        $entityName = ClassUtils::getClass($entity);
323
        if ($csvEntityName !== $entityName) {
324
            return null;
325
        }
326
327
        // カラム名がエンティティに存在するかどうかをチェック.
328
        if (!$entity->offsetExists($Csv->getFieldName())) {
329
            return null;
330
        }
331
332 4
        // データを取得.
333
        $data = $entity->offsetGet($Csv->getFieldName());
334 3
335
        // one to one の場合は, dtb_csv.reference_field_name, 合致する結果を取得する.
336
        if ($data instanceof \Eccube\Entity\AbstractEntity) {
337 4
            return $data->offsetGet($Csv->getReferenceFieldName());
338
        } elseif ($data instanceof \Doctrine\Common\Collections\Collection) {
339
            // one to manyの場合は, カンマ区切りに変換する.
340
            $array = [];
341
            foreach ($data as $elem) {
342
                $array[] = $elem->offsetGet($Csv->getReferenceFieldName());
343
            }
344
345
            return implode($this->eccubeConfig['eccube_csv_export_multidata_separator'], $array);
346
        } elseif ($data instanceof \DateTime) {
347
            // datetimeの場合は文字列に変換する.
348 5
            return $data->format($this->eccubeConfig['eccube_csv_export_date_format']);
349
        } else {
350 5
            // スカラ値の場合はそのまま.
351
            return $data;
352 5
        }
353 5
    }
354 5
355
    /**
356 5
     * 文字エンコーディングの変換を行うコールバック関数を返す.
357
     *
358
     * @return \Closure
359 5
     */
360
    public function getConvertEncodingCallback()
361 5
    {
362 3
        $config = $this->eccubeConfig;
363
364
        return function ($value) use ($config) {
365
            return mb_convert_encoding(
366
                (string) $value, $config['eccube_csv_export_encoding'], 'UTF-8'
367
            );
368
        };
369 5
    }
370
371 5
    public function fopen()
372 5
    {
373
        if (is_null($this->fp) || $this->closed) {
374
            $this->fp = fopen('php://output', 'w');
375 5
        }
376
    }
377
378 5
    /**
379
     * @param $row
380 5
     */
381 5
    public function fputcsv($row)
382 5
    {
383
        if (is_null($this->convertEncodingCallBack)) {
384
            $this->convertEncodingCallBack = $this->getConvertEncodingCallback();
385
        }
386
387
        fputcsv($this->fp, array_map($this->convertEncodingCallBack, $row), $this->eccubeConfig['eccube_csv_export_separator']);
388
    }
389
390
    public function fclose()
391
    {
392
        if (!$this->closed) {
393 1
            fclose($this->fp);
394
            $this->closed = true;
395 1
        }
396 1
    }
397 1
398 1
    /**
399
     * 受注検索用のクエリビルダを返す.
400 1
     *
401 1
     * @param Request $request
402
     *
403
     * @return \Doctrine\ORM\QueryBuilder
404 1
     */
405 1 View Code Duplication
    public function getOrderQueryBuilder(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
406
    {
407 1
        $session = $request->getSession();
408
        $builder = $this->formFactory
409
            ->createBuilder(SearchOrderType::class);
410
        $searchForm = $builder->getForm();
411
412
        $viewData = $session->get('eccube.admin.order.search', []);
413
        $searchData = FormUtil::submitAndGetData($searchForm, $viewData);
414
415
        // 受注データのクエリビルダを構築.
416
        $qb = $this->orderRepository
417 1
            ->getQueryBuilderBySearchDataForAdmin($searchData);
418
419 1
        return $qb;
420 1
    }
421 1
422 1
    /**
423
     * 会員検索用のクエリビルダを返す.
424 1
     *
425 1
     * @param Request $request
426
     *
427
     * @return \Doctrine\ORM\QueryBuilder
428 1
     */
429 1 View Code Duplication
    public function getCustomerQueryBuilder(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
430
    {
431 1
        $session = $request->getSession();
432
        $builder = $this->formFactory
433
            ->createBuilder(SearchCustomerType::class);
434
        $searchForm = $builder->getForm();
435
436
        $viewData = $session->get('eccube.admin.customer.search', []);
437
        $searchData = FormUtil::submitAndGetData($searchForm, $viewData);
438
439
        // 会員データのクエリビルダを構築.
440
        $qb = $this->customerRepository
441
            ->getQueryBuilderBySearchData($searchData);
442
443
        return $qb;
444
    }
445
446
    /**
447
     * 商品検索用のクエリビルダを返す.
448
     *
449
     * @param Request $request
450
     *
451
     * @return \Doctrine\ORM\QueryBuilder
452
     */
453 View Code Duplication
    public function getProductQueryBuilder(Request $request)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
454
    {
455
        $session = $request->getSession();
456
        $builder = $this->formFactory
457
            ->createBuilder(SearchProductType::class);
458
        $searchForm = $builder->getForm();
459
460
        $viewData = $session->get('eccube.admin.product.search', []);
461
        $searchData = FormUtil::submitAndGetData($searchForm, $viewData);
462
463
        // 商品データのクエリビルダを構築.
464
        $qb = $this->productRepository
465
            ->getQueryBuilderBySearchDataForAdmin($searchData);
466
467
        return $qb;
468
    }
469
}
470