Failed Conditions
Pull Request — master (#1438)
by Tomoyuki
37:16
created

CsvExportService   B

Complexity

Total Complexity 47

Size/Duplication

Total Lines 412
Duplicated Lines 11.65 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 69.86%

Importance

Changes 3
Bugs 0 Features 0
Metric Value
wmc 47
c 3
b 0
f 0
lcom 1
cbo 13
dl 48
loc 412
ccs 51
cts 73
cp 0.6986
rs 8.439

22 Methods

Rating   Name   Duplication   Size   Complexity  
A getCsvs() 0 4 1
A exportHeader() 0 15 4
A initCsvType() 0 17 2
D getData() 0 43 9
A fopen() 0 6 3
A fputcsv() 0 8 2
A fclose() 0 7 2
A exportData() 0 19 4
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 getConvertEncodhingCallback() 0 10 1
A getOrderQueryBuilder() 16 16 2
A getCustomerQueryBuilder() 16 16 2
A getProductQueryBuilder() 16 16 2
A findDeserializeObjects() 0 17 4

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
 * This file is part of EC-CUBE
4
 *
5
 * Copyright(c) 2000-2015 LOCKON CO.,LTD. All Rights Reserved.
6
 *
7
 * http://www.lockon.co.jp/
8
 *
9
 * This program is free software; you can redistribute it and/or
10
 * modify it under the terms of the GNU General Public License
11
 * as published by the Free Software Foundation; either version 2
12
 * of the License, or (at your option) any later version.
13
 *
14
 * This program is distributed in the hope that it will be useful,
15
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17
 * GNU General Public License for more details.
18
 *
19
 * You should have received a copy of the GNU General Public License
20
 * along with this program; if not, write to the Free Software
21
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22
 */
23
24
25
namespace Eccube\Service;
26
27
use Eccube\Common\Constant;
28
use Eccube\Util\EntityUtil;
29
use Symfony\Component\Form\FormFactory;
30
use Symfony\Component\HttpFoundation\Request;
31
use Doctrine\Common\Collections\ArrayCollection;
32
33
class CsvExportService
0 ignored issues
show
introduced by
Missing class doc comment
Loading history...
34
{
35
    /**
36
     * @var
37
     */
38
    protected $fp;
39
40
    /**
41
     * @var
42
     */
43
    protected $closed = false;
44
45
    /**
46
     * @var \Closure
47
     */
48
    protected $convertEncodingCallBack;
49
50
    /**
51
     * @var \Doctrine\ORM\EntityManager
52
     */
53
    protected $em;
54
55
    /**
56
     * @var \Doctrine\ORM\QueryBuilder;
57
     */
58
    protected $qb;
59
60
    /**
61
     * @var array
62
     */
63
    protected $config;
64
65
    /**
66
     * @var \Eccube\Entity\Master\CsvType
67
     */
68
    protected $CsvType;
69
70
    /**
71
     * @var \Eccube\Entity\Csv[]
72
     */
73
    protected $Csvs;
74
75
    /**
76
     * @var \Eccube\Repository\CsvRepository
77
     */
78
    protected $csvRepository;
79
80
    /**
81
     * @var \Eccube\Repository\Master\CsvTypeRepository
82
     */
83
    protected $csvTypeRepository;
84
85
    /**
86
     * @var \Eccube\Repository\OrderRepository
87
     */
88
    protected $orderRepository;
89
90
    /**
91
     * @var \Eccube\Repository\CustomerRepository
92
     */
93
    protected $customerRepository;
94
95
    /**
96
     * @var \Eccube\Repository\ProductRepository
97
     */
98
    protected $productRepository;
99
100
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$config" missing
Loading history...
101
     * @param $config
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
102
     */
103 5
    public function setConfig($config)
104
    {
105 5
        $this->config = $config;
106 5
    }
107
108
    /**
109
     * @param \Eccube\Repository\CsvRepository $csvRepository
110
     */
111 5
    public function setCsvRepository(\Eccube\Repository\CsvRepository $csvRepository)
112
    {
113 5
        $this->csvRepository = $csvRepository;
114
    }
115
116
    /**
117
     * @param \Eccube\Repository\Master\CsvTypeRepository $csvTypeRepository
118
     */
119 5
    public function setCsvTypeRepository(\Eccube\Repository\Master\CsvTypeRepository $csvTypeRepository)
120
    {
121 5
        $this->csvTypeRepository = $csvTypeRepository;
122
    }
123
124
    /**
125
     * @param \Eccube\Repository\OrderRepository $orderRepository
126
     */
127 5
    public function setOrderRepository(\Eccube\Repository\OrderRepository $orderRepository)
128
    {
129 5
        $this->orderRepository = $orderRepository;
130
    }
131
132
    /**
133
     * @param \Eccube\Repository\CustomerRepository $customerRepository
134
     */
135 5
    public function setCustomerRepository(\Eccube\Repository\CustomerRepository $customerRepository)
136
    {
137 5
        $this->customerRepository = $customerRepository;
138
    }
139
140
    /**
141
     * @param \Eccube\Repository\ProductRepository $productRepository
142
     */
143 5
    public function setProductRepository(\Eccube\Repository\ProductRepository $productRepository)
144
    {
145 5
        $this->productRepository = $productRepository;
146
    }
147
148
    /**
149
     * @param \Doctrine\ORM\EntityManager $em
150
     */
151 5
    public function setEntityManager(\Doctrine\ORM\EntityManager $em)
0 ignored issues
show
Bug introduced by
You have injected the EntityManager via parameter $em. This is generally not recommended as it might get closed and become unusable. Instead, it is recommended to inject the ManagerRegistry and retrieve the EntityManager via getManager() each time you need it.

The EntityManager might become unusable for example if a transaction is rolled back and it gets closed. Let’s assume that somewhere in your application, or in a third-party library, there is code such as the following:

function someFunction(ManagerRegistry $registry) {
    $em = $registry->getManager();
    $em->getConnection()->beginTransaction();
    try {
        // Do something.
        $em->getConnection()->commit();
    } catch (\Exception $ex) {
        $em->getConnection()->rollback();
        $em->close();

        throw $ex;
    }
}

If that code throws an exception and the EntityManager is closed. Any other code which depends on the same instance of the EntityManager during this request will fail.

On the other hand, if you instead inject the ManagerRegistry, the getManager() method guarantees that you will always get a usable manager instance.

Loading history...
152
    {
153 5
        $this->em = $em;
154
    }
155
156
    /**
157
     * @return \Doctrine\ORM\EntityManager
158
     */
159 2
    public function getEntityManager()
160
    {
161 2
        return $this->em;
162
    }
163
164
    /**
165
     * @param \Doctrine\ORM\QueryBuilder $qb
166
     */
167 4
    public function setExportQueryBuilder(\Doctrine\ORM\QueryBuilder $qb)
168
    {
169 4
        $this->qb = $qb;
170
    }
171
172
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$CsvType" missing
Loading history...
173
     * Csv種別からServiceの初期化を行う.
174
     *
175
     * @param $CsvType|integer
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
176
     */
177 5
    public function initCsvType($CsvType)
178
    {
179 5
        if ($CsvType instanceof \Eccube\Entity\Master\CsvType) {
0 ignored issues
show
Bug introduced by
The class Eccube\Entity\Master\CsvType does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
180
            $this->CsvType = $CsvType;
181
        } else {
182
            $this->CsvType = $this->csvTypeRepository->find($CsvType);
183
        }
184
185
        $criteria = array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
186
            'CsvType' => $CsvType,
187 5
            'enable_flg' => Constant::ENABLED
188
        );
189
        $orderBy = array(
0 ignored issues
show
introduced by
Add a comma after each item in a multi-line array
Loading history...
190
            'rank' => 'ASC'
191 5
        );
192
        $this->Csvs = $this->csvRepository->findBy($criteria, $orderBy);
193 5
    }
194
195
    /**
196
     * @return \Eccube\Entity\Csv[]
197
     */
198
    public function getCsvs()
199
    {
200
        return $this->Csvs;
201
    }
202
203
    /**
204
     * ヘッダ行を出力する.
205
     * このメソッドを使う場合は, 事前にinitCsvType($CsvType)で初期化しておく必要がある.
206
     */
207 4
    public function exportHeader()
208
    {
209
        if (is_null($this->CsvType) || is_null($this->Csvs)) {
210
            throw new \LogicException('init csv type incomplete.');
211
        }
212
213 4
        $row = array();
214 4
        foreach ($this->Csvs as $Csv) {
215
            $row[] = $Csv->getDispName();
216
        }
217
218
        $this->fopen();
219
        $this->fputcsv($row);
220
        $this->fclose();
221 4
    }
222
223
    /**
224
     * クエリビルダにもとづいてデータ行を出力する.
225
     * このメソッドを使う場合は, 事前にsetExportQueryBuilder($qb)で出力対象のクエリビルダをわたしておく必要がある.
226
     *
227
     * @param \Closure $closure
228
     */
229 4
    public function exportData(\Closure $closure)
230
    {
231
        if (is_null($this->qb) || is_null($this->em)) {
232
            throw new \LogicException('query builder not set.');
233
        }
234
235
        $this->fopen();
236
237
        $query = $this->qb->getQuery();
238 4
        foreach ($query->getResult() as $iteratableResult) {
239
            $closure($iteratableResult, $this);
240
            $this->em->detach($iteratableResult);
241
            $this->em->clear();
242
            $query->free();
243
            flush();
244
        }
245
246
        $this->fclose();
247
    }
248
249
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$entity" missing
Loading history...
250
     * CSV出力項目と比較し, 合致するデータを返す.
251
     *
252
     * @param \Eccube\Entity\Csv $Csv
253
     * @param $entity
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
254
     * @return mixed|null|string|void
255
     */
256
    public function getData(\Eccube\Entity\Csv $Csv, $entity)
257
    {
258
        // エンティティ名が一致するかどうかチェック.
259
        $csvEntityName = str_replace('\\\\', '\\', $Csv->getEntityName());
260
        $entityName = str_replace('\\\\', '\\', get_class($entity));
261
        if ($csvEntityName !== $entityName) {
262
            return null;
263
        }
264
265
        // カラム名がエンティティに存在するかどうかをチェック.
266
        if (!$entity->offsetExists($Csv->getFieldName())) {
267
            return null;
268
        }
269
270
        // データを取得.
271
        $data = $entity->offsetGet($Csv->getFieldName());
272
273
        // one to one の場合は, dtb_csv.referece_field_nameと比較し, 合致する結果を取得する.
274
        if ($data instanceof \Eccube\Entity\AbstractEntity) {
0 ignored issues
show
Bug introduced by
The class Eccube\Entity\AbstractEntity does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
275
            if (EntityUtil::isNotEmpty($data)) {
276
                return $data->offsetGet($Csv->getReferenceFieldName());
277
            }
278
        } elseif ($data instanceof \Doctrine\Common\Collections\Collection) {
279
            // one to manyの場合は, カンマ区切りに変換する.
280
            $array = array();
281
            foreach ($data as $elem) {
282
                if (EntityUtil::isNotEmpty($elem)) {
283
                    $array[] = $elem->offsetGet($Csv->getReferenceFieldName());
284
                }
285
            }
286
            return implode($this->config['csv_export_multidata_separator'], $array);
0 ignored issues
show
introduced by
Missing blank line before return statement
Loading history...
287
288
        } elseif ($data instanceof \DateTime) {
289
            // datetimeの場合は文字列に変換する.
290
            return $data->format($this->config['csv_export_date_format']);
291
292
        } else {
293
            // スカラ値の場合はそのまま.
294
            return $data;
295
        }
296
297
        return null;
298
    }
299
300
    /**
301
     * 文字エンコーディングの変換を行うコールバック関数を返す.
302
     *
303
     * @return \Closure
304
     */
305 5
    public function getConvertEncodhingCallback()
306
    {
307 5
        $config = $this->config;
308
309
        return function ($value) use ($config) {
310
            return mb_convert_encoding(
311
                (string) $value, $config['csv_export_encoding'], 'UTF-8'
312
            );
313 5
        };
314
    }
315
316
    /**
317
     *
318
     */
319 2
    public function fopen()
320
    {
321
        if (is_null($this->fp) || $this->closed) {
322
            $this->fp = fopen('php://output', 'w');
323
        }
324 2
    }
325
326
    /**
0 ignored issues
show
introduced by
Doc comment for parameter "$row" missing
Loading history...
327
     * @param $row
0 ignored issues
show
introduced by
Missing parameter name
Loading history...
328
     * @param null $callback
0 ignored issues
show
Bug introduced by
There is no parameter named $callback. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
introduced by
Superfluous parameter comment
Loading history...
329
     */
330 1
    public function fputcsv($row)
331
    {
332
        if (is_null($this->convertEncodingCallBack)) {
333
            $this->convertEncodingCallBack = $this->getConvertEncodhingCallback();
334
        }
335
336
        fputcsv($this->fp, array_map($this->convertEncodingCallBack, $row), $this->config['csv_export_separator']);
337 1
    }
338
339
    /**
340
     *
341
     */
342 5
    public function fclose()
343
    {
344 2
        if (!$this->closed) {
345
            fclose($this->fp);
346 5
            $this->closed = true;
347
        }
348 2
    }
349
350
    /**
351
     * 受注検索用のクエリビルダを返す.
352
     *
353
     * @param Request $request
354
     * @return \Doctrine\ORM\QueryBuilder
355
     */
356 2 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...
357
    {
358
        $session = $request->getSession();
359
        if ($session->has('eccube.admin.order.search')) {
360
            $searchData = $session->get('eccube.admin.order.search');
361
            $this->findDeserializeObjects($searchData);
362
        } else {
363
            $searchData = array();
364 2
        }
365
366
        // 受注データのクエリビルダを構築.
367 2
        $qb = $this->orderRepository
368
            ->getQueryBuilderBySearchDataForAdmin($searchData);
369
370 2
        return $qb;
371 2
    }
372
373
    /**
374
     * 会員検索用のクエリビルダを返す.
375
     *
376
     * @param Request $request
377
     * @return \Doctrine\ORM\QueryBuilder
378
     */
379 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...
380
    {
381
        $session = $request->getSession();
382
        if ($session->has('eccube.admin.customer.search')) {
383
            $searchData = $session->get('eccube.admin.customer.search');
384
            $this->findDeserializeObjects($searchData);
385
        } else {
386 1
            $searchData = array();
387
        }
388
389
        // 会員データのクエリビルダを構築.
390 1
        $qb = $this->customerRepository
391
            ->getQueryBuilderBySearchData($searchData);
392
393 1
        return $qb;
394 1
    }
395
396
    /**
397
     * 商品検索用のクエリビルダを返す.
398
     *
399
     * @param Request $request
400
     * @return \Doctrine\ORM\QueryBuilder
401
     */
402 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...
403
    {
404
        $session = $request->getSession();
405
        if ($session->has('eccube.admin.product.search')) {
406
            $searchData = $session->get('eccube.admin.product.search');
407
            $this->findDeserializeObjects($searchData);
408
        } else {
409
            $searchData = array();
410
        }
411
412
        // 商品データのクエリビルダを構築.
413
        $qb = $this->productRepository
414
            ->getQueryBuilderBySearchDataForAdmin($searchData);
415
416
        return $qb;
417
    }
418
419
    /**
420
     * セッションでシリアライズされた Doctrine のオブジェクトを取得し直す.
421
     *
422
     * XXX self::setExportQueryBuilder() をコールする前に EntityManager を取得したいので、引数で渡している
423
     *
424
     * @param array $searchData セッションから取得した検索条件の配列
425
     * @param EntityManager $em
0 ignored issues
show
Bug introduced by
There is no parameter named $em. Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
426
     */
427
    protected function findDeserializeObjects(array &$searchData)
428
    {
429
        $em = $this->getEntityManager();
430
        foreach ($searchData as &$Conditions) {
431
            if ($Conditions instanceof ArrayCollection) {
432
                $Conditions = new ArrayCollection(
433
                    array_map(
434
                        function ($Entity) use ($em) {
435
                            return $em->getRepository(get_class($Entity))->find($Entity->getId());
436
                        }, $Conditions->toArray()
437
                    )
438
                );
439
            } elseif ($Conditions instanceof \Eccube\Entity\AbstractEntity) {
0 ignored issues
show
Bug introduced by
The class Eccube\Entity\AbstractEntity does not exist. Did you forget a USE statement, or did you not list all dependencies?

This error could be the result of:

1. Missing dependencies

PHP Analyzer uses your composer.json file (if available) to determine the dependencies of your project and to determine all the available classes and functions. It expects the composer.json to be in the root folder of your repository.

Are you sure this class is defined by one of your dependencies, or did you maybe not list a dependency in either the require or require-dev section?

2. Missing use statement

PHP does not complain about undefined classes in ìnstanceof checks. For example, the following PHP code will work perfectly fine:

if ($x instanceof DoesNotExist) {
    // Do something.
}

If you have not tested against this specific condition, such errors might go unnoticed.

Loading history...
440
                $Conditions = $em->getRepository(get_class($Conditions))->find($Conditions->getId());
441
            }
442
        }
443
    }
444
}
445