Completed
Push — master ( ccb689...308630 )
by Andreas
02:01
created

CreditCard::getHolderName()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
rs 10
cc 1
eloc 2
nc 1
nop 0
1
<?php
2
3
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5
/*
6
 * This file is part of the Larium CreditCard package.
7
 *
8
 * (c) Andreas Kollaros <[email protected]>
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 Larium\CreditCard;
15
16
/**
17
 * CreditCard class acts as value object.
18
 *
19
 * @author  Andreas Kollaros <[email protected]>
20
 */
21
final class CreditCard
22
{
23
    const VISA               = 'visa';
24
    const MASTER             = 'master';
25
    const DISCOVER           = 'discover';
26
    const AMEX               = 'american_express';
27
    const DINERS_CLUB        = 'diners_club';
28
    const JCB                = 'jcb';
29
    const SWITCH_BRAND       = 'switch';
30
    const SOLO               = 'solo';
31
    const DANKORT            = 'dankort';
32
    const MAESTRO            = 'maestro';
33
    const FORBRUGSFORENINGEN = 'forbrugsforeningen';
34
    const LASER              = 'laser';
35
36
    /**
37
     * Card holder name.
38
     * Should be in upper case.
39
     *
40
     * @var string
41
     */
42
    private $holderName;
43
44
    /**
45
     * Expire date of card as value object
46
     *
47
     * @var ExpiryDate
48
     */
49
    private $expiryDate;
50
51
    /**
52
     * The brand of card.
53
     *
54
     * @var string
55
     */
56
    private $brand;
57
58
    /**
59
     * The number of card.
60
     *
61
     * @var string
62
     */
63
    private $number;
64
65
    /**
66
     * The verification value of card (cvv).
67
     * 3 or 4 digits.
68
     *
69
     * @var integer
70
     */
71
    private $cvv;
72
73
    /**
74
     * Whether card is require verification value to be present.
75
     *
76
     * @var boolean
77
     */
78
    private $requireCvv = true;
79
80
    /**
81
     * Token stored from a real credit card and can be used for purchases.
82
     *
83
     * @var Token
84
     */
85
    private $token;
86
87
    private static $cardCompanies = array(
88
        self::VISA               => '/^4\d{12}(\d{3})?$/',
89
        self::MASTER             => '/^(5[1-5]\d{4}|677189)\d{10}$/',
90
        self::DISCOVER           => '/^(6011|65\d{2})\d{12}$/',
91
        self::AMEX               => '/^3[47]\d{13}$/',
92
        self::DINERS_CLUB        => '/^3(0[0-5]|[68]\d)\d{11}$/',
93
        self::JCB                => '/^35(28|29|[3-8]\d)\d{12}$/',
94
        self::SWITCH_BRAND       => '/^6759\d{12}(\d{2,3})?$/',
95
        self::SOLO               => '/^6767\d{12}(\d{2,3})?$/',
96
        self::DANKORT            => '/^5019\d{12}$/',
97
        self::MAESTRO            => '/^(5[06-8]|6\d)\d{10,17}$/',
98
        self::FORBRUGSFORENINGEN => '/^600722\d{10}$/',
99
        self::LASER              => '/^(6304|6706|6771|6709)\d{8}(\d{4}|\d{6,7})?$/'
100
    );
101
102
    public function __construct(array $options = array())
103
    {
104
        $default = array(
105
            'holderName' => null,
106
            'month'      => 1,
107
            'year'       => 1970,
108
            'brand'      => null,
109
            'number'     => null,
110
            'cvv'        => null,
111
            'requireCvv' => true,
112
            'token'      => null
113
        );
114
115
        $options = array_intersect_key($options, $default);
116
117
        $options = array_replace($default, $options);
118
119
        $month = $options['month'];
120
        $year  = $options['year'];
121
        $brand = $options['brand'];
122
        $token = $options['token'];
123
124
        unset($options['month'], $options['year'], $options['brand'], $options['token']);
125
126
        $expiryDate = new ExpiryDate($month, $year);
127
        $this->expiryDate = $expiryDate;
128
129
        foreach ($options as $prop => $value) {
130
            $this->$prop = $value;
131
        }
132
133
        if (false === $this->brand = $this->detectBrand()) {
0 ignored issues
show
Documentation Bug introduced by
It seems like $this->detectBrand() can also be of type integer or false. However, the property $brand is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
134
            $this->brand = $brand;
135
        }
136
137
        if ($token) {
138
            $token instanceof Token
139
                ? $this->token = $token
140
                : $this->token = new Token($token);
141
        }
142
    }
143
144
    private function detectBrand()
145
    {
146
        foreach (self::$cardCompanies as $name => $pattern) {
147
            if ($name == 'maestro') {
148
                continue;
149
            }
150
151
            if (preg_match($pattern, $this->number)) {
152
                return $name;
153
            }
154
        }
155
156
        if (preg_match(self::$cardCompanies['maestro'], $this->number)) {
157
            return 'maestro';
158
        }
159
160
        return false;
161
    }
162
163
    private function with($prop, $value)
164
    {
165
        $card = clone $this;
166
167
        $card->$prop = $value;
168
169
        return $card;
170
    }
171
172
    /**
173
     * Gets the number of card.
174
     *
175
     * @return string
176
     */
177
    public function getNumber()
178
    {
179
        return $this->number;
180
    }
181
182
    /**
183
     * Sets card number.
184
     *
185
     * @param  string $number
186
     * @return CreditCard
187
     */
188
    public function withNumber($number)
189
    {
190
        $card = $this->with('number', $number);
191
        $card->brand = $card->detectBrand();
0 ignored issues
show
Documentation Bug introduced by
It seems like $card->detectBrand() can also be of type integer or false. However, the property $brand is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

    public function __construct($id)
    {
        $this->id = $id;
    }

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
192
        $card->token = null;
193
194
        return $card;
195
    }
196
197
    /**
198
     * Gets card holder first name.
199
     *
200
     * @return string
201
     */
202
    public function getHolderName()
203
    {
204
        return $this->holderName;
205
    }
206
207
    /**
208
     * Sets card holder first name.
209
     *
210
     * @param  string $firstName
0 ignored issues
show
Bug introduced by
There is no parameter named $firstName. 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...
211
     * @return CreditCard
212
     */
213
    public function withHolderName($holderName)
214
    {
215
        $holderName = strtoupper($holderName);
216
        return $this->with('holderName', $holderName);
217
    }
218
219
    /**
220
     * Gets expiry date card.
221
     *
222
     * @return ExpireDate
223
     */
224
    public function getExpiryDate()
225
    {
226
        return $this->expiryDate;
227
    }
228
229
    /**
230
     * Sets expiry month of card.
231
     *
232
     * @param  ExpiryDate $expiryDate
233
     * @return CreditCard
234
     */
235
    public function withExpiryDate(ExpiryDate $expiryDate)
236
    {
237
        return $this->with('expiryDate', $expiryDate);
238
    }
239
240
    /**
241
     * Gets the brand of card.
242
     *
243
     * @return string
244
     */
245
    public function getBrand()
246
    {
247
        return $this->brand;
248
    }
249
250
    /**
251
     * Sets the brand of card.
252
     *
253
     * @param  string $brand
254
     * @return CreditCard
255
     */
256
    public function withBrand($brand)
257
    {
258
        return $this->with('brand', $brand);
259
    }
260
261
    /**
262
     * Gets card verification value (cvv).
263
     *
264
     * @return integer
265
     */
266
    public function getCvv()
267
    {
268
        return $this->cvv;
269
    }
270
271
    /**
272
     * Sets card verification value.
273
     *
274
     * @param  integer $cvv
275
     * @return CreditCard
276
     */
277
    public function withCvv($cvv)
278
    {
279
        return $this->with('cvv', $cvv);
280
    }
281
282
    /**
283
     * Check if cvv is required for credit card validation.
284
     *
285
     * @return boolean
286
     */
287
    public function isRequireCvv()
288
    {
289
        return $this->requireCvv;
290
    }
291
292
    /**
293
     * Gets referenece token of a credit card.
294
     *
295
     * @return Token
296
     */
297
    public function getToken()
298
    {
299
        return $this->token;
300
    }
301
302
    /**
303
     * Sets token value.
304
     *
305
     * @param  Token $token
306
     * @return CreditCard
307
     */
308
    public function withToken(Token $token)
309
    {
310
        $card = $this->with('token', $token);
311
312
        if (null !== $card->number) {
313
            $lastDigits = strlen($card->number) <= 4
314
                ? $card->number :
315
                substr($card->number, -4);
316
            $card->number = "XXXX-XXXX-XXXX-" . $lastDigits;
317
        }
318
319
        $card->cvv = null;
320
321
        return $card;
322
    }
323
324
    /**
325
     * Checks whether credit card has stored a Token reference or not.
326
     *
327
     * @return boolean
328
     */
329
    public function hasToken()
330
    {
331
        return null !== $this->token;
332
    }
333
}
334