Passed
Push — master ( d35c25...12af85 )
by Vicens
02:35
created

Captcha::build()   D

Complexity

Conditions 13
Paths 36

Size

Total Lines 95
Code Lines 48

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 95
rs 4.9922
c 0
b 0
f 0
cc 13
eloc 48
nc 36
nop 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * @description 验证码生成类
4
 * @author vicens <[email protected]>
5
 */
6
7
namespace Vicens\Captcha;
8
9
use Symfony\Component\HttpFoundation\Session\Session;
10
11
/**
12
 * Class Captcha
13
 * @method Captcha length(int $length)  设置验证码字符长度
14
 * @method Captcha charset(string $charset) 设置验证码的字符集
15
 * @method Captcha width(int $width) 设置验证码宽度
16
 * @method Captcha height(int $height) 设置验证码高度
17
 * @method Captcha textColor(string $color) 设置文本颜色
18
 * @method Captcha textFont(string $font) 设置文本字体
19
 * @method Captcha backgroundColor(string $color) 设置背景颜色
20
 * @method Captcha distortion(boolean $distortion) 是否开启失真模式
21
 * @method Captcha maxFrontLines(int $maxFrontLines) 设置最大前景线条数
22
 * @method Captcha minFrontLines(int $minFrontLines) 设置最小前景线条数
23
 * @method Captcha maxAngle(int $maxAngle) 文字最大倾斜角度
24
 * @method Captcha maxOffset(int $maxOffset) 文字最大偏移
25
 *
26
 * @package Vicens\Captcha
27
 */
28
class Captcha
29
{
30
    /**
31
     * 存储在session中的key
32
     */
33
    const NAME = 'captcha';
34
35
    /**
36
     * 验证码配置
37
     * @var array
38
     */
39
    protected $config = [
40
        /**
41
         * 默认验证码长度
42
         * @var int
43
         */
44
        'length' => 4,
45
        /**
46
         * 验证码字符集
47
         * @var string
48
         */
49
        'charset' => 'abcdefghijklmnpqrstuvwxyz123456789',
50
        /**
51
         * 默认验证码宽度
52
         * @var int
53
         */
54
        'width' => 150,
55
        /**
56
         * 默认验证码高度
57
         * @var int
58
         */
59
        'height' => 40,
60
        /**
61
         * 指定文字颜色
62
         * @var string
63
         */
64
        'textColor' => null,
65
        /**
66
         * 文字字体文件
67
         * @var string
68
         */
69
        'textFont' => null,
70
        /**
71
         * 指定图片背景色
72
         * @var string
73
         */
74
        'backgroundColor' => null,
75
        /**
76
         * 开启失真模式
77
         * @var bool
78
         */
79
        'distortion' => true,
80
        /**
81
         * 最大前景线条数
82
         * @var int
83
         */
84
        'maxFrontLines' => null,
85
        /**
86
         * 最大背景线条数
87
         * @val int
88
         */
89
        'maxBehindLines' => null,
90
        /**
91
         * 文字最大角度
92
         * @var int
93
         */
94
        'maxAngle' => 8,
95
        /**
96
         * 文字最大偏移量
97
         * @var int
98
         */
99
        'maxOffset' => 5
100
    ];
101
102
    /**
103
     * 存储驱动
104
     * @var Session
105
     */
106
    protected $store;
107
108
109
    public function __construct(array $config = [])
110
    {
111
112
        $this->store = new Session();
113
114
        $this->setConfig($config);
115
    }
116
117
    /**
118
     * 设置验证码配置
119
     *
120
     * @param array|string $config 配置数组或配置项key
121
     * @param mixed $value 配置项值
122
     * @return $this
123
     */
124
    public function setConfig($config, $value = null)
125
    {
126
127
        if (!is_array($config)) {
128
            $config = [$config => $value];
129
        }
130
131
        foreach ($config as $key => $value) {
132
            if (array_key_exists($key, $this->config)) {
133
                $this->config[$key] = $value;
134
            }
135
        }
136
        return $this;
137
    }
138
139
    /**
140
     * 获取配置
141
     *
142
     * @param string|null $key 配置项key
143
     * @return string|number|array
144
     */
145
    public function getConfig($key = null)
146
    {
147
        if ($key !== null) {
148
            return $this->config[$key];
149
        }
150
151
        return $this->config;
152
    }
153
154
    /**
155
     * 生成验证码
156
     *
157
     * @return Image
158
     */
159
    public function make()
160
    {
161
162
        $code = $this->generate();
163
164
        $hash = password_hash($code, PASSWORD_BCRYPT, array('cost' => 10));
165
166
        if ($hash === false) {
167
            throw new \RuntimeException('Bcrypt hashing not supported.');
168
        }
169
170
        $this->store->set(self::NAME, $hash);
171
172
        return new Image($this->build($code));
173
    }
174
175
    /**
176
     * 仅测试正确性, 不删除验证码
177
     *
178
     * @param string $input
179
     * @return bool
180
     */
181
    public function test($input)
182
    {
183
184
        if (!($this->store->has(self::NAME) && $input)) {
185
            return false;
186
        }
187
188
        //返回验证结果
189
        return password_verify(strtolower($input), $this->store->get(self::NAME));
190
    }
191
192
    /**
193
     * 检测正确性,并删除验证码
194
     *
195
     * @param string $input
196
     * @return bool
197
     */
198
    public function check($input)
199
    {
200
        $result = $this->test($input);
201
202
        $this->store->remove(self::NAME);
203
204
        return $result;
205
    }
206
207
    /**
208
     * 生成验证码
209
     *
210
     * @return string
211
     */
212
    protected function generate()
213
    {
214
        $characters = str_split($this->getConfig('charset'));
215
        $length = $this->getConfig('length');
216
217
        $code = '';
218
        for ($i = 0; $i < $length; $i++) {
219
            $code .= $characters[rand(0, count($characters) - 1)];
220
        }
221
222
        return strtolower($code);
223
    }
224
225
226
    /**
227
     * 创建验证码图片
228
     *
229
     * @param string $code
230
     * @return resource
231
     */
232
    protected function build($code)
233
    {
234
235
        // 图片宽
236
        $width = $this->getConfig('width');
237
        // 图片高
238
        $height = $this->getConfig('height');
239
        // 背景颜色
240
        $backgroundColor = $this->getConfig('backgroundColor');
241
242
243
        //随机取一个字体
244
        $font = $this->getTextFont();
245
246
        //根据宽高创建一个背景画布
247
        $image = imagecreatetruecolor($width, $height);
248
249
        if ($backgroundColor === null) {
250
            $backgroundColor = imagecolorallocate($image, mt_rand(200, 255), mt_rand(200, 255), mt_rand(200, 255));
251
        } else {
252
            $color = $backgroundColor;
253
            $backgroundColor = imagecolorallocate($image, $color[0], $color[1], $color[2]);
254
        }
255
        //填充背景色
256
        imagefill($image, 0, 0, $backgroundColor);
257
258
        //绘制背景干扰线
259
        $this->drawLines($image, $this->getConfig('maxBehindLines'));
260
261
        //写入验证码文字
262
        $color = $this->renderText($image, $code, $font);
263
264
        //绘制前景干扰线
265
        $this->drawLines($image, $this->getConfig('maxFrontLines'), $color);
266
267
268
        if ($this->getConfig('distortion')) {
269
270
            //创建失真
271
            $contents = imagecreatetruecolor($width, $height);
272
            $X = mt_rand(0, $width);
273
            $Y = mt_rand(0, $height);
274
            $phase = mt_rand(0, 10);
275
            $scale = 1.1 + mt_rand(0, 10000) / 30000;
276
277
            for ($x = 0; $x < $width; $x++) {
278
                for ($y = 0; $y < $height; $y++) {
279
                    $Vx = $x - $X;
280
                    $Vy = $y - $Y;
281
                    $Vn = sqrt($Vx * $Vx + $Vy * $Vy);
282
283
                    if ($Vn != 0) {
284
                        $Vn2 = $Vn + 4 * sin($Vn / 30);
285
                        $nX = $X + ($Vx * $Vn2 / $Vn);
286
                        $nY = $Y + ($Vy * $Vn2 / $Vn);
287
                    } else {
288
                        $nX = $X;
289
                        $nY = $Y;
290
                    }
291
                    $nY = $nY + $scale * sin($phase + $nX * 0.2);
292
293
                    $p = $this->getColor($image, round($nX), round($nY), $backgroundColor);
294
295
                    if ($p == 0) {
296
                        $p = $backgroundColor;
297
                    }
298
299
                    imagesetpixel($contents, $x, $y, $p);
300
                }
301
            }
302
303
304
            $image = $contents;
305
        }
306
307
        //如果不指定字体颜色和背景颜色,则使用图像过滤器修饰
308
        if (function_exists('imagefilter') && is_null($backgroundColor) && is_null($this->getConfig('textColor'))) {
309
            //颜色翻转 - 1/2几率
310
            if (mt_rand(0, 1) == 0) {
311
                imagefilter($image, IMG_FILTER_NEGATE);
312
            }
313
            //用边缘检测来突出图像的边缘 - 1/11几率
314
            if (mt_rand(0, 10) == 0) {
315
                imagefilter($image, IMG_FILTER_EDGEDETECT);
316
            }
317
            //改变图像的对比度
318
            imagefilter($image, IMG_FILTER_CONTRAST, mt_rand(-50, 10));
319
320
            if (mt_rand(0, 5) == 0) {
321
                //用高斯算法和指定颜色模糊图像
322
                imagefilter($image, IMG_FILTER_COLORIZE, mt_rand(-80, 50), mt_rand(-80, 50), mt_rand(-80, 50));
323
            }
324
        }
325
        return $image;
326
    }
327
328
    /**
329
     * 获取一个字体
330
     *
331
     * @return string
332
     */
333
    protected function getTextFont()
334
    {
335
        //指定字体
336
        if ($this->getConfig('textFont') && file_exists($this->getConfig('textFont'))) {
337
            return $this->getConfig('textFont');
338
        }
339
        //随机字体
340
        return __DIR__ . '/../fonts/' . mt_rand(0, 5) . '.ttf';
341
    }
342
343
    /**
344
     * 写入验证码到图片中
345
     *
346
     * @param resource $image
347
     * @param string $phrase
348
     * @param string $font
349
     * @return int
350
     */
351
    protected function renderText($image, $phrase, $font)
352
    {
353
        $length = strlen($phrase);
354
        if ($length === 0) {
355
            return imagecolorallocate($image, 0, 0, 0);
356
        }
357
358
        // 计算文字尺寸
359
        $size = $this->getConfig('width') / $length - mt_rand(0, 3) - 1;
360
        $box = imagettfbbox($size, 0, $font, $phrase);
361
        $textWidth = $box[2] - $box[0];
362
        $textHeight = $box[1] - $box[7];
363
        $x = ($this->getConfig('width') - $textWidth) / 2;
364
        $y = ($this->getConfig('height') - $textHeight) / 2 + $size;
365
366
        if (!count($this->getConfig('textColor'))) {
367
            $textColor = array(mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
368
        } else {
369
            $textColor = $this->getConfig('textColor');
370
        }
371
        $color = imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
372
373
        // 循环写入字符,随机角度
374
        for ($i = 0; $i < $length; $i++) {
375
            $box = imagettfbbox($size, 0, $font, $phrase[$i]);
376
            $w = $box[2] - $box[0];
377
            $angle = mt_rand(-$this->getConfig('maxAngle'), $this->getConfig('maxAngle'));
378
            $offset = mt_rand(-$this->getConfig('maxOffset'), $this->getConfig('maxOffset'));
379
            imagettftext($image, $size, $angle, $x, $y + $offset, $color, $font, $phrase[$i]);
380
            $x += $w;
381
        }
382
383
        return $color;
384
    }
385
386
    /**
387
     * 画线
388
     *
389
     * @param resource $image
390
     * @param int $width
391
     * @param int $height
392
     * @param int|null $color
393
     */
394
    protected function renderLine($image, $width, $height, $color = null)
395
    {
396
        if ($color === null) {
397
            $color = imagecolorallocate($image, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
398
        }
399
400
        if (mt_rand(0, 1)) { // 横向
401
            $Xa = mt_rand(0, $width / 2);
402
            $Ya = mt_rand(0, $height);
403
            $Xb = mt_rand($width / 2, $width);
404
            $Yb = mt_rand(0, $height);
405
        } else { // 纵向
406
            $Xa = mt_rand(0, $width);
407
            $Ya = mt_rand(0, $height / 2);
408
            $Xb = mt_rand(0, $width);
409
            $Yb = mt_rand($height / 2, $height);
410
        }
411
        imagesetthickness($image, mt_rand(1, 3));
412
        imageline($image, $Xa, $Ya, $Xb, $Yb, $color);
413
    }
414
415
    /**
416
     * 画线
417
     *
418
     * @param resource $image
419
     * @param int $max
420
     * @param int|null $color
421
     */
422
    protected function drawLines($image, $max, $color = null)
423
    {
424
        $square = $this->getConfig('width') * $this->getConfig('height');
425
        $effects = mt_rand($square / 3000, $square / 2000);
426
427
        // 计算线条数
428
        if ($max != null && $max > 0) {
429
            $effects = min($max, $effects);
430
        }
431
432
        if ($max !== 0) {
433
            for ($e = 0; $e < $effects; $e++) {
434
435
                if ($color !== null) {
436
                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'), $color);
437
                } else {
438
                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'));
439
                }
440
441
            }
442
        }
443
    }
444
445
    /**
446
     * 获取颜色
447
     *
448
     * @param resource $image
449
     * @param int $x
450
     * @param int $y
451
     * @param int $background
452
     * @return int
453
     */
454
    protected function getColor($image, $x, $y, $background)
455
    {
456
        $L = imagesx($image);
457
        $H = imagesy($image);
458
        if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
459
            return $background;
460
        }
461
462
        return imagecolorat($image, $x, $y);
463
    }
464
465
    /**
466
     * @param string $name
467
     * @param array $arguments
468
     * @return $this
469
     */
470
    public function __call($name, $arguments)
471
    {
472
        if (array_key_exists($name, $this->config)) {
473
            $this->config[$name] = $arguments[0];
474
        }
475
476
        return $this;
477
    }
478
}