Passed
Push — master ( b1bd76...be2fa3 )
by Vicens
02:41
created

Captcha::build()   C

Complexity

Conditions 9
Paths 36

Size

Total Lines 61
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
dl 0
loc 61
rs 6.7603
c 0
b 0
f 0
cc 9
eloc 26
nc 36
nop 1

How to fix   Long Method   

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
            $image = $this->createDistortion($image, $width, $height, $backgroundColor);
271
        }
272
273
        //如果不指定字体颜色和背景颜色,则使用图像过滤器修饰
274
        if (function_exists('imagefilter') && is_null($backgroundColor) && is_null($this->getConfig('textColor'))) {
275
            //颜色翻转 - 1/2几率
276
            if (mt_rand(0, 1) == 0) {
277
                imagefilter($image, IMG_FILTER_NEGATE);
278
            }
279
            //用边缘检测来突出图像的边缘 - 1/11几率
280
            if (mt_rand(0, 10) == 0) {
281
                imagefilter($image, IMG_FILTER_EDGEDETECT);
282
            }
283
            //改变图像的对比度
284
            imagefilter($image, IMG_FILTER_CONTRAST, mt_rand(-50, 10));
285
286
            if (mt_rand(0, 5) == 0) {
287
                //用高斯算法和指定颜色模糊图像
288
                imagefilter($image, IMG_FILTER_COLORIZE, mt_rand(-80, 50), mt_rand(-80, 50), mt_rand(-80, 50));
289
            }
290
        }
291
        return $image;
292
    }
293
294
    /**
295
     * 创建失真
296
     *
297
     * @param resource $image
298
     * @param int $width
299
     * @param int $height
300
     * @param int $backgroundColor
301
     * @return resource
302
     */
303
    protected function createDistortion($image, $width, $height, $backgroundColor)
304
    {
305
        //创建失真
306
        $contents = imagecreatetruecolor($width, $height);
307
        $X = mt_rand(0, $width);
308
        $Y = mt_rand(0, $height);
309
        $phase = mt_rand(0, 10);
310
        $scale = 1.1 + mt_rand(0, 10000) / 30000;
311
312
        for ($x = 0; $x < $width; $x++) {
313
            for ($y = 0; $y < $height; $y++) {
314
                $Vx = $x - $X;
315
                $Vy = $y - $Y;
316
                $Vn = sqrt($Vx * $Vx + $Vy * $Vy);
317
318
                if ($Vn != 0) {
319
                    $Vn2 = $Vn + 4 * sin($Vn / 30);
320
                    $nX = $X + ($Vx * $Vn2 / $Vn);
321
                    $nY = $Y + ($Vy * $Vn2 / $Vn);
322
                } else {
323
                    $nX = $X;
324
                    $nY = $Y;
325
                }
326
                $nY = $nY + $scale * sin($phase + $nX * 0.2);
327
328
                $p = $this->getColor($image, round($nX), round($nY), $backgroundColor);
329
330
                if ($p == 0) {
331
                    $p = $backgroundColor;
332
                }
333
334
                imagesetpixel($contents, $x, $y, $p);
335
            }
336
        }
337
338
339
        return $contents;
340
    }
341
342
    /**
343
     * 获取一个字体
344
     *
345
     * @return string
346
     */
347
    protected function getTextFont()
348
    {
349
        //指定字体
350
        if ($this->getConfig('textFont') && file_exists($this->getConfig('textFont'))) {
351
            return $this->getConfig('textFont');
352
        }
353
        //随机字体
354
        return __DIR__ . '/../fonts/' . mt_rand(0, 5) . '.ttf';
355
    }
356
357
    /**
358
     * 写入验证码到图片中
359
     *
360
     * @param resource $image
361
     * @param string $phrase
362
     * @param string $font
363
     * @return int
364
     */
365
    protected function renderText($image, $phrase, $font)
366
    {
367
        $length = strlen($phrase);
368
        if ($length === 0) {
369
            return imagecolorallocate($image, 0, 0, 0);
370
        }
371
372
        // 计算文字尺寸
373
        $size = $this->getConfig('width') / $length - mt_rand(0, 3) - 1;
374
        $box = imagettfbbox($size, 0, $font, $phrase);
375
        $textWidth = $box[2] - $box[0];
376
        $textHeight = $box[1] - $box[7];
377
        $x = ($this->getConfig('width') - $textWidth) / 2;
378
        $y = ($this->getConfig('height') - $textHeight) / 2 + $size;
379
380
        if (!count($this->getConfig('textColor'))) {
381
            $textColor = array(mt_rand(0, 150), mt_rand(0, 150), mt_rand(0, 150));
382
        } else {
383
            $textColor = $this->getConfig('textColor');
384
        }
385
        $color = imagecolorallocate($image, $textColor[0], $textColor[1], $textColor[2]);
386
387
        // 循环写入字符,随机角度
388
        for ($i = 0; $i < $length; $i++) {
389
            $box = imagettfbbox($size, 0, $font, $phrase[$i]);
390
            $w = $box[2] - $box[0];
391
            $angle = mt_rand(-$this->getConfig('maxAngle'), $this->getConfig('maxAngle'));
392
            $offset = mt_rand(-$this->getConfig('maxOffset'), $this->getConfig('maxOffset'));
393
            imagettftext($image, $size, $angle, $x, $y + $offset, $color, $font, $phrase[$i]);
394
            $x += $w;
395
        }
396
397
        return $color;
398
    }
399
400
    /**
401
     * 画线
402
     *
403
     * @param resource $image
404
     * @param int $width
405
     * @param int $height
406
     * @param int|null $color
407
     */
408
    protected function renderLine($image, $width, $height, $color = null)
409
    {
410
        if ($color === null) {
411
            $color = imagecolorallocate($image, mt_rand(100, 255), mt_rand(100, 255), mt_rand(100, 255));
412
        }
413
414
        if (mt_rand(0, 1)) { // 横向
415
            $Xa = mt_rand(0, $width / 2);
416
            $Ya = mt_rand(0, $height);
417
            $Xb = mt_rand($width / 2, $width);
418
            $Yb = mt_rand(0, $height);
419
        } else { // 纵向
420
            $Xa = mt_rand(0, $width);
421
            $Ya = mt_rand(0, $height / 2);
422
            $Xb = mt_rand(0, $width);
423
            $Yb = mt_rand($height / 2, $height);
424
        }
425
        imagesetthickness($image, mt_rand(1, 3));
426
        imageline($image, $Xa, $Ya, $Xb, $Yb, $color);
427
    }
428
429
    /**
430
     * 画线
431
     *
432
     * @param resource $image
433
     * @param int $max
434
     * @param int|null $color
435
     */
436
    protected function drawLines($image, $max, $color = null)
437
    {
438
        $square = $this->getConfig('width') * $this->getConfig('height');
439
        $effects = mt_rand($square / 3000, $square / 2000);
440
441
        // 计算线条数
442
        if ($max != null && $max > 0) {
443
            $effects = min($max, $effects);
444
        }
445
446
        if ($max !== 0) {
447
            for ($e = 0; $e < $effects; $e++) {
448
449
                if ($color !== null) {
450
                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'), $color);
451
                } else {
452
                    $this->renderLine($image, $this->getConfig('width'), $this->getConfig('height'));
453
                }
454
455
            }
456
        }
457
    }
458
459
    /**
460
     * 获取颜色
461
     *
462
     * @param resource $image
463
     * @param int $x
464
     * @param int $y
465
     * @param int $background
466
     * @return int
467
     */
468
    protected function getColor($image, $x, $y, $background)
469
    {
470
        $L = imagesx($image);
471
        $H = imagesy($image);
472
        if ($x < 0 || $x >= $L || $y < 0 || $y >= $H) {
473
            return $background;
474
        }
475
476
        return imagecolorat($image, $x, $y);
477
    }
478
479
    /**
480
     * @param string $name
481
     * @param array $arguments
482
     * @return $this
483
     */
484
    public function __call($name, $arguments)
485
    {
486
        if (array_key_exists($name, $this->config)) {
487
            $this->config[$name] = $arguments[0];
488
        }
489
490
        return $this;
491
    }
492
}