Failed Conditions
Pull Request — experimental/sf (#29)
by Kentaro
50:12 queued 39:05
created

CustomerRepository   C

Complexity

Total Complexity 57

Size/Duplication

Total Lines 378
Duplicated Lines 18.25 %

Coupling/Cohesion

Components 3
Dependencies 9

Test Coverage

Coverage 94.67%

Importance

Changes 0
Metric Value
dl 69
loc 378
ccs 142
cts 150
cp 0.9467
rs 5.04
c 0
b 0
f 0
wmc 57
lcom 3
cbo 9

11 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 16 1
A newCustomer() 0 11 1
F getQueryBuilderBySearchData() 51 151 41
A getUniqueSecretKey() 9 9 2
A getUniqueResetKey() 9 9 2
A getProvisionalCustomerBySecretKey() 0 7 1
A getRegularCustomerByEmail() 0 7 1
A getRegularCustomerByResetKey() 0 11 1
A getResetPassword() 0 4 1
A getNonWithdrawingCustomers() 0 9 1
B updateBuyData() 0 46 5

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 CustomerRepository 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 CustomerRepository, 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) LOCKON CO.,LTD. All Rights Reserved.
7
 *
8
 * http://www.lockon.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\Repository;
15
16
use Doctrine\ORM\EntityManagerInterface;
17
use Eccube\Common\EccubeConfig;
18
use Eccube\Doctrine\Query\Queries;
19
use Eccube\Entity\Customer;
20
use Eccube\Entity\Master\CustomerStatus;
21
use Eccube\Entity\Master\OrderStatus;
22
use Eccube\Util\StringUtil;
23
use Symfony\Bridge\Doctrine\RegistryInterface;
24
use Symfony\Component\Security\Core\Encoder\EncoderFactoryInterface;
25
26
/**
27
 * CustomerRepository
28
 *
29
 * This class was generated by the Doctrine ORM. Add your own custom
30
 * repository methods below.
31
 */
32
class CustomerRepository extends AbstractRepository
33
{
34
    /**
35
     * @var Queries
36
     */
37
    protected $queries;
38
39
    /**
40
     * @var EntityManagerInterface
41
     */
42
    protected $entityManager;
43
44
    /**
45
     * @var OrderRepository
46
     */
47
    protected $orderRepository;
48
49
    /**
50
     * @var EccubeConfig
51
     */
52
    protected $eccubeConfig;
53
54
    /**
55
     * @var EncoderFactoryInterface
56
     */
57
    protected $encoderFactory;
58
59
    /**
60
     * CustomerRepository constructor.
61
     *
62
     * @param RegistryInterface $registry
63
     * @param Queries $queries
64
     * @param EntityManagerInterface $entityManager
65
     * @param OrderRepository $orderRepository
66
     * @param EncoderFactoryInterface $encoderFactory
67
     * @param EccubeConfig $eccubeConfig
68
     */
69 756
    public function __construct(
70
        RegistryInterface $registry,
71
        Queries $queries,
72
        EntityManagerInterface $entityManager,
73
        OrderRepository $orderRepository,
74
        EncoderFactoryInterface $encoderFactory,
75
        EccubeConfig $eccubeConfig
76
    ) {
77 756
        parent::__construct($registry, Customer::class);
78
79 756
        $this->queries = $queries;
80 756
        $this->entityManager = $entityManager;
81 756
        $this->orderRepository = $orderRepository;
82 756
        $this->encoderFactory = $encoderFactory;
83 756
        $this->eccubeConfig = $eccubeConfig;
84
    }
85
86 8
    public function newCustomer()
87
    {
88 8
        $CustomerStatus = $this->getEntityManager()
89 8
            ->find(CustomerStatus::class, CustomerStatus::PROVISIONAL);
90
91 8
        $Customer = new \Eccube\Entity\Customer();
92
        $Customer
93 8
            ->setStatus($CustomerStatus);
0 ignored issues
show
Bug introduced by
It seems like $CustomerStatus defined by $this->getEntityManager(...merStatus::PROVISIONAL) on line 88 can also be of type object; however, Eccube\Entity\Customer::setStatus() does only seem to accept null|object<Eccube\Entity\Master\CustomerStatus>, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
94
95 8
        return $Customer;
96
    }
97
98 40
    public function getQueryBuilderBySearchData($searchData)
99
    {
100 40
        $qb = $this->createQueryBuilder('c')
101 40
            ->select('c');
102
103 40
        if (isset($searchData['multi']) && StringUtil::isNotBlank($searchData['multi'])) {
104
            //スペース除去
105 13
            $clean_key_multi = preg_replace('/\s+|[ ]+/u', '', $searchData['multi']);
106 13
            $id = preg_match('/^\d{0,10}$/', $clean_key_multi) ? $clean_key_multi : null;
107
            $qb
108 13
                ->andWhere('c.id = :customer_id OR CONCAT(c.name01, c.name02) LIKE :name OR CONCAT(c.kana01, c.kana02) LIKE :kana OR c.email LIKE :email')
109 13
                ->setParameter('customer_id', $id)
110 13
                ->setParameter('name', '%'.$clean_key_multi.'%')
111 13
                ->setParameter('kana', '%'.$clean_key_multi.'%')
112 13
                ->setParameter('email', '%'.$clean_key_multi.'%');
113
        }
114
115
        // Pref
116 40
        if (!empty($searchData['pref']) && $searchData['pref']) {
117
            $qb
118 1
                ->andWhere('c.Pref = :pref')
119 1
                ->setParameter('pref', $searchData['pref']->getId());
120
        }
121
122
        // sex
123 40
        if (!empty($searchData['sex']) && count($searchData['sex']) > 0) {
124 2
            $sexs = [];
125 2
            foreach ($searchData['sex'] as $sex) {
126 2
                $sexs[] = $sex->getId();
127
            }
128
129
            $qb
130 2
                ->andWhere($qb->expr()->in('c.Sex', ':sexs'))
131 2
                ->setParameter('sexs', $sexs);
132
        }
133
134 40
        if (!empty($searchData['birth_month']) && $searchData['birth_month']) {
135
            $qb
136 1
                ->andWhere('EXTRACT(MONTH FROM c.birth) = :birth_month')
137 1
                ->setParameter('birth_month', $searchData['birth_month']);
138
        }
139
140
        // birth
141 40
        if (!empty($searchData['birth_start']) && $searchData['birth_start']) {
142
            $qb
143 2
                ->andWhere('c.birth >= :birth_start')
144 2
                ->setParameter('birth_start', $searchData['birth_start']);
145
        }
146 40 View Code Duplication
        if (!empty($searchData['birth_end']) && $searchData['birth_end']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
147 2
            $date = clone $searchData['birth_end'];
148 2
            $date->modify('+1 days');
149
            $qb
150 2
                ->andWhere('c.birth < :birth_end')
151 2
                ->setParameter('birth_end', $date);
152
        }
153
154
        // tel
155 40 View Code Duplication
        if (isset($searchData['phone_number']) && StringUtil::isNotBlank($searchData['phone_number'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
156 1
            $tel = preg_replace('/[^0-9]/ ', '', $searchData['phone_number']);
157
            $qb
158 1
                ->andWhere('c.phone_number LIKE :phone_number')
159 1
                ->setParameter('phone_number', '%'.$tel.'%');
160
        }
161
162
        // buy_total
163 40 View Code Duplication
        if (isset($searchData['buy_total_start']) && StringUtil::isNotBlank($searchData['buy_total_start'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
164
            $qb
165 1
                ->andWhere('c.buy_total >= :buy_total_start')
166 1
                ->setParameter('buy_total_start', $searchData['buy_total_start']);
167
        }
168 40 View Code Duplication
        if (isset($searchData['buy_total_end']) && StringUtil::isNotBlank($searchData['buy_total_end'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
169
            $qb
170 1
                ->andWhere('c.buy_total <= :buy_total_end')
171 1
                ->setParameter('buy_total_end', $searchData['buy_total_end']);
172
        }
173
174
        // buy_times
175 40
        if (!empty($searchData['buy_times_start']) && $searchData['buy_times_start']) {
176
            $qb
177 1
                ->andWhere('c.buy_times >= :buy_times_start')
178 1
                ->setParameter('buy_times_start', $searchData['buy_times_start']);
179
        }
180 40
        if (!empty($searchData['buy_times_end']) && $searchData['buy_times_end']) {
181
            $qb
182 1
                ->andWhere('c.buy_times <= :buy_times_end')
183 1
                ->setParameter('buy_times_end', $searchData['buy_times_end']);
184
        }
185
186
        // create_date
187 40
        if (!empty($searchData['create_date_start']) && $searchData['create_date_start']) {
188
            $qb
189 1
                ->andWhere('c.create_date >= :create_date_start')
190 1
                ->setParameter('create_date_start', $searchData['create_date_start']);
191
        }
192 40 View Code Duplication
        if (!empty($searchData['create_date_end']) && $searchData['create_date_end']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
193 1
            $date = clone $searchData['create_date_end'];
194 1
            $date->modify('+1 days');
195
            $qb
196 1
                ->andWhere('c.create_date < :create_date_end')
197 1
                ->setParameter('create_date_end', $date);
198
        }
199
200
        // update_date
201 40
        if (!empty($searchData['update_date_start']) && $searchData['update_date_start']) {
202
            $qb
203 1
                ->andWhere('c.update_date >= :update_date_start')
204 1
                ->setParameter('update_date_start', $searchData['update_date_start']);
205
        }
206 40 View Code Duplication
        if (!empty($searchData['update_date_end']) && $searchData['update_date_end']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
207 1
            $date = clone $searchData['update_date_end'];
208 1
            $date->modify('+1 days');
209
            $qb
210 1
                ->andWhere('c.update_date < :update_date_end')
211 1
                ->setParameter('update_date_end', $date);
212
        }
213
214
        // last_buy
215 40
        if (!empty($searchData['last_buy_start']) && $searchData['last_buy_start']) {
216
            $qb
217 1
                ->andWhere('c.last_buy_date >= :last_buy_start')
218 1
                ->setParameter('last_buy_start', $searchData['last_buy_start']);
219
        }
220 40 View Code Duplication
        if (!empty($searchData['last_buy_end']) && $searchData['last_buy_end']) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
221 1
            $date = clone $searchData['last_buy_end'];
222 1
            $date->modify('+1 days');
223
            $qb
224 1
                ->andWhere('c.last_buy_date < :last_buy_end')
225 1
                ->setParameter('last_buy_end', $date);
226
        }
227
228
        // status
229 40
        if (!empty($searchData['customer_status']) && count($searchData['customer_status']) > 0) {
230
            $qb
231 4
                ->andWhere($qb->expr()->in('c.Status', ':statuses'))
232 4
                ->setParameter('statuses', $searchData['customer_status']);
233
        }
234
235
        // buy_product_name
236 40 View Code Duplication
        if (isset($searchData['buy_product_name']) && StringUtil::isNotBlank($searchData['buy_product_name'])) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated across 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...
237
            $qb
238 2
                ->leftJoin('c.Orders', 'o')
239 2
                ->leftJoin('o.OrderItems', 'oi')
240 2
                ->andWhere('oi.product_name LIKE :buy_product_name')
241 2
                ->setParameter('buy_product_name', '%'.$searchData['buy_product_name'].'%');
242
        }
243
244
        // Order By
245 40
        $qb->addOrderBy('c.update_date', 'DESC');
246
247 40
        return $this->queries->customize(QueryKey::CUSTOMER_SEARCH, $qb, $searchData);
248
    }
249
250
    /**
251
     * ユニークなシークレットキーを返す.
252
     *
253
     * @return string
254
     */
255 303 View Code Duplication
    public function getUniqueSecretKey()
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...
256
    {
257
        do {
258 303
            $key = StringUtil::random(32);
259 303
            $Customer = $this->findOneBy(['secret_key' => $key]);
260 303
        } while ($Customer);
261
262 303
        return $key;
263
    }
264
265
    /**
266
     * ユニークなパスワードリセットキーを返す
267
     *
268
     * @return string
269
     */
270 View Code Duplication
    public function getUniqueResetKey()
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...
271
    {
272
        do {
273
            $key = StringUtil::random(32);
274
            $Customer = $this->findOneBy(['reset_key' => $key]);
275
        } while ($Customer);
276
277
        return $key;
278
    }
279
280
    /**
281
     * 仮会員をシークレットキーで検索する.
282
     *
283
     * @param $secretKey
284
     *
285
     * @return null|Customer 見つからない場合はnullを返す.
286
     */
287 4
    public function getProvisionalCustomerBySecretKey($secretKey)
288
    {
289 4
        return $this->findOneBy([
290 4
            'secret_key' => $secretKey,
291 4
            'Status' => CustomerStatus::PROVISIONAL,
292
        ]);
293
    }
294
295
    /**
296
     * 本会員をemailで検索する.
297
     *
298
     * @param $email
299
     *
300
     * @return null|Customer 見つからない場合はnullを返す.
301
     */
302 1
    public function getRegularCustomerByEmail($email)
303
    {
304 1
        return $this->findOneBy([
305 1
            'email' => $email,
306 1
            'Status' => CustomerStatus::REGULAR,
307
        ]);
308
    }
309
310
    /**
311
     * 本会員をリセットキーで検索する.
312
     *
313
     * @param $resetKey
314
     *
315
     * @return null|Customer 見つからない場合はnullを返す.
316
     */
317 3
    public function getRegularCustomerByResetKey($resetKey)
318
    {
319 3
        return $this->createQueryBuilder('c')
320 3
            ->where('c.reset_key = :reset_key AND c.Status = :status AND c.reset_expire >= :reset_expire')
321 3
            ->setParameter('reset_key', $resetKey)
322 3
            ->setParameter('status', CustomerStatus::REGULAR)
323 3
            ->setParameter('reset_expire', new \DateTime())
324 3
            ->setMaxResults(1)
325 3
            ->getQuery()
326 3
            ->getOneOrNullResult();
327
    }
328
329
    /**
330
     * リセット用パスワードを生成する.
331
     *
332
     * @return string
333
     */
334 2
    public function getResetPassword()
335
    {
336 2
        return StringUtil::random(8);
337
    }
338
339
    /**
340
     * 会員の初回購入時間、購入時間、購入回数、購入金額を更新する
341
     *
342
     * @param Customer $Customer
343
     * @param null $isNewOrder
344
     */
345 1
    public function updateBuyData(Customer $Customer, $isNewOrder = null)
346
    {
347
        // 会員の場合、初回購入時間・購入時間・購入回数・購入金額を更新
348
349
        $arr = [
350 1
            OrderStatus::NEW,
351
            OrderStatus::PAY_WAIT,
352
            OrderStatus::BACK_ORDER,
353
            OrderStatus::DELIVERED,
354
            OrderStatus::PAID,
355
        ];
356
357 1
        $result = $this->orderRepository->getCustomerCount($Customer, $arr);
358
359 1
        if (!empty($result)) {
360 1
            $data = $result[0];
361
362 1
            $now = new \DateTime();
363
364 1
            $firstBuyDate = $Customer->getFirstBuyDate();
365 1
            if (empty($firstBuyDate)) {
366 1
                $Customer->setFirstBuyDate($now);
367
            }
368
369 1
            if ($isNewOrder) {
370 1
                $Customer->setLastBuyDate($now);
371
            } else {
372
                $Order = $this->orderRepository->find($data['order_id']);
373
                if ($Order) {
374
                    $Customer->setLastBuyDate($Order->getOrderDate());
375
                }
376
            }
377
378 1
            $Customer->setBuyTimes($data['buy_times']);
379 1
            $Customer->setBuyTotal($data['buy_total']);
380
        } else {
381
            // 受注データが存在しなければ初期化
382 1
            $Customer->setFirstBuyDate(null);
383 1
            $Customer->setLastBuyDate(null);
384 1
            $Customer->setBuyTimes(0);
385 1
            $Customer->setBuyTotal(0);
386
        }
387
388 1
        $this->entityManager->persist($Customer);
389 1
        $this->entityManager->flush();
390
    }
391
392
    /**
393
     * 仮会員, 本会員の会員を返す.
394
     * Eccube\Entity\CustomerのUniqueEntityバリデーションで使用しています.
395
     *
396
     * @param array $criteria
397
     *
398
     * @return Customer[]
399
     */
400 42
    public function getNonWithdrawingCustomers(array $criteria = [])
401
    {
402 42
        $criteria['Status'] = [
403 42
            CustomerStatus::PROVISIONAL,
404 42
            CustomerStatus::REGULAR,
405
        ];
406
407 42
        return $this->findBy($criteria);
408
    }
409
}
410