-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathatom.xml
1165 lines (963 loc) · 207 KB
/
atom.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
<title>徐豪杰写字的地方</title>
<link href="/atom.xml" rel="self"/>
<link href="http://blog.jayxhj.com/"/>
<updated>2018-02-06T13:03:36.000Z</updated>
<id>http://blog.jayxhj.com/</id>
<author>
<name>jayxhj</name>
</author>
<generator uri="http://hexo.io/">Hexo</generator>
<entry>
<title>把图床从七牛迁移到 GitHub</title>
<link href="http://blog.jayxhj.com/2018/02/migrate-images-from-qiniu-to-github/"/>
<id>http://blog.jayxhj.com/2018/02/migrate-images-from-qiniu-to-github/</id>
<published>2018-02-06T11:10:31.000Z</published>
<updated>2018-02-06T13:03:36.000Z</updated>
<content type="html"><p>事情是这样,国内的域名是需要备案的,备案其实是备案服务器,这样通信管理局就能知道你的网站在哪里托管,在哪台服务器。这里有详细介绍为何要备案 <del><a href="https://help.aliyun.com/knowledge_detail/36907.html?spm=a2c4g.11174283.6.539.HMkMNn" target="_blank" rel="external">为什么要备案</a></del> 这实在是个神奇的规定,哪些网站能访问,居然是别人替你做决定。</p>
<p>问题就来了,没备案,不仅解析会有问题,连 CDN 都是用不了的,人在屋檐下不得不低头?所以就有了这篇,如何将七牛的图片迁移至 GitHub。</p>
<p>共有以下几个步骤:</p>
<ol>
<li>获取文章中的图片 url</li>
<li>将 CDN 上的图片下载下来</li>
<li>移动至 Hexo source/images 目录</li>
<li>推送至 GitHub</li>
</ol>
<p>问题有几个(方案放括号里):</p>
<ol>
<li>博客的链接需要更改,如 img.jayxhj.com 需要改为 blog.jayxhj.com/images (sed 替换)</li>
<li>图片 url 在 Markdown 文件内 (grep 就好)</li>
<li>由于域名下线,那么直接按原有的 url 访问获取不到对应图片的 (将域名替换为七牛分配的 CDN 加速域名)</li>
<li>显然手动一个个下载图片是不好的 (awk 循环顺序下载)</li>
</ol>
<p>以上问题,用命令解释就是</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep -ohE <span class="string">"\!\[.*\]\(.*\)"</span> ./<span class="built_in">source</span>/_posts/* | grep -oE <span class="string">"blog.jayxhj.com/images[a-zA-Z0-9./?=_-]*"</span> | sed <span class="string">'s/blog.jayxhj.com\/images/oiiz5quoo.qnssl.com/g'</span> | awk <span class="string">'&#123;sysetm("curl -O"$0)&#125;'</span></span><br></pre></td></tr></table></figure>
<p>下面一步步说明。</p>
<h2 id="获取文章中的图片 -url"><a href="# 获取文章中的图片 -url" class="headerlink" title="获取文章中的图片 url"></a>获取文章中的图片 url</h2><p>由于 Hexo 的文章源文档都是 Markdown 文档,因此需要将所有的图片地址获取出来,命令如下:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">grep -ohE <span class="string">"\!\[.*\]\(.*\)"</span> ./<span class="built_in">source</span>/_posts/* | grep -oE <span class="string">"blog.jayxhj.com/images[a-zA-Z0-9./?=_-]*"</span></span><br></pre></td></tr></table></figure>
<p>第一件事,是匹配 MarkDown 中的图片 url,<code>grep -ohE &quot;\!\[.*\]\(.*\)&quot; ./source/_posts/*</code> 命令的作用是匹配 <code>![]()</code> 这样的 url,再提取链接。</p>
<p>由于我们的目的是获取对应 url,现在是知道了域名的链接及前缀 <strong>blog.jayxhj.com/images</strong>,那么做以下事情,使用正则表达式匹配 url,对应 <code>-E &quot;blog.jayxhj.com/images[a-zA-Z0-9./?=_-]*&quot;</code>,仅截取匹配到的部分,那就是 -o,表示 –only-matching ,-h 表示 –no-filename ,这样获取从多个文件获取时,就不会有文件名了。</p>
<h2 id="将 -CDN- 上的图片下载下来"><a href="# 将 -CDN- 上的图片下载下来" class="headerlink" title="将 CDN 上的图片下载下来"></a>将 CDN 上的图片下载下来 </h2><p> 所有文章的 url 获取到了,但是这些图片没法访问,因为域名未备案,七牛会将资源屏蔽,但是通过七牛提供的 CDN 域名是可以下载的,我的是 <strong>oiiz5quoo.qnssl.com</strong> 。</p>
<p>前面获取到了,那么现在只需要替换域名,curl 下载即可。命令为 <code>sed &#39;s/blog.jayxhj.com\/images/oiiz5quoo.qnssl.com/g&#39; | awk &#39;{sysetm(&quot;curl -O&quot; $0)}</code></p>
<p>此处有提醒:如果图片存放在七牛的文件夹里,那么应该按照在七牛定义的分割符来拼接出目录名,再递归创建目录,mkdir -p 即可。</p>
<h2 id="将文章内的图片域名替换"><a href="# 将文章内的图片域名替换" class="headerlink" title="将文章内的图片域名替换"></a>将文章内的图片域名替换</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">gsed -i <span class="string">'s/http:\/\/img.jayxhj.com/\/\/blog.jayxhj.com\/images/g'</span> ./<span class="built_in">source</span>/_posts/*</span><br></pre></td></tr></table></figure>
<p>希望以后备案这个词可以消失 😊 。</p>
</content>
<summary type="html">
<p>事情是这样,国内的域名是需要备案的,备案其实是备案服务器,这样通信管理局就能知道你的网站在哪里托管,在哪台服务器。这里有详细介绍为何要备案 <del><a href="https://help.aliyun.com/knowledge_detail/36907.html?s
</summary>
<category term="blog" scheme="http://blog.jayxhj.com/categories/blog/"/>
<category term="blog" scheme="http://blog.jayxhj.com/tags/blog/"/>
<category term="迁移" scheme="http://blog.jayxhj.com/tags/%E8%BF%81%E7%A7%BB/"/>
</entry>
<entry>
<title>调试线上 JS 的几种姿势</title>
<link href="http://blog.jayxhj.com/2016/12/ways-to-debug-js-files-online/"/>
<id>http://blog.jayxhj.com/2016/12/ways-to-debug-js-files-online/</id>
<published>2016-12-15T10:16:20.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>线上某个页面出问题时,我们需要查看日志,需要看页面表现,需要定位问题在哪里以便及时修复。一般后端会通过打日志的形式来确定 bug 的起源,前端就不一样了,上线前 JS 经过混淆、压缩、合并,代码已经不是原来的样子了,无疑给调试带来了困难。那么如何定位问题呢?本文希望从两方面解释,一方面是如何去复现问题,第二步是根据第一步的信息,如何更快更科学地 debug 。</p>
<h1 id="问题的定位"><a href="# 问题的定位" class="headerlink" title="问题的定位"></a>问题的定位 </h1><h2 id="本地复现、调试、解决、上线"><a href="# 本地复现、调试、解决、上线" class="headerlink" title="本地复现、调试、解决、上线"></a> 本地复现、调试、解决、上线 </h2><p> 有些问题其实不用在线上调试,如果场景容易满足,case 容易在线下构造,那么直接在本地改就好了,这种方式比较适合简单的问题,无需特定场景和环境,bug 就能修复。</p>
<h2 id="使用 -Chrome- 浏览器进行 -debug"><a href="# 使用 -Chrome- 浏览器进行 -debug" class="headerlink" title="使用 Chrome 浏览器进行 debug"></a>使用 Chrome 浏览器进行 debug</h2><p>调试页面的问题,使用最多的浏览器就是 Chrome 和 Firefox ,而我习惯于使用 Chrome ,就介绍一下使用 Chrome debug JS 的姿势。</p>
<p>Google 官方给出了 <a href="https://developers.google.com/web/tools/chrome-devtools/" target="_blank" rel="external">DevTools</a> 的介绍,里面有很多案例介绍及使用方法介绍,以博客的形式给出,很方便。</p>
<p>下面介绍的内容实际上就是从 <a href="https://developers.google.com/web/tools/chrome-devtools/javascript/add-breakpoints" target="_blank" rel="external">Set Breakpoints</a> 这个小节学习的。摘录某个调试方法。</p>
<h3 id="设置 -DOM- 改变时的断点"><a href="# 设置 -DOM- 改变时的断点" class="headerlink" title="设置 DOM 改变时的断点"></a>设置 DOM 改变时的断点 </h3><p> 你代码的某个地方出现了 bug ,它错误地改变、删除或者增加一个 DOM 点。DevTools 提供了一个快速找到 bug 根源的工具:针对 DOM 改变的断点。</p>
<p>DevTools 让你可以在某个节点上打断点,让你不用手动地在代码里四处寻找是哪里导致了 DOM 的变化。无论在哪个节点,或是节点的子节点,当它们背添加、删除、改变时,DevTools 都能让页面暂停,然后把你带到引起 DOM 改变的具体代码行。</p>
<p>添加 DOM 改变的断点 的方式: 右键单击要监控的节点,选择要监控的行为,当节点发生改变时,JS 将停止执行,页面将显示引起监控的 DOM 节点改变的 JS 代码行。另外,从 Call Stack 可以查看引发当前监控 DOM 发生改变的 JS 调用栈。</p>
<p>监控节点断点类型:</p>
<p>以下是各个类型的 DOM 节点改变时的断点详细说明:</p>
<ul>
<li><p><strong>Subtree modifications</strong> .</p>
<p>当当前选择的节点的子节点被删除、添加、或者内容发生变化时触发。当子节点的属性改变或者当前节点发生改变时不会触发。</p>
</li>
<li><p><strong>Attributes modifications</strong>.</p>
<p>当当前节点的属性被添加或者删除,或者当前节点的某个属性的值发生变化时触发。</p>
</li>
<li><p><strong>Node Removal</strong>.</p>
<p>当当前节点被删除时触发。</p>
</li>
</ul>
<p>设置 DOM change 相关的断点最大的好处在于,能准确定位到是哪里引起了改变,缺点在于,当出现不对 DOM 产生改变的 bug 时,这种方法就失效了。这种情况,就得确定 bug 的代码范围,再使用断点逐步追踪。</p>
<h1 id="JS- 的调试"><a href="#JS- 的调试" class="headerlink" title="JS 的调试"></a>JS 的调试 </h1><p> 众所周知,静态文件会存在缓存问题。一个页面的加载,如果包含多个 JS ,如何定位到具体的 JS 以及如何使用修改后的 JS 来查看页面效果就很是问题了。下面介绍的方式可以解决前面的问题,原理都是使用本地的 JS 做代理,来接管线上的 JS ,这样线上的调试就转移到本地了。由于线上的 JS 一般都经过压缩、混淆、合并,故最好本地有合成线上 JS 的子 JS ,下面介绍的方式是直接用本地的 JS 替代线上的 JS ,如果是前面说的那种情况,用合并后的 JS 替换线上 JS 即可。</p>
<h2 id="使用代理软件"><a href="# 使用代理软件" class="headerlink" title="使用代理软件"></a>使用代理软件</h2><p>macOS 系统下,Web 调试代理软件一般使用 <a href="https://www.charlesproxy.com/" target="_blank" rel="external">Charles</a> , 使用代理软件的好处是,网络请求的所有资源,请求的细节,都能记录下来,另外它能作为系统代理接管浏览器的网络访问,将这些请求记录下来,是 Web debugging 的利器。</p>
<p>下面要介绍的方式就是 Charles 的代理功能,使用本地的 JS 代理线上的 JS 。</p>
<ol>
<li><p>设置 Charles 为系统代理,代理 Chrome 的网络连接。</p>
<p>这个步骤,需要开启 Charles ,并设置 Charles 为系统代理。Firefox 可以直接设置,Chrome 网络设置实际上就是修改的系统的网络设置,步骤为 <strong>系统偏好设置 -&gt; 网络 -&gt; 高级 -&gt; 代理</strong> 一图胜千言 <img src="//blog.jayxhj.com/images/ServeAsSystemProxy.jpeg" alt="系统代理"></p>
</li>
<li><p>Map Local</p>
<p>使用 Chrome 请求网页, 即可在 Charles 中查看到当前网页所有资源对应的请求,将某个 JS 文件映射到要用作代理的本地 JS 文件,或者粗暴点,直接代理目录,就可以代理文件夹下的所有 JS 了。</p>
<p><img src="//blog.jayxhj.com/images/MapLocal.jpeg" alt="map local"></p>
</li>
</ol>
<p>这个方式下,当在本地修改 JS 后,能直接在线上环境看到效果,最重要的是,免去了修改 JS 后上线 JS 不生效的问题。归根结底,本地修改,上线再看效果这种原始的方式,本该淘汰。</p>
<p>但此方式有个缺点,需要来回切换,你需要切换到编辑器或者 IDE 改好后再回 Chrome 刷新页面看效果,而且不能实时编辑,下面的使用 <strong>Chrome workspace</strong> 的方式无疑是效果最好而且最省时省力的。</p>
<h2 id="使用 -Chrome-workspace"><a href="# 使用 -Chrome-workspace" class="headerlink" title="使用 Chrome workspace"></a>使用 <a href="https://developers.google.com/web/tools/setup/setup-workflow" target="_blank" rel="external">Chrome workspace</a></h2><p>此方式实际上也是使用本地的文件做代理,但区别于其他方式的是,它与 Chrome 无缝集成,是 Chrome DevTools 提供的功能,支持断点,支持将修改持久化到本地。下面介绍配置方式。</p>
<ol>
<li><p>将本地文件添加到 workspace</p>
<ul>
<li>开启调试面板,切换至 Sources 选项卡</li>
<li>右击选择 <strong>Add Folder to Workspace</strong> <img src="//blog.jayxhj.com/images/addfolder.png" alt="Add Folder to Workspace"></li>
<li>选择要代理的本地的文件夹</li>
<li>点击地址栏下方弹出来的窗口,选择 <strong>允许</strong> ,Chrome 才能获得访问本地文件的权限</li>
</ul>
</li>
<li><p>Map to File System Resource</p>
<p>选择要代理的文件,Chrome 将自动列出可以映射的文件,此时就建立了远程文件与本地文件的映射。 <img src="//blog.jayxhj.com/images/maptoresource.png" alt="Map to File System Resource"></p>
</li>
</ol>
<p>设置好之后当 debug 好之后,可以直接将修改持久化到本地的文件,再也不用复制粘贴了。</p>
<p>其他用法见官方介绍:<a href="https://developers.google.com/web/tools/setup/setup-workflow" target="_blank" rel="external">https://developers.google.com/web/tools/setup/setup-workflow</a></p>
</content>
<summary type="html">
<p>线上某个页面出问题时,我们需要查看日志,需要看页面表现,需要定位问题在哪里以便及时修复。一般后端会通过打日志的形式来确定 bug 的起源,前端就不一样了,上线前 JS 经过混淆、压缩、合并,代码已经不是原来的样子了,无疑给调试带来了困难。那么如何定位问题呢?本文希望从两方面
</summary>
<category term="JS" scheme="http://blog.jayxhj.com/tags/js/"/>
<category term="debugging" scheme="http://blog.jayxhj.com/tags/debugging/"/>
</entry>
<entry>
<title>将文件内的 tab 转化为空格</title>
<link href="http://blog.jayxhj.com/2016/08/ways-to-convert-tabs-to-spaces/"/>
<id>http://blog.jayxhj.com/2016/08/ways-to-convert-tabs-to-spaces/</id>
<published>2016-08-20T10:15:46.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>tab 与 space 孰好孰坏这个问题是程序界的圣战之一。StackExchange 旗下的 programmers 就有关于这个问题的 <a href="http://programmers.stackexchange.com/questions/57/tabs-versus-spaces-what-is-the-proper-indentation-character-for-everything-in-e" target="_blank" rel="external">讨论</a> , 没有必要大打出手,倒是可以了解下各种情况下使用这两者的差异及各自的优势。不过本篇不准备讨论这个问题,本篇只想解决日常开发中的需求:将 tab 转化为空格。</p>
<p>总结了一下,大概有以下几种思路:</p>
<ol>
<li>通过程序进行替换</li>
<li>通过重定向输入替换,生成新文件</li>
</ol>
<p>第一个思路适合大批量的替换,也适合大文件的替换,第二个思路实际上是读取文件替换后生成新文件,和第一种差别其实不大,但是很多软件支持指定参数,就非常适合通过管道及重定向来进行替换了。</p>
<h2 id="通过 -vim"><a href="# 通过 -vim" class="headerlink" title="通过 vim"></a>通过 vim</h2><h3 id="通过配置去格式化"><a href="# 通过配置去格式化" class="headerlink" title="通过配置去格式化"></a>通过配置去格式化 </h3><p> 在 ~/.vimrc 中加入以下配置</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">set tabstop=4&#10;set shiftwidth=4&#10;set expandtab</span><br></pre></td></tr></table></figure>
<p>打开要格式化的文件,运行 <code>:retab</code> 即可格式化。</p>
<p>或者一行搞定:<code>:set tabstop=4 shiftwidth=4 expandtab | :retab</code></p>
<p>这种配置方式对所有的文件都生效,如果需要添加例外,添加下面的配置即可 <code>autocmd FileType make setlocal noexpandtab</code> FileType 后接文件类型名称。</p>
<p>这种方式是通过配置的方式来格式化文本,定制性比较高。</p>
<h3 id="通过正则替换"><a href="# 通过正则替换" class="headerlink" title="通过正则替换"></a>通过正则替换 </h3><p> 打开文件,直接运行下面的命令即可:<code>:%s/\t/ /g</code> 。</p>
<h2 id="通过 -expand"><a href="# 通过 -expand" class="headerlink" title="通过 expand"></a>通过 expand</h2><p>由于 expand 是通过读取文件或标准输入并重定向至标准输出,故要达到更改的效果,需重新写入原文件。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">expand -t <span class="number">4</span> input &gt; output <span class="comment"># 间隔默认为 8</span></span><br></pre></td></tr></table></figure>
<h2 id="通过 -col"><a href="# 通过 -col" class="headerlink" title="通过 col"></a>通过 col</h2><p>原理同 expand :</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">cat -vt tab_file | col -x &gt; output</span><br></pre></td></tr></table></figure>
<h2 id="通过 -sed"><a href="# 通过 -sed" class="headerlink" title="通过 sed"></a>通过 sed</h2><p>大杀器登场,通过 sed 来处理文本替换这种事情无疑是科学有效又简洁的姿势。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">sed -iE <span class="string">'s/\t/ /g'</span> file <span class="comment"># 从头换到底</span></span><br></pre></td></tr></table></figure>
<hr>
<p>不过在我看来,为什么这么做,比怎么做到更有价值,网上的讨论还是很多的,在我看来最主要的是习惯问题,以及涉及到团队合作时,统一格式及风格,再就是涉及到文件交换及接口约定时,使用符合特定领域约定俗成的规范。基本就是这样的原则。</p>
<p>看看西乔给的问卷及统计结果 <a href="http://mp.weixin.qq.com/s?__biz=MzAxMzMxNDIyOA==&amp;mid=216031001&amp;idx=2&amp;sn=977df68e90ebce478fe0c14ffc33530b&amp;scene=1&amp;srcid=08209I9tmAKiZMwzhAfEIwUx#rd" target="_blank" rel="external">缩进圣战的统计结果</a> 。</p>
</content>
<summary type="html">
<p>tab 与 space 孰好孰坏这个问题是程序界的圣战之一。StackExchange 旗下的 programmers 就有关于这个问题的 <a href="http://programmers.stackexchange.com/questions/57/tabs-ver
</summary>
<category term="bash" scheme="http://blog.jayxhj.com/tags/bash/"/>
<category term="tab" scheme="http://blog.jayxhj.com/tags/tab/"/>
<category term="vim" scheme="http://blog.jayxhj.com/tags/vim/"/>
</entry>
<entry>
<title>男人袜布洛克牛皮鞋,买买买</title>
<link href="http://blog.jayxhj.com/2016/08/brogue-of-nanrenwa/"/>
<id>http://blog.jayxhj.com/2016/08/brogue-of-nanrenwa/</id>
<published>2016-08-10T14:47:58.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p> 我喜欢 <a href="http://t.cn/RZ9OcKw" target="_blank" rel="external"> 男人袜 </a> 的产品,他们给我带来了惊喜和良好的体验,以及不错的产品。我会向我的朋友们推荐它。</p>
<a id="more"></a>
<p> 这次参加了男人袜官网社区的活动,活动是发评论提建议得奖品。其中的奖品就是男人袜皮鞋。自我感觉还是对男人袜了解的,至少在产品上,是一直在使用,男人袜,随心裁,男人内,船袜,指甲刀,总体来说很满意。</p>
<p> 最初邂逅男人袜是一个一分钱买袜子的活动,不记得是从哪里知道的了,买了之后感觉袜子穿着舒服,有质感,无 logo ,而且穿着之后是感觉不到袜子的存在的。基本上做到了一个袜子该有的样子。再后来知道了男人袜推出的以订杂志的方式订购袜子的方式,简单说就是一次付款,剩下的时间,定期寄送袜子。恰逢老爸生日,就送了这样一份礼物,买了包年服务。再到后来,春节时候推出了「有钱任性套装」,24 双袜子,分八次寄送,彻底解决了袜子不够的问题。</p>
<p> 我之所以愿意在男人袜持续购买产品,在于看到了他们对产品的持续优化,对产品质量的基本把控,这是他们产品的立身之本,在我看来,他们做的很优秀,价格不贵,又持续提高,是真正的不错的产品。从另一方面,不论是营销活动,还是他们传达出的理念,让人觉得,这是个真正看重客户,愿意提升自己的产品,提升大家对产品的体验,提升服务的质量的团队。</p>
<p> 有这样一件事,让我觉得是真正感受到了温暖。之前买三角内裤,到货后发现小了,想退货,跟客服微信联系后,客服说直接给我寄送过来,旧的不用退了,因为其他的客人如果收到别人穿过的,明显不好。是的,我写的是,客人。因为,我真正觉得自己是个客人,受到了主人(男人袜)的照顾与款待。人们喜欢用羊毛出在羊身上这句话来形容商家的别有用心与唯利是图,但我想说,羊毛虽然出在羊身上,但商品除了是商品,还包含附带的服务,男人袜不仅注重服务,而且能把在你购买前后,产品使用,事后持续输出服务,做成闭环,并且能在这个过程中持续升级,这是我非常欣赏的点。</p>
<p> 回到男人袜的产品,不论是袜子,还是男人内,都是质量上佳。袜子的组成成分 「60% 竹纤维 抗菌防臭 / 15% 精梳棉 柔软加分 20% 聚酯纤维 耐磨耐穿 / 5% 氨纶纤维 弹性柔韧」,很明显,这是经过计算和平衡的。上脚体验很舒服,不松不紧恰好能包住脚,流汗后能吸汗不致脚湿。男人内是后来推出的产品,这款产品同样延续了男人袜的风格,不花哨,简简单单,手感好贴合身体,流汗后也不会产生沾粘感。</p>
<p> 男人袜给人最大的感受是,能持续推出活动,能立足产品玩出新花样,不仅有贴合潮流的活动,还能持续产生新的产品,还能跨界与其他商家为用户带来好的产品。不过最大的功劳,还是做好了自己的产品,基于微信的营销以及独特的购买模式,让这家公司活了下来,能继续做好产品。重要的其实一直是:活下来,做下去。</p>
<p> 下面放图,体会下男人袜最新推出的皮鞋,我这款是布洛克皮鞋,上脚感觉像踩在 💩 上面,橡胶打底,莱卡皮筋,很舒适。</p>
<p> 难的不是拍图,也不是排版,难的是给图片起名字,卒。</p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/goal.jpg" alt="男人袜使命"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/nanrenwa.jpg" alt="男人袜,你的生活助理"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/silk-packing.jpg" alt="丝绸包裹袋"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/shoe.jpg" alt="鞋"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/front-side.jpg" alt="鞋前部"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/side-inboard.jpg" alt="侧边"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/heel.jpg" alt="后跟"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/back-sole.jpg" alt="后脚掌"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/forward-sole.jpg" alt="前脚掌"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/lycra-elastic.jpg" alt="莱卡皮筋"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/inside.jpg" alt="内里"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/hogskin-insole.jpg" alt="前脚掌鞋垫,猪皮内里"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/footgear.jpg" alt="鞋撑"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/maintain-suit-all.jpg" alt="保养护理套装"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/maintain-suit.jpg" alt="套装坦荡荡"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/pandora.jpg" alt="潘多拉魔盒"></p>
<p><img src="//blog.jayxhj.com/images/nanrenwa/glasses.jpg" alt="男人袜愿景"></p>
<p> 访问 <a href="http://t.cn/RZ9OcKw" target="_blank" rel="external"> 男人袜官网 </a>,体验好产品。</p>
</content>
<summary type="html">
<p>我喜欢 <a href="http://t.cn/RZ9OcKw">男人袜</a> 的产品,他们给我带来了惊喜和良好的体验,以及不错的产品。我会向我的朋友们推荐它。</p>
</summary>
<category term="life" scheme="http://blog.jayxhj.com/categories/life/"/>
<category term="brogue" scheme="http://blog.jayxhj.com/tags/brogue/"/>
<category term="nanrenwa" scheme="http://blog.jayxhj.com/tags/nanrenwa/"/>
</entry>
<entry>
<title>在 PHPStorm 中配置 Xdebug</title>
<link href="http://blog.jayxhj.com/2016/07/configure-xdebug-in-phpstorm/"/>
<id>http://blog.jayxhj.com/2016/07/configure-xdebug-in-phpstorm/</id>
<published>2016-07-01T08:04:05.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>在 <a href="/2016/01/using-json-viewer-for-debugging/">使用 Chrome 扩展程序 JSON Viewer 进行调试</a> 这片文章中我曾介绍过,使用 <code>echo json_encode()</code> 的方式进行调试,再配合 Chrome 浏览器插件 <del>JSON Viewer</del> (建议使用 JSON Handler 代替)。</p>
<h2 id="调试方式的对比"><a href="# 调试方式的对比" class="headerlink" title="调试方式的对比"></a>调试方式的对比 </h2><p> 手动调试的方式实际上局限性很大,缺点很明显:</p>
<ol>
<li>复杂的程序变量的中间状态无法跟踪,只能得到最终结果</li>
<li>要 debug 的变量或者对象较多时不方便打印</li>
<li>调试代码与程序代码混杂在一起,容易出错,而且来回切换成本还挺高,效率上就低多了</li>
</ol>
<p>上面的缺点中,</p>
<p>1 可以通过 Xdebug 的单步调试解决,通过打断点,一步步追踪,可以深入某个 function 或者一步步执行,来查看变量的整个状态变化。</p>
<p>2 这个缺点可以通过 <code>error_log()</code> 函数写入文件中,再配合 <code>tail -f log_file</code> 来调试,当然在 Xdebug 中也能一步步查看变量状态。</p>
<p>3 也是我决定用 Xdebug 的原因,打断点很方便管理,不用的断点暂时反选,这样可以在需要启用时启用,而且无需写调试代码,这种调试方式是非侵入式的。</p>
<h2 id="开发中的痛点及思考"><a href="# 开发中的痛点及思考" class="headerlink" title="开发中的痛点及思考"></a>开发中的痛点及思考 </h2><p> 以上是我在开发过程中遇到的痛点,而解决方法很简单:</p>
<ol>
<li>类与类之间低耦合,这样可以将 bug 缩小范围,且代码也更健壮,方便后续修改</li>
<li><a href="https://en.wikipedia.org/wiki/SOLID_(object-oriented_design" target="_blank" rel="external">SOLID 原则</a></li>
<li>引入单元测试覆盖大部分的功能及类的测试</li>
</ol>
<p>其中 2 是需要在类的设计上下功夫的,需要长时间的代码编写与逐步改善,1 则可以使用设计模式来解决,3 则可以让你将注意力放在各个功能的衔接点上,通过上面的三个方法,基本能解决平常的小 bug 了。</p>
<p>下面来介绍 debug 工具 Xdebug ,并介绍如何在 PHPStorm 中配置使用。</p>
<h2 id="Xdebug- 的安装与配置"><a href="#Xdebug- 的安装与配置" class="headerlink" title="Xdebug 的安装与配置"></a>Xdebug 的安装与配置</h2><p>Xdebug 有很多安装的方式,这个页面 <a href="https://xdebug.org/docs/install" target="_blank" rel="external">https://xdebug.org/docs/install</a> 给出了常用的安装方式。 另外有个更好的页面,给出了一步步的步骤,<a href="https://xdebug.org/wizard.php" target="_blank" rel="external">https://xdebug.org/wizard.php</a> ,会告诉你如何编译安装指定的版本。</p>
<p>安装步骤如下:</p>
<ol>
<li>下载对应的 Xdebug 压缩包;</li>
<li><p>编译</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /path/to/xdebug</span><br><span class="line">phpize</span><br><span class="line">./configure</span><br><span class="line">make</span><br></pre></td></tr></table></figure>
</li>
<li><p>复制 so 文件到扩展目录</p>
</li>
<li><p>修改 php.ini 加上以下配置</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br></pre></td><td class="code"><pre><span class="line">[Xdebug]</span><br><span class="line">zend_extension = /path/to/extensions/xdebug.so</span><br><span class="line">xdebug.remote_enable=1</span><br><span class="line">xdebug.profiler_enable=1</span><br><span class="line">xdebug.remote_port=9000</span><br><span class="line">xdebug.profiler_output_dir= /tmp/xdebug</span><br></pre></td></tr></table></figure>
</li>
</ol>
<blockquote>
<p>phpize 命令是用来准备 PHP 扩展库的编译环境的。</p>
</blockquote>
<p>使用 <code>php -m</code> 查看扩展是否加载,再重启 php-fpm 。</p>
<p>下面介绍如何在 PHPStorm 中集成 Xdebug 。</p>
<h2 id="PHPStorm- 里配置 -Xdebug"><a href="#PHPStorm- 里配置 -Xdebug" class="headerlink" title="PHPStorm 里配置 Xdebug"></a>PHPStorm 里配置 Xdebug</h2><p>PHPStorm 文档里介绍了如何配置 Xdebug <a href="https://www.jetbrains.com/help/phpstorm/10.0/configuring-xdebug.html" target="_blank" rel="external">https://www.jetbrains.com/help/phpstorm/10.0/configuring-xdebug.html</a></p>
<p>介绍下几个主要的步骤:</p>
<ol>
<li>设置 PHP 解释器。位于 Preferences &gt; Languages &amp; Frameworks &gt; PHP 右侧的 Interpreter ,点击 … 按钮,设置本地的 PHP 环境</li>
<li><p>配置 Xdebug 的行为。</p>
<ol>
<li>配置 <strong>Debug Port</strong> ,让其与 php.ini 中的 <code>xdebug.remote_port</code>端口号相同。</li>
<li>接收 Xdebug 与 PHPStorm 的连接,<strong>Can accept external connections</strong></li>
</ol>
</li>
<li><p>监听外部的 debug 连接,开启这个选项 <strong>Run | Start Listening for PHP debug connections</strong></p>
</li>
</ol>
<p>通过上面的配置,就可以在 PHPStorm 运行 Xdebug 了。不过这还不够,需要配置具体的项目,这样 Xdebug 才知道如何运行,并在指定的断点处停止并通知 PHPStorm 。</p>
<h2 id="配置实例运行具体的 -debug- 项目"><a href="# 配置实例运行具体的 -debug- 项目" class="headerlink" title="配置实例运行具体的 debug 项目"></a>配置实例运行具体的 debug 项目 </h2><p> 下图中显示了如何配置某个 debug 项目。</p>
<p><img src="//blog.jayxhj.com/images/add-debug-configuration.png" alt="debug configuration"></p>
<p>选择 <strong>PHP Web Application</strong> 即可配置某个 web 项目,设置项目的 url 及当前配置的名称即可。</p>
<p>以上这些步骤走完,一个 web 项目的 Xdebug 配置就做好了,只需设置断点,再点击 Run -&gt; Debug ‘你的 debug 项目名称’ ,即可开始 debug 之旅了。</p>
<p>Enjoy it.</p>
</content>
<summary type="html">
<p>在 <a href="/2016/01/using-json-viewer-for-debugging/">使用 Chrome 扩展程序 JSON Viewer 进行调试</a> 这片文章中我曾介绍过,使用 <code>echo json_encode()</code> 的
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="PHP" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="Xdebug" scheme="http://blog.jayxhj.com/tags/xdebug/"/>
</entry>
<entry>
<title>PHP 发起 curl POST 请求时传递数组</title>
<link href="http://blog.jayxhj.com/2016/06/post-array-data-in-php-curl/"/>
<id>http://blog.jayxhj.com/2016/06/post-array-data-in-php-curl/</id>
<published>2016-06-03T02:42:03.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p> 使用 PHP 的 curl 可以发起 HTTP 外部请求,但是发起 POST 请求时,是无法直接传递数组的,从 curl 层面来说,也没有所谓的数组的概念,而更加通用也更合理的传递数据的格式其实是键值对(key value pair)。</p>
<p> 那么,我们先得知道,为什么要传递数组。</p>
<p>HTTP 协议规定了 HTTP 请求的三个部分:状态行、请求头、消息主体。消息主体实际上是没有规定格式的。平常主要用到的几个请求头 Content-Type 为 </p>
<figure class="highlight markdown"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">application/x-www-form-urlencoded</span><br><span class="line">multipart/form-data</span><br><span class="line">application/json</span><br><span class="line">text/xml</span><br></pre></td></tr></table></figure>
<p> 所以问题的答案很明白了,传递什么样的数据类型得看需要发送什么样的请求。</p>
<p> 一个典型的 curl POST 请求是下面这样:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">curl -X POST --data <span class="string">'params[]=check1&amp;params[]=check2'</span> <span class="string">'http://jayxhj.com/test/curl.php'</span></span><br></pre></td></tr></table></figure>
<p> 上面的请求将发送一个 Content-Type 为 application/x-www-form-urlencoded 的请求,请求的 body 为 <code>params[]=check1&amp;params[]=check2</code> ,在服务端,只需使用 <code>$_POST</code> 即可获取。</p>
<p> 那么回到 curl ,我们只需设置 option 为 <strong>CURLOPT_POSTFIELDS</strong> 的 VALUE 为 key/value pair ,即可将数组以字符串的形式传递至服务端,并直接由 <code>$_POST</code> 获取。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="variable">$curl</span> = curl_init();</span><br><span class="line"></span><br><span class="line">curl_setopt(<span class="variable">$curl</span>, CURLOPT_URL, <span class="string">'http://jayxhj.com/test/curl.php'</span>);</span><br><span class="line">curl_setopt(<span class="variable">$curl</span>, CURLOPT_HEADER, <span class="number">0</span>);</span><br><span class="line">curl_setopt(<span class="variable">$curl</span>, CURLOPT_RETURNTRANSFER, <span class="number">1</span>);</span><br><span class="line"></span><br><span class="line"><span class="variable">$array</span> = [</span><br><span class="line"> <span class="string">'jayxhj'</span>,</span><br><span class="line"> <span class="string">'pt'</span></span><br><span class="line">];</span><br><span class="line"><span class="variable">$str</span> = http_build_query(<span class="variable">$array</span>);</span><br><span class="line"></span><br><span class="line">curl_setopt(<span class="variable">$curl</span>, CURLOPT_POSTFIELDS, <span class="variable">$str</span>);</span><br><span class="line"><span class="variable">$data</span> = curl_exec(<span class="variable">$curl</span>);</span><br><span class="line"></span><br><span class="line">curl_close(<span class="variable">$curl</span>);</span><br><span class="line"></span><br><span class="line">var_dump(<span class="variable">$data</span>);</span><br></pre></td></tr></table></figure>
</content>
<summary type="html">
<p> 使用 PHP 的 curl 可以发起 HTTP 外部请求,但是发起 POST 请求时,是无法直接传递数组的,从 curl 层面来说,也没有所谓的数组的概念,而更加通用也更合理的传递数据的格式其实是键值对(key value pair)。</p>
<p> 那么,我们先得知道
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="curl" scheme="http://blog.jayxhj.com/tags/curl/"/>
<category term="php" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="tips" scheme="http://blog.jayxhj.com/tags/tips/"/>
</entry>
<entry>
<title>常用 Chrome 扩展介绍及使用</title>
<link href="http://blog.jayxhj.com/2016/05/jayxhjs-chrome-extensions/"/>
<id>http://blog.jayxhj.com/2016/05/jayxhjs-chrome-extensions/</id>
<published>2016-05-26T08:27:35.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>Chrome 浏览器和 Firefox 浏览器为何大受欢迎,其中一个重要原因就是因为有数量巨大的扩展库,而 360 之类的浏览器内核也是基于 Chromium 内核和 IE 内核,因为这些扩展,可以让应用(浏览器就是应用)的功能变得无比丰富和强大。</p>
<p>这篇文章会列举本人平常使用的插件,并介绍常用的用法,但由于扩展使用的主观性太强,每个人都有不同的偏好,所以此篇会把重点放在扩展的使用上,并会不定时更新。扩展排名不分先后。</p>
<p>每个扩展介绍会包含其简要介绍,下载地址及基本用法,默认给出的是 Chrome webstore 的下载地址,如果有托管在 GitHub 会给出其托管地址。</p>
<h2 id="Avatars-for-Github"><a href="#Avatars-for-Github" class="headerlink" title="Avatars for Github"></a>Avatars for Github</h2><p>一句话介绍:在 GitHub feed 页展示你关注人的头像。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/avatars-for-github/pgjmdbklnfklcjfbonjfkdhaonlfogbb" target="_blank" rel="external">https://chrome.google.com/webstore/detail/avatars-for-github/pgjmdbklnfklcjfbonjfkdhaonlfogbb</a></p>
<p>开发者网站:<a href="https://github.com/anasnakawa/chrome-github-avatars" target="_blank" rel="external">https://github.com/anasnakawa/chrome-github-avatars</a></p>
<p><img src="//blog.jayxhj.com/images/GitHub-feed-before.png" alt="GitHub-feed-before"></p>
<p><img src="//blog.jayxhj.com/images/GitHub-feed-before.png" alt="GitHub-feed-after"></p>
<h2 id="Axure-RP-Extension-for-Chrome"><a href="#Axure-RP-Extension-for-Chrome" class="headerlink" title="Axure RP Extension for Chrome"></a>Axure RP Extension for Chrome</h2><p>一句话介绍:web 版的 Axure 客户端。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/dogkpdfcklifaemcdfbildhcofnopogp" target="_blank" rel="external">https://chrome.google.com/webstore/detail/dogkpdfcklifaemcdfbildhcofnopogp</a></p>
<p>开发者网站:<a href="http://www.axure.com/" target="_blank" rel="external">http://www.axure.com/</a></p>
<p>使用:安装此插件后,即可在不安装 Axure 下的情况下,打开原型,直接在浏览器中运行。</p>
<h2 id="Chrome-Sniffer-Plus"><a href="#Chrome-Sniffer-Plus" class="headerlink" title="Chrome Sniffer Plus"></a>Chrome Sniffer Plus</h2><p>一句话介绍:网页下的类库探测器。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/fhhdlnnepfjhlhilgmeepgkhjmhhhjkh" target="_blank" rel="external">https://chrome.google.com/webstore/detail/fhhdlnnepfjhlhilgmeepgkhjmhhhjkh</a></p>
<p>开发者网站:<a href="http://justjavac.com/" target="_blank" rel="external">http://justjavac.com/</a></p>
<p>使用:探测当前网页正在使用的开源软件或者 JS 类库。其实一个网站或者应用,技术栈一般会涉及到很多的技术,这个扩展可以检测一些基本的开源类库,作为日常的探测够用了。</p>
<h2 id="hosts 文件管理工具"><a href="#hosts 文件管理工具" class="headerlink" title="hosts 文件管理工具"></a>hosts 文件管理工具 </h2><p> 一句话介绍:host 管理及切换工具。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/kpfmckjjpabojdhlncnccfhkfhbmnjfi" target="_blank" rel="external">https://chrome.google.com/webstore/detail/kpfmckjjpabojdhlncnccfhkfhbmnjfi</a></p>
<p>开发者网站:<a href="https://github.com/gbk/chrome-hosts-manager" target="_blank" rel="external">https://github.com/gbk/chrome-hosts-manager</a></p>
<p>使用:主要用来进行 ip 与 host 之间的绑定,另外可以对域名 ip 进行分组,非常适合用来进行切换环境。另外一个主要功能就是显示当前 ip ,在没有挂代理的情况下,可以显示当前页面的服务器 ip 。</p>
<p><img src="//blog.jayxhj.com/images/chrome-hosts-manager.jpg" alt="显示当前页面 ip"></p>
<h2 id="JSON-Editor"><a href="#JSON-Editor" class="headerlink" title="JSON Editor"></a>JSON Editor</h2><p>一句话介绍:JSON 在线解析及编辑器。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/lhkmoheomjbkfloacpgllgjcamhihfaj" target="_blank" rel="external">https://chrome.google.com/webstore/detail/lhkmoheomjbkfloacpgllgjcamhihfaj</a></p>
<p>开发者网站:<a href="http://jsoneditoronline.org/" target="_blank" rel="external">http://jsoneditoronline.org/</a></p>
<p>使用:<a href="http://jsoneditoronline.org/" target="_blank" rel="external">http://jsoneditoronline.org/</a> 的 chrome 扩展版本,可以用来修改 JSON 数据,并实时验证 JSON 数据的合法性,构造复杂的 JSON 数组、对象、字符串。</p>
<h2 id="JSONView"><a href="#JSONView" class="headerlink" title="JSONView"></a>JSONView</h2><p>一句话介绍:JSON 查看器。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc" target="_blank" rel="external">https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc</a></p>
<p>开发者网站:<a href="https://github.com/gildas-lormeau/JSONView-for-Chrome" target="_blank" rel="external">https://github.com/gildas-lormeau/JSONView-for-Chrome</a></p>
<p>使用:适合用来查看 JSON ,但是由于在解析当前页面时,如果发现是 JSON ,会再次请求一次页面,所以不适合查看、测试对多次请求结果不一致的 api 。之前写过一篇文章,介绍的就是 <a href="/2016/01/using-json-viewer-for-debugging/">使用 JSONView 配合进行 Debug</a> 。</p>
<h2 id="OneTab"><a href="#OneTab" class="headerlink" title="OneTab"></a>OneTab</h2><p>一句话介绍:可以节省 95% 内存使用的标签页管理工具。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/chphlpgkkbolifaimnlloiipkdnihall" target="_blank" rel="external">https://chrome.google.com/webstore/detail/chphlpgkkbolifaimnlloiipkdnihall</a></p>
<p>开发者网站:<a href="http://one-tab.com/" target="_blank" rel="external">http://one-tab.com/</a></p>
<p>用法:使用 OneTab 可以将暂时不需要处理的网页集中到一起,大大减小 Chrome 吃内存的问题。使用它可以对网页进行分组,可以随意更改标签页顺序,能分享自己的标签页给他人,并且让人放心的是,即使关闭浏览器,或者重启电脑, OneTab 里的内容还在,可以放心地使用,开发团队表示,后续将引入云端功能,保持多设备的同步,大快人心。使用 <code>Alt + Shift + 1</code> 即可呼出 OneTab 。</p>
<h2 id="Proxy-SwitchyOmega"><a href="#Proxy-SwitchyOmega" class="headerlink" title="Proxy SwitchyOmega"></a>Proxy SwitchyOmega</h2><p>一句话介绍:代理管理及切换工具。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/padekgcemlokbadohgkifijomclgjgif" target="_blank" rel="external">https://chrome.google.com/webstore/detail/padekgcemlokbadohgkifijomclgjgif</a></p>
<p>开发者网站:<a href="https://github.com/FelisCatus/SwitchyOmega" target="_blank" rel="external">https://github.com/FelisCatus/SwitchyOmega</a></p>
<h2 id="Search-by-Image-by-Google"><a href="#Search-by-Image-by-Google" class="headerlink" title="Search by Image (by Google)"></a>Search by Image (by Google)</h2><p>一句话介绍:Google 搜图工具。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/search-by-image-by-google/dajedkncpodkggklbegccjpmnglmnflm" target="_blank" rel="external">https://chrome.google.com/webstore/detail/search-by-image-by-google/dajedkncpodkggklbegccjpmnglmnflm</a></p>
<p>开发者网站:<a href="https://chrome.google.com/webstore/category/ext/15-by-google" target="_blank" rel="external">https://chrome.google.com/webstore/category/ext/15-by-google</a></p>
<p>使用:选中某个图片,右键,点击 <strong>Search Google with this image</strong>,即可搜索与此图类似的图片。其实就是调用的 Google Images 的服务。国内百度的 <a href="http://image.baidu.com/" target="_blank" rel="external">以图搜图</a> 做的也挺不错。</p>
<h2 id="Wikiwand-Wikipedia-Modernized"><a href="#Wikiwand-Wikipedia-Modernized" class="headerlink" title="Wikiwand: Wikipedia Modernized"></a>Wikiwand: Wikipedia Modernized</h2><p>一句话介绍:更美的维基百科。</p>
<p>地址:<a href="http://www.wikiwand.com/#/install" target="_blank" rel="external">http://www.wikiwand.com/#/install</a></p>
<p>开发者网站:<a href="http://www.wikiwand.com/" target="_blank" rel="external">http://www.wikiwand.com/</a></p>
<h2 id="印象笔记·剪藏"><a href="# 印象笔记·剪藏" class="headerlink" title="印象笔记·剪藏"></a>印象笔记·剪藏 </h2><p> 一句话介绍: 智能归类、剪辑、收藏、悦读并提供关键字搜索功能的印象笔记工具。</p>
<p>地址:<a href="https://chrome.google.com/webstore/detail/evernote-web-clipper/pioclpoplcdbaefihamjohnefbikjilc" target="_blank" rel="external">https://chrome.google.com/webstore/detail/evernote-web-clipper/pioclpoplcdbaefihamjohnefbikjilc</a></p>
<p>开发者网站:<a href="http://evernote.com/" target="_blank" rel="external">http://evernote.com/</a></p>
</content>
<summary type="html">
<p>Chrome 浏览器和 Firefox 浏览器为何大受欢迎,其中一个重要原因就是因为有数量巨大的扩展库,而 360 之类的浏览器内核也是基于 Chromium 内核和 IE 内核,因为这些扩展,可以让应用(浏览器就是应用)的功能变得无比丰富和强大。</p>
<p>这篇文章会
</summary>
<category term="chrome" scheme="http://blog.jayxhj.com/tags/chrome/"/>
<category term="extensions" scheme="http://blog.jayxhj.com/tags/extensions/"/>
</entry>
<entry>
<title>动手开发自己的第一个 composer 包</title>
<link href="http://blog.jayxhj.com/2016/05/basic-composer-package-development/"/>
<id>http://blog.jayxhj.com/2016/05/basic-composer-package-development/</id>
<published>2016-05-13T07:28:41.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>composer 是 PHP 的依赖管理工具,本篇文章就来说明如何构建一个包,并提交到 <a href="https://packagist.org/" target="_blank" rel="external">Packagist</a> ,这样别人就可以方便地通过 composer 使用你的包了。</p>
<p>开发 composer 包有以下几个步骤:</p>
<ol>
<li>初始化 composer.json 文件</li>
<li>定义命名空间及包名</li>
<li>实现包需要实现的功能</li>
<li>提交到 GitHub</li>
<li>在 Packagist 注册包</li>
</ol>
<h2 id="初始化 -composer-json- 文件"><a href="# 初始化 -composer-json- 文件" class="headerlink" title="初始化 composer.json 文件"></a>初始化 composer.json 文件 </h2><p> 安装好 composer 后即可在本地运行 <code>composer init</code> 通过交互式命令行设置 composer.json 。</p>
<p>下面介绍其中的几个属性,以及常规的设置:</p>
<ol>
<li>name<br><br>此属性定义包名,以 <code>/</code> 隔开,前面的为供应商名字,后面为包名,供应商代表 Packagist 网站为开发者提供的唯一的名字,用来组织包以及防止命名冲突。所以提交时最好先访问 <a href="https://packagist.org/packages/yourvendorname" target="_blank" rel="external">https://packagist.org/packages/yourvendorname</a> 将 url 中的 yourvendorname 替换为你想要取的名字,如果页面没有 404 ,说明已经被注册了。</li>
<li>license<br><br>许可证。关于许可证,建议看两篇文章,<a href="http://choosealicense.com/" target="_blank" rel="external">开源项目 license 介绍 </a> 、<a href="http://www.ruanyifeng.com/blog/2011/05/how_to_choose_free_software_licenses.html" target="_blank" rel="external"> 如何选择 license</a></li>
<li>require<br><br>安装当前包所需的依赖。只有所有依赖被安装当前包才会被安装。</li>
<li>autoload<br><br>此配置下主要是 <strong>PSR-4</strong> 或者 <strong>PSR-0</strong> 设置,更推荐使用 PSR-4 标准。</li>
</ol>
<p><a href="http://json-schema.org/" target="_blank" rel="external">http://json-schema.org/</a> 上介绍了 JSON Schema 的定义以及各个语言对其各种功能的实现,有 validator 的实现,其中 <a href="http://www.jsonschemavalidator.net/" target="_blank" rel="external">JSON Schema Validator</a> 是在线的验证服务。其实最简单的就是使用 <code>composer validate composer.json</code> 来验证文件是否是有错误。</p>
<p>这是我演示的设置 composer.json 的视频</p>
<script type="text/javascript" src="https://asciinema.org/a/45460.js" id="asciicast-45460" async>
</script>
<h2 id="项目结构"><a href="# 项目结构" class="headerlink" title="项目结构"></a>项目结构 </h2><p> 以我开发的 <a href="https://packagist.org/packages/geosso/geosso" target="_blank" rel="external">单点登录 SDK</a> 为例,此项目基于 Laravel ,实现了站点接入单点登录系统的简单接入,应用只需在服务端注册并实现指定接口,即可接入 SSO 。</p>
<p>项目结构是典型的 MVC 结构,</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">.&#10;&#9492;&#9472;&#9472; geo&#10; &#9492;&#9472;&#9472; geosso&#10; &#9500;&#9472;&#9472; LICENSE&#10; &#9500;&#9472;&#9472; README.md&#10; &#9500;&#9472;&#9472; composer.json&#10; &#9492;&#9472;&#9472; src&#10; &#9500;&#9472;&#9472; Contracts&#10; &#9500;&#9472;&#9472; Http&#10; &#9474; &#9500;&#9472;&#9472; Controllers&#10; &#9474; &#9500;&#9472;&#9472; Middleware&#10; &#9474; &#9492;&#9472;&#9472; Requests&#10; &#9500;&#9472;&#9472; ParamsBean&#10; &#9500;&#9472;&#9472; Providers&#10; &#9500;&#9472;&#9472; Support&#10; &#9492;&#9472;&#9472; config&#10;&#10;12 directories</span><br></pre></td></tr></table></figure>
<p>LICENSE、README.md 及 composer.json 是运行 <code>tree -d</code> 之后手工添加上去的。</p>
<p>项目根目录定义在 src 下,在 composer.json 中也有定义,这样当 composer 加载这个包时就知道如何通过命名空间去解析文件路径。</p>
<p>Http 目录代表请求响应,之下的 Controllers 表示合法请求的控制器,Middleware 代表请求的第一道关卡,通过中间件去拦截请求,Requests 去获取前端请求并对请求过滤。</p>
<p>Contracts 代表接口定义。ParamsBean 代表应用层与底层服务沟通时的参数封装,通过 Bean 去获取各个参数,而不是传递 array 使得调用一致,并且强制接口调用时做类型检测,可以很大程度上统一各层之间的参数传递。</p>
<p>Providers 代表 Laravel 的服务容器,通过服务容器,可以注册路由与配置,加载助手类,绑定接口与其实现。</p>
<p>Support 就是一些助手类,对常用的与逻辑无关的功能的封装,config 代表应用自己的配置,通过 config 可以方便地将配置设置并使用全局函数 <code>config()</code> 调用。</p>
<h2 id="提交至 -GitHub"><a href="# 提交至 -GitHub" class="headerlink" title="提交至 GitHub"></a>提交至 GitHub</h2><p>按照前面的步骤,一个包就有了基本的骨架,接下来就是上传至 GitHub ,配置项目,集成持续集成服务,发布开源项目许可证。</p>
<p>GitHub 初始化项目时可以选择生成 .gitignore 文件,选择许可证,初始化 README.md 文件,切换至本地的项目目录后,按如下步骤即可将目录上传至 GitHub:</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">&gt;git init <span class="comment"># 初始化仓库</span></span><br><span class="line">&gt;git remote <span class="built_in">set</span>-url origin --push --add git@github.com:jayxhj/geosso.git <span class="comment"># 添加远程追踪仓库地址</span></span><br><span class="line">&gt; git add .</span><br><span class="line">&gt; git commit</span><br><span class="line">&gt; git push origin master</span><br></pre></td></tr></table></figure>
<h2 id="提交至 -Packagist"><a href="# 提交至 -Packagist" class="headerlink" title="提交至 Packagist"></a>提交至 Packagist</h2><p>Packagist 为 composer 默认获取包元数据信息的地址,从 Packagist 获取到元数据信息后,再从 GitHub 上拉取代码。因此,当把你开发的包上传至 GitHub 后还需要将其在 Packagist 注册,这样全世界的人都能通过 composer 去拉去你的代码了。</p>
<p>提交至 Packagist 只需三个步骤:</p>
<ol>
<li>注册帐号</li>
<li>在 <a href="https://packagist.org/packages/submit" target="_blank" rel="external">https://packagist.org/packages/submit</a> 提交开发包</li>
<li>设置 webhook 以便提交包更新后能及时地同步至 Packagist</li>
</ol>
<p>自此,一个基本的包开发就结束了。通过 composer 来管理 PHP 的依赖,通过编写 composer package 去扩展自己的类库,通过引入其他的类库来填充自己的功能,就不用重复造轮子了。</p>
</content>
<summary type="html">
<p>composer 是 PHP 的依赖管理工具,本篇文章就来说明如何构建一个包,并提交到 <a href="https://packagist.org/" target="_blank" rel="external">Packagist</a> ,这样别人就可以方便地通过 c
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="PHP" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="composer" scheme="http://blog.jayxhj.com/tags/composer/"/>
</entry>
<entry>
<title>获取当前日期在本月的第几周</title>
<link href="http://blog.jayxhj.com/2016/04/get-week-number-in-month-from-date-in-php/"/>
<id>http://blog.jayxhj.com/2016/04/get-week-number-in-month-from-date-in-php/</id>
<published>2016-04-26T11:44:42.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><h1 id="获取当前日期在本月的第几周"><a href="# 获取当前日期在本月的第几周" class="headerlink" title="获取当前日期在本月的第几周"></a>获取当前日期在本月的第几周 </h1><p> 使用 <strong>date()</strong> 函数可以获取某个日期的当前星期数,此星期数为当前日期在一整年中的星期数。假如要获取当前日期在当前月份的星期数,只需用当前日期的星期数减去当前月份第一天的星期数加 1 即可。</p>
<h2 id="获取当前日期的星期数"><a href="# 获取当前日期的星期数" class="headerlink" title="获取当前日期的星期数"></a>获取当前日期的星期数</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="keyword">echo</span> date(<span class="string">'W'</span>,time());</span><br></pre></td></tr></table></figure>
<h2 id="获取当前日期是本月的第几周"><a href="# 获取当前日期是本月的第几周" class="headerlink" title="获取当前日期是本月的第几周"></a>获取当前日期是本月的第几周</h2><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="comment">/**</span><br><span class="line"> * <span class="doctag">@param</span> int $date 时间戳</span><br><span class="line"> * <span class="doctag">@return</span> int 当前日期在本月的第几周</span><br><span class="line"> *</span><br><span class="line"> * */</span></span><br><span class="line"><span class="function"><span class="keyword">function</span> <span class="title">weekOfMonth</span><span class="params">(<span class="variable">$date</span>)</span> </span>&#123;</span><br><span class="line"> <span class="variable">$firstOfMonth</span> = strtotime(date(<span class="string">"Y-m-01"</span>, <span class="variable">$date</span>));</span><br><span class="line"> <span class="keyword">return</span> intval(date(<span class="string">"W"</span>, <span class="variable">$date</span>)) - intval(date(<span class="string">"W"</span>, <span class="variable">$firstOfMonth</span>)) + <span class="number">1</span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</content>
<summary type="html">
<h1 id="获取当前日期在本月的第几周"><a href="# 获取当前日期在本月的第几周" class="headerlink" title="获取当前日期在本月的第几周"></a>获取当前日期在本月的第几周 </h1><p> 使用 <strong>date()</stro
</summary>
<category term="php" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="php" scheme="http://blog.jayxhj.com/tags/php/"/>
</entry>
<entry>
<title>Lumen 常用开发技巧</title>
<link href="http://blog.jayxhj.com/2016/04/lumen-tricks/"/>
<id>http://blog.jayxhj.com/2016/04/lumen-tricks/</id>
<published>2016-04-09T14:09:26.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><h1 id="密码加密与验证"><a href="# 密码加密与验证" class="headerlink" title="密码加密与验证"></a>密码加密与验证 </h1><p> 加密</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="comment">/**</span><br><span class="line">* 密码入库加密</span><br><span class="line">* <span class="doctag">@param</span> string $password</span><br><span class="line">* <span class="doctag">@return</span> string</span><br><span class="line">*</span><br><span class="line">* */</span></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">passwordEncrypt</span><span class="params">(<span class="variable">$password</span>)</span></span><br><span class="line"></span>&#123;</span><br><span class="line"> <span class="keyword">return</span> app(<span class="string">'hash'</span>)-&gt;make(<span class="variable">$password</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>密码验证</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="comment">/**</span><br><span class="line">* 密码验证</span><br><span class="line">* <span class="doctag">@param</span> string $password</span><br><span class="line">* <span class="doctag">@param</span> string $hashedPassword 加密后的密码</span><br><span class="line">* <span class="doctag">@return</span> bool</span><br><span class="line">*</span><br><span class="line">* */</span></span><br><span class="line"><span class="keyword">public</span> <span class="function"><span class="keyword">function</span> <span class="title">passwordValidate</span><span class="params">(<span class="variable">$password</span>, <span class="variable">$hashedPassword</span>)</span></span><br><span class="line"></span>&#123;</span><br><span class="line"> <span class="keyword">return</span> app(<span class="string">'hash'</span>)-&gt;check(<span class="variable">$password</span>, <span class="variable">$hashedPassword</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="查询数据库判断是否有记录"><a href="# 查询数据库判断是否有记录" class="headerlink" title="查询数据库判断是否有记录"></a>查询数据库判断是否有记录 </h2><p> 如果使用 Eloquent 的 <a href="https://laravel.com/docs/5.2/eloquent#query-scopes" target="_blank" rel="external">Query Scopes</a> ,查询时使用链式方法调用,通常是这么查询,<code>$modelObj = Model::id($id)-&gt;get()</code> 来查询指定条件的结果集。但是这么查出来的,实际上返回的是 <code>Illuminate\Support\Collection</code> 对象。那么下面的方法,比较适合判断查出来的结果是否存在。</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="variable">$modelObj</span> = Model::id(<span class="variable">$id</span>)-&gt;get();</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> (! <span class="variable">$modelObj</span>-&gt;isEmpty()) &#123;</span><br><span class="line"> <span class="variable">$modelObj</span>-&gt;toArray();</span><br><span class="line">&#125;</span><br><span class="line"><span class="comment">// 或者这样</span></span><br><span class="line"><span class="keyword">if</span> (<span class="variable">$modelObj</span>-&gt;count()) &#123;</span><br><span class="line"> <span class="variable">$modelObj</span>-&gt;toArray();</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
</content>
<summary type="html">
<h1 id="密码加密与验证"><a href="# 密码加密与验证" class="headerlink" title="密码加密与验证"></a>密码加密与验证 </h1><p> 加密</p>
<figure class="highlight php"><table><tr
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="lumen" scheme="http://blog.jayxhj.com/tags/lumen/"/>
</entry>
<entry>
<title>在 lumen 中设置日期与数据库时区</title>
<link href="http://blog.jayxhj.com/2016/03/setting-timezones-in-lumen/"/>
<id>http://blog.jayxhj.com/2016/03/setting-timezones-in-lumen/</id>
<published>2016-03-24T11:17:10.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>项目使用了 Laravel 的精简版 Lumen 来做 api 和 sso 的服务。今天在提交代码时发现,生成的 database migration 代码时间相差八个小时。看来是时区设置的问题,查找了一下,发现有好几个地方都有时区的设置,那就简单总结一下。</p>
<h2 id="PHP- 时区设置"><a href="#PHP- 时区设置" class="headerlink" title="PHP 时区设置"></a>PHP 时区设置</h2><p>PHP 里可以在 php.ini 中设置 <strong><code>date.timezone</code></strong> 选项来设置时区,也可以在脚本中动态制定时区,如使用 <strong><code>date_default_timezone_set()</code></strong> 来动态指定时区。使用脚本指定时会覆盖 php.ini 中的设置。时区列表可以使用 <strong><code>\DateTimeZone::listIdentifiers()</code></strong> 来获取。也可以到 <a href="http://lxr.php.net/xref/PHP_5_6/ext/date/lib/timezonemap.h" target="_blank" rel="external">timezonemap.h</a> 来查看对应的可选项。</p>
<p>中国的时区设置可以使用以下选项:<br><figure class="highlight c"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Chongqing"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Chungking"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Harbin"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Macao"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Macau"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Shanghai"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"Asia/Taipei"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"PRC"</span> &#125;,</span><br><span class="line">&#123; <span class="string">"cst"</span>, <span class="number">0</span>, <span class="number">28800</span>, <span class="string">"ROC"</span> &#125;,</span><br></pre></td></tr></table></figure></p>
<h2 id="lumen- 时区设置"><a href="#lumen- 时区设置" class="headerlink" title="lumen 时区设置"></a>lumen 时区设置</h2><p>lumen 的时区设置有数据库操作的时区设置以及与使用时间相关的函数的时区设置。</p>
<h3 id="日期相关设置"><a href="# 日期相关设置" class="headerlink" title="日期相关设置"></a>日期相关设置 </h3><p> 在 lumen 中应用程序的入口,会 new 一个 Application 实例,这个实例会读取 env 配置文件中的 <strong>APP_TIMEZONE</strong> 配置,若没有配置则会使用 UTC 时间。Application 位于 <code>/path/to/project/vendor/laravel/lumen-framework/src/Application.php</code> 。所以最好是在 .env 中设置时区 <code>APP_TIMEZONE=PRC</code> 。</p>
<h3 id="数据库"><a href="# 数据库" class="headerlink" title="数据库"></a>数据库 </h3><p> 数据库的时区设置可以在 config 的 database 文件中设置。database 文件分两个,一个是框架级别,一个是项目级别。框架级别的文件位于 <code>/path/to/project/vendor/laravel/lumen-framework/config/database.php</code> ,而项目级别的文件位于 <code>/path/to/project/config/database.php</code> 。</p>
<p>此配置是和 MySQL 相关的,所以关键字当然是去 MySQL 中找。在 <a href="https://dev.mysql.com/doc/refman/5.5/en/time-zone-support.html" target="_blank" rel="external">MySQL 时区设置</a> 文档中可以看到,有以下三种配置</p>
<figure class="highlight sql"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">1. SYSTEM 表示与系统时区相同</span><br><span class="line">2. '+10:00' or '-6:00' 表示与 UTC 时间的一个偏移量</span><br><span class="line">3. 'Europe/Helsinki', 'US/Eastern', or 'MET' 表示命名时区,命名时区必须在 mysql 库下的 time_zone_name 有注册</span><br></pre></td></tr></table></figure>
<p>导入时区的命令为 </p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">shell&gt; mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root --database=mysql -p</span><br></pre></td></tr></table></figure>
<p>建议是在项目级别设置时区。在 .env 中指定 <code>DB_TIMEZONE=+08:00</code> 和在 database 中指定 <code>&#39;timezone&#39; =&gt; env(&#39;DB_TIMEZONE&#39;, &#39;+08:00&#39;)</code> 均可。</p>
<h2 id="总结"><a href="# 总结" class="headerlink" title="总结"></a>总结 </h2><p> 时区设置分为 PHP 时区设置和 MySQL 的时区设置。PHP 时区设置涉及到 php.ini 配置以及运行时配置,而在 lumen 中还涉及到环境变量的配置。lumen 中既可以在 config 目录下的文件中指定,也可以在 .env 文件中指定。建议既指定 PHP 的配置又指定环境变量的配置,环境变量的配置可供整个应用程序使用,是项目级别的共享配置。</p>
</content>
<summary type="html">
<p>项目使用了 Laravel 的精简版 Lumen 来做 api 和 sso 的服务。今天在提交代码时发现,生成的 database migration 代码时间相差八个小时。看来是时区设置的问题,查找了一下,发现有好几个地方都有时区的设置,那就简单总结一下。</p>
<h2
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
<category term="laravel" scheme="http://blog.jayxhj.com/tags/laravel/"/>
<category term="lumen" scheme="http://blog.jayxhj.com/tags/lumen/"/>
<category term="php" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="timezone" scheme="http://blog.jayxhj.com/tags/timezone/"/>
</entry>
<entry>
<title>反引号 (`) 与 单引号 (') 以及 双引号 ('') 的使用</title>
<link href="http://blog.jayxhj.com/2016/03/single-double-quote-and-backtick/"/>
<id>http://blog.jayxhj.com/2016/03/single-double-quote-and-backtick/</id>
<published>2016-03-20T10:32:36.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>反引号 <code>` </code> (backtick) 与单引号 <code>&#39;</code>(single quote) 以及双引号 <code>&quot;</code> (double quote) 在 PHP 及 MySQL 中都有不同的含义,下面将分别说明并给出它们在 PHP 和 MySQL 结合使用时的一些注意事项。</p>
<h2 id="在 -PHP- 中"><a href="# 在 -PHP- 中" class="headerlink" title="在 PHP 中"></a>在 PHP 中 </h2><p><code>` </code> 在 PHP 中是 <a href="http://php.net/manual/zh/language.operators.execution.php" target="_blank" rel="external"> 执行运算符</a>,效果与调用 <a href="http://php.net/manual/zh/function.shell-exec.php" target="_blank" rel="external">shell_exec()</a> 相同 。</p>
<p><code>&#39;</code> 在 PHP 中用来输出字面量值,被单引号括起来的内容会被原样输出。</p>
<p><code>&quot;</code> 在 PHP 中可以用来解析变量。</p>
<h2 id="在 -MySQL- 中"><a href="# 在 -MySQL- 中" class="headerlink" title="在 MySQL 中"></a>在 MySQL 中</h2><p><code>` </code> 被用来在查询,告诉解析器反引号内的内容表示一个字面量,直接读取而不用做变量替换。</p>
<p><code>&quot;</code> 与 <code>&#39;</code> 用来均被用来解析 MySQL 字符串及 <a href="http://dev.mysql.com/doc/refman/5.7/en/string-literals.html#character-escape-sequences" target="_blank" rel="external">特殊字符</a> ,不过这与 MySQL 的 SQL mode 设置有关。当 <code>ANSI_QUOTES</code> 开启时,<code>&quot;</code> 将被当作标识符。</p>
<h3 id="SQL-mode"><a href="#SQL-mode" class="headerlink" title="SQL mode"></a>SQL mode</h3><p>MySQL 中开启 ANSI mode 时将会使用 <code>&#39;</code> 来告诉解析器,<code>&#39;</code> 内包裹的字符串字面量直接读取,无需解析。</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line">mysql&gt; SET SESSION sql_mode ='ANSI_QUOTES';</span><br><span class="line">Query OK, 0 rows affected (0.01 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT 'hello', '"hello"', '""hello""', 'hel''lo','\'hello','\r';</span><br><span class="line">+-------+---------+-----------+--------+--------+---+</span><br><span class="line">| hello | "hello" | ""hello"" | hel'lo |'hello | |</span><br><span class="line">+-------+---------+-----------+--------+--------+---+</span><br><span class="line"> |hello | "hello" | ""hello"" | hel'lo |'hello |</span><br><span class="line">+-------+---------+-----------+--------+--------+---+</span><br><span class="line">1 row in set (0.01 sec)</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT "hello", '"hello"', '""hello""', 'hel''lo','\'hello','\r';</span><br><span class="line">ERROR 1054 (42S22): Unknown column 'hello' in 'field list'</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT hello, '"hello"', '""hello""', 'hel''lo','\'hello','\r';</span><br><span class="line">ERROR 1054 (42S22): Unknown column 'hello' in 'field list'</span><br><span class="line"></span><br><span class="line">mysql&gt; SELECT `hello`, '"hello"', '""hello""', 'hel''lo','\'hello','\r';</span><br><span class="line">ERROR 1054 (42S22): Unknown column 'hello' in 'field list'</span><br></pre></td></tr></table></figure>
<h2 id="PHP- 执行 -MySQL- 查询"><a href="#PHP- 执行 -MySQL- 查询" class="headerlink" title="PHP 执行 MySQL 查询"></a>PHP 执行 MySQL 查询 </h2><p> 为了防止 SQL 注入,最好使用预处理语句及参数绑定来执行查询。建议使用 <a href="http://php.net/manual/en/book.pdo.php" target="_blank" rel="external">PDO</a> 或者 <a href="http://php.net/manual/en/book.mysqli.php" target="_blank" rel="external">MySQLi</a></p>
<ul>
<li><p>PDO </p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span> </span><br><span class="line"><span class="variable">$dbh</span> = <span class="keyword">new</span> PDO(<span class="string">'mysql:host=localhost;dbname=test'</span>, <span class="variable">$user</span>, <span class="variable">$pass</span>);</span><br><span class="line"><span class="variable">$stmt</span> = <span class="variable">$dbh</span>-&gt;prepare(<span class="string">"INSERT INTO REGISTRY (name, value) VALUES (:name, :value)"</span>);</span><br><span class="line"><span class="variable">$stmt</span>-&gt;bindParam(<span class="string">':name'</span>, <span class="variable">$name</span>);</span><br><span class="line"><span class="variable">$stmt</span>-&gt;bindParam(<span class="string">':value'</span>, <span class="variable">$value</span>);</span><br></pre></td></tr></table></figure>
</li>
</ul>
<ul>
<li><p>MySQLi</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="variable">$mysqli</span> = <span class="keyword">new</span> mysqli(<span class="string">"localhost"</span>, <span class="string">"user"</span>, <span class="string">"password"</span>, <span class="string">"database"</span>);</span><br><span class="line"><span class="variable">$stmt</span> = <span class="variable">$mysqli</span>-&gt;prepare(<span class="string">"INSERT INTO `test`(`id`) VALUES (?)"</span>);</span><br><span class="line"><span class="variable">$stmt</span>-&gt;bind_param(<span class="string">"i"</span>, <span class="variable">$id</span>)</span><br></pre></td></tr></table></figure>
</li>
</ul>
<h2 id="实践"><a href="# 实践" class="headerlink" title="实践"></a>实践</h2><p>SQL 标准规定使用 <code>&#39;</code> 来包裹字符串,但不同的数据库有不同的实现,最好的方式,当然是使用 PDO ,这样就不用对 sql 语句单独进行处理,而且在转换数据库时业务逻辑中的 sql 也无需修改和转换。</p>
<p>最好的实践其实应该在代码设计上规避错误:</p>
<ol>
<li>取名不与 <a href="http://dev.mysql.com/doc/refman/5.5/en/reserved-words.html" target="_blank" rel="external">MySQL 保留字</a> 冲突 </li>
<li>清楚 MySQL 服务器 SQL mode <strong><code>SELECT @@sql_mode</code></strong></li>
<li>使用 <code>` </code> 包裹表名及列名</li>
<li>使用 <code>&#39;</code> 包裹查询条件</li>
</ol>
</content>
<summary type="html">
<p>反引号 <code>` </code> (backtick) 与单引号 <code>&#39;</code>(single quote) 以及双引号 <code>&quot;</code> (double quote) 在 PHP 及 MySQL 中都有不同的含义,下面将分
</summary>
<category term="MySQL" scheme="http://blog.jayxhj.com/tags/mysql/"/>
<category term="PHP" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="quote" scheme="http://blog.jayxhj.com/tags/quote/"/>
</entry>
<entry>
<title>解决 PHP "headers already sent" 错误</title>
<link href="http://blog.jayxhj.com/2016/03/fix-headers-already-sent-error-in-php/"/>
<id>http://blog.jayxhj.com/2016/03/fix-headers-already-sent-error-in-php/</id>
<published>2016-03-09T05:32:16.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>这篇文章是 stackoverflow 上的一个问题的回答,答案集中了其它答案的想法,并做了归纳,所以翻译过来,以后遇到 “headers already sent” 的错误就可以直接一一排查,或者干脆在代码动手及设计上避免这类问题了。</p>
<h2 id="发送 -header- 前不要有任何输出"><a href="# 发送 -header- 前不要有任何输出" class="headerlink" title="发送 header 前不要有任何输出"></a>发送 header 前不要有任何输出 </h2><p> 发送或者修改 HTTP 头信息的方法必须在任何输出被输出之前被调用。否则调用将会出错:</p>
<blockquote>
<p>Warning: Cannot <strong>modify</strong> header information - headers already sent (<strong>output</strong> started at script:line)</p>
</blockquote>
<p>这些方法可以修改(modify) HTTP 头信息:</p>
<ul>
<li><a href="http://php.net/header" target="_blank" rel="external">header</a> / <a href="http://php.net/header_remove" target="_blank" rel="external">header_remove</a></li>
<li><a href="http://php.net/session_start" target="_blank" rel="external">session_start</a> / <a href="http://php.net/session_regenerate_id" target="_blank" rel="external">session_regenerate_id</a></li>
<li><a href="http://php.net/setcookie" target="_blank" rel="external">setcookie</a> / <a href="http://php.net/setrawcookie" target="_blank" rel="external">setrawcookie</a></li>
</ul>
<p>输出(output)可以是:</p>
<ul>
<li>无意的:<ul>
<li><code>&lt;?php</code> 之前或者 <code>?&gt;</code> 之后的空格</li>
<li><a href="https://zh.wikipedia.org/wiki/ 位元組順序記號" target="_blank" rel="external">UTF-8 BOM</a> </li>
</ul>
</li>
<li>有意的:<ul>
<li><code>print</code> ,<code>echo</code> 以及其他能产生输出的方法</li>
<li>在 <code>&lt;?php</code> 前原始的 <code>&lt;html&gt;</code> 区块</li>
</ul>
</li>
</ul>
<h2 id="为什么这个错误会产生"><a href="# 为什么这个错误会产生" class="headerlink" title="为什么这个错误会产生"></a>为什么这个错误会产生 </h2><p> 为了理解为什么 HTTP header 必须在输出之前发送出去,我们有必要了解看一下一个典型的 HTTP 相应。PHP 脚本主要用来生成 HTML ,但它也会发送一系列的 HTTP/CGI 头信息到 web 服务器:</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line">HTTP/1.1 200 OK</span><br><span class="line">Powered-By: PHP/5.3.7</span><br><span class="line">Vary: Accept-Encoding</span><br><span class="line">Content-Type: text/html; charset=utf-8</span><br><span class="line"></span><br><span class="line"><span class="tag">&lt;<span class="title">html</span>&gt;</span><span class="tag">&lt;<span class="title">head</span>&gt;</span><span class="tag">&lt;<span class="title">title</span>&gt;</span>PHP page output page<span class="tag">&lt;/<span class="title">title</span>&gt;</span><span class="tag">&lt;/<span class="title">head</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;<span class="title">body</span>&gt;</span><span class="tag">&lt;<span class="title">h1</span>&gt;</span>Content<span class="tag">&lt;/<span class="title">h1</span>&gt;</span> <span class="tag">&lt;<span class="title">p</span>&gt;</span>Some more output follows...<span class="tag">&lt;/<span class="title">p</span>&gt;</span></span><br><span class="line">and <span class="tag">&lt;<span class="title">a</span> <span class="attribute">href</span>=<span class="value">"/"</span>&gt;</span> <span class="tag">&lt;<span class="title">img</span> <span class="attribute">src</span>=<span class="value">internal-icon-delayed</span>&gt;</span> <span class="tag">&lt;/<span class="title">a</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p>页面或者输出总是紧跟在头信息后面。PHP 必须先把头信息发送给 web 服务器,并且它只能发送一次,在这之后就再也不能修改头信息了。</p>
<p>当 PHP 第一次接收到输出时(<code>print</code> ,<code>echo</code>,<code>&lt;html&gt;</code>) 它会清掉所有收集到的头信息。在此之后它能把输出所有想输出的内容,但是再想发送 HTTP 头信息就不可能了。</p>
<h2 id="怎么找到到底是哪里提前产生了输出?"><a href="# 怎么找到到底是哪里提前产生了输出?" class="headerlink" title="怎么找到到底是哪里提前产生了输出?"></a>怎么找到到底是哪里提前产生了输出?</h2><p><code>header()</code> 头信息包含所有与问题产生相关的信息:</p>
<blockquote>
<p>Warning: Cannot modify header information - headers already sent by (output started at /www/usr2345/htdocs/auth.php:52) in /www/usr2345/htdocs/index.php on line 100</p>
</blockquote>
<p>在上面的警告中,<code>line 100</code> 指向调用 <code>header()</code> 失败的脚本行数。</p>
<p>圆括号里的 <code>output started</code> 这条信息更加重要。它指出了先于 header() 前的输出的源头。在这个例子中是 <code>auth.php</code> 的 第 52 行,这就是你要去找的过早的输出的地方。</p>
<p>典型的原因有这些:</p>
<ol>
<li><p><strong>print</strong>,<strong>echo</strong><br>有意的 <code>print</code> 和 <code>echo</code> 语句输出将会中断输出 HTTP 头信息的机会。应用程序流必须重组以避免这种行为,可以使用 <a href="http://php.net/function" target="_blank" rel="external">function</a> 和模版来重组,从而保证 <code>header()</code> 调用是在信息被写出之前。<br>产生输出的方法包括: </p>
<ul>
<li>print, echo, printf, vprintf</li>
<li>trigger_error, ob_flush, ob_end_flush, var_dump, print_r</li>
<li>readfile, passthru, flush, imagepng, imagejpeg </li>
</ul>
<p>以及其他用户自定义的方法。</p>
</li>
<li><p><strong>原始的 HTML</strong><br>在一个 PHP 文件中未被解析的 HTML 区块也是输出。脚本中各种可能触发调用 <code>header()</code> 的条件都必须在任何 <code>&lt;html&gt;</code> 区块前声明。</p>
</li>
<li><p><strong>&lt;?php 前的空格导致的 <code>&quot;script.php line 1&quot;</code> 警告 </strong><br> 如果警告指向第 <strong>1</strong> 行的输出,那么它很有可能指向的是在 <code>&lt;?php</code> 之前的空格,文本或者 HTML 。 </p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"> <span class="preprocessor">&lt;?php</span></span><br><span class="line"><span class="comment">// 在 &lt;?php 前有个空格</span></span><br></pre></td></tr></table></figure>
<p>同样它可能出现在附加的脚本或者脚本区块上:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="preprocessor">?&gt;</span></span><br><span class="line"></span><br><span class="line"><span class="preprocessor">&lt;?php</span></span><br></pre></td></tr></table></figure>
<p>PHP 确实在闭合标签后占据了一个换行符,但是它不会在上面的空白处插入换行符、制表符或者空格(也就是说这是我们自己造成的)。 </p>
</li>
<li><p><strong>UTF-8 BOM</strong><br>换行符或者空格可能导致问题,但是不可见的字符序列同样可以。最著名的就是大多数文本编辑器并不会显示的 <strong>UTF-8 BOM</strong> 。它是在 UTF-8 编码的文档里可选甚至是多余的,被标示为 <code>EF BB BF</code> 的字节序列。但是 PHP 必须把它当作原始的输出来处理。它可能以 <code></code> 这样的符号输出(如果客户端以 Latin-1 来解释这个文档)或者其他这样的“非法输出”。<br>以某种图形化的编辑器或者基于 JAVA 的 IDE 查看这类文件时,你可能察觉不到 <strong>UTF-8 BOM</strong> 的存在。它们没有把 <strong>UTF-8 BOM</strong> 形象化(受制于 Unicode 标准)。然而大多数程序编辑器和控制台编辑程序会这样处理:<br><img src="http://i.stack.imgur.com/aXgWY.png" alt=""><br>像这样就能简单地提早发现问题了。其他的编辑器在设置某些选项后也能纠正这样的问题(Windows 上的 Notepad++ 可以识别并且 <a href="http://stackoverflow.com/questions/3589358/fix-utf8-bom"target="_blank"rel="external">纠正 BOM 问题 </a> ),另一个发现 BOM 的方法就是借助十六进制的编辑器。在 *nix 系统上,大都提供了 <a href="http://linux.die.net/man/1/hexdump"target="_blank"rel="external"><code>hexdump</code></a> ,如果没有的话,其他图形化的变种也可以用来简化审计这些问题的步骤:<br><img src="http://i.stack.imgur.com/QyqUr.png"alt=""><br> 一个简单的修正方法就是将文本编辑器设置为 <strong>以 UTF-8 (no BOM) 保存文件</strong>(<strong>save files as UTF-8 (no BOM)</strong>)或者其他类似的设置。 </p>
<h3 id="修正程序"><a href="# 修正程序" class="headerlink" title="修正程序"></a>修正程序 </h3><p> 有很多自动化的工具可以检测并修改文本文件(<a href="http://stackoverflow.com/questions/1068650/using-awk-to-remove-the-byte-order-mark" target="_blank" rel="external"><code>sed</code></a> / <a href="http://stackoverflow.com/questions/1068650/using-awk-to-remove-the-byte-order-mark" target="_blank" rel="external"><code>awk</code></a> 或者 <code>recode</code> )。PHP 里有 <a href="http://freshcode.club/projects/phptags" target="_blank" rel="external"><code>phptags</code></a> 。它可以把打开标签和关闭标签重写成长标签(&lt;?php)或者短标签(&lt;?)的形式。也可以轻松地解决前导或尾随的空格、Unicode 和 UTF-x BOM 问题: </p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">phptags --whitespace *.php</span><br></pre></td></tr></table></figure>
<p>同样,你可以在某个目录或整个项目目录使用这个命令。</p>
</li>
<li><p><strong>?&gt; 后的空白 </strong><br> 如果错误代码在闭合标签 &gt;? 这一行的前面,那么这就是 &gt;? 后的空格或者原始文本输出导致的问题。PHP 的结束标记并不会在遇到闭合标签时终止执行脚本,任何 ?&gt; 之后的文本或者空格字符都会被当作页面内容输出。<br>通用的被鼓励的做法,特别是针对新手,是避免在 PHP 文件后加上闭合标签 <strong>?&gt;</strong> 。这样就能避免一部分产生这类问题的情况。</p>
</li>
<li><p><strong>错误源提示:”Unknown on line 0”</strong><br>如果没有给出具体的错误源,那么这就是典型的 PHP 扩展或者 <code>php.ini</code> 设置的问题:</p>
<ul>
<li>偶尔是 <code>gzip</code> 编码设置或者是 <a href="http://stackoverflow.com/questions/622192/php-warning-headers-already-sent-in-unknown" target="_blank" rel="external"><code>ob_gzhandler</code></a></li>
<li>也有可能是 php.ini 设置里模块加载了两次导致 PHP 产生了启动 / 警告信息</li>
</ul>
</li>
<li><p><strong>先前的错误导致输出了错误信息 </strong><br> 如果前面的 PHP 语句或者表达式造成了 warning 或者 notice 信息导致输出,这些输出也被认为是过早地输出。<br>在这种情况下你需要避免错误,推后这些语句的执行,或者抑制这些信息的输出,可以使用 <code>isset()</code> 进行判断,或者使用抑制符 <code>@</code>,前提是它们不会阻止后续的调试。</p>
</li>
</ol>
<h2 id="没有错误信息输出"><a href="# 没有错误信息输出" class="headerlink" title="没有错误信息输出"></a>没有错误信息输出 </h2><p> 如果你禁用了 php.ini 里的 <code>error_reporting</code> 或者 <code>display_errors</code> 设置,那么将不会产生 warning 。但是忽略错误并不会让问题消失,头信息仍然不能在过早的输出输出之前发送出去。</p>
<p>所以当 <code>header(&quot;Location: ...&quot;)</code> 跳转静默地失败时,建议你去查看 warnings 。在脚本的最前面用下面的两条命令重新开启错误报告设置:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">error_reporting(E_ALL);</span><br><span class="line">ini_set("display_errors", 1);</span><br></pre></td></tr></table></figure>
<p>或者如果其他的设置都失败了那就设置 <code>set_error_handler(&quot;var_dump&quot;);</code> 。</p>
<p>至于跳转的 header ,在执行至最后的代码时你应该遵循下面的这种风格:</p>
<figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">exit</span>(header(<span class="string">"Location: /finished.html"</span>));</span><br></pre></td></tr></table></figure>
<p>最好是提供一个方法,特别是当 <code>header()</code> 执行失败时打印出用户信息。</p>
<h2 id="变通方法:输出缓冲"><a href="# 变通方法:输出缓冲" class="headerlink" title="变通方法:输出缓冲"></a>变通方法:输出缓冲</h2><p>PHP 的输出缓冲的方法是缓解这种问题的一种变通方法。它运行起来可靠,但是你绝不要使用它来替代你架构良好应用程序结构,从控制逻辑中分离输出。它的真实目的是用来减轻大块数据传输至服务器时的压力。</p>
<ol>
<li><a href="http://php.net/manual/en/outcontrol.configuration.php" target="_blank" rel="external"><code>output_buffering 设置</code></a> 在 <a href="http://www.php.net/manual/en/configuration.file.php" target="_blank" rel="external">php.ini</a> 或者 <a href="http://www.php.net/manual/en/configuration.changes.php" target="_blank" rel="external">.htaccess</a> 或者甚至在最新的 FPM/FastCGI 的 <a href="http://php.net/manual/en/configuration.file.per-user.php" target="_blank" rel="external">.user.ini</a> 中设置;</li>
<li>同样你可以在脚本的最前面使用 <a href="http://php.net/ob_start" target="_blank" rel="external"><code>ob_start()</code></a> 来设置,但是它并没那么可靠: <ul>
<li>即使 <code>&lt;?php ob_start(); ?&gt;</code> 在第一个脚本里,空格或者 BOM 也有可能在此之前被输出</li>
<li>它可以隐藏 HTML 输出里的空格(将空格放到 buffer 中),但是只要应用程序逻辑企图发送二进制内容(比如生成的图片),缓冲里的无关的输出就会成为问题(这样 ob_clean() 方法就成为下一步的变通方法了)。</li>
<li>缓冲有大小限制,并且在默认配置下很容易超出,并且这种情况并不少见,一旦发生也不太容易追踪。</li>
</ul>
</li>
</ol>
<p>因此这两个方法变得不可靠了,特别是当你需要更改开发环境或者生产环境的配置的时候。这就是为什么输出缓冲被认为只是一种蹩脚的变通方法。</p>
<p>建议参考官方手册里的基本 <a href="http://www.php.net/manual/en/outcontrol.examples.basic.php" target="_blank" rel="external">使用方法</a> ,以及它的优缺点:</p>
<ul>
<li><a href="http://stackoverflow.com/questions/2832010/what-is-output-buffering" target="_blank" rel="external">输出缓冲是什么</a></li>
<li><a href="http://stackoverflow.com/questions/2148114/why-use-output-buffering-in-php" target="_blank" rel="external">为什么使用 output buffering</a></li>
<li><a href="http://stackoverflow.com/questions/4731375/is-using-output-buffering-considered-a-bad-practice" target="_blank" rel="external">使用输出缓冲是不好的实践吗?</a></li>
<li><a href="http://stackoverflow.com/questions/2919569/use-case-for-output-buffering-as-the-correct-solution-to-headers-already-sent" target="_blank" rel="external">正确使用 output buffering 解决 “header already sent” 的例子</a></li>
</ul>
<h3 id="但是在其他的服务器上是好的?"><a href="# 但是在其他的服务器上是好的?" class="headerlink" title="但是在其他的服务器上是好的?"></a><strong>但是在其他的服务器上是好的?</strong></h3><p>如果你之前没有收到过头信息的 warning ,那么 php.ini 里的 <a href="http://php.net/manual/zh/outcontrol.configuration.php" target="_blank" rel="external">output_buffering</a> 设置改变了。在现在的/不同的服务器上很有可能没有设置。</p>
<h2 id="使用 -headers-sent- 检查"><a href="# 使用 -headers-sent- 检查" class="headerlink" title="使用 headers_sent() 检查"></a><strong>使用 <code>headers_sent()</code> 检查 </strong></h2><p> 你可以使用 <a href="http://php.net/headers_sent" target="_blank" rel="external"><code>headers_sent</code></a> 来检查是否可以发送头信息。这种方法可以有效地检查以便输出一个错误信息或是应用其他的逻辑。<br>不错的回退变通方法有: </p>
<ul>
<li><p><strong>HTML<code>&lt;meta&gt;</code> tag</strong><br> 如果你的应用程序很难在结构上解决这个问题,有个简单但显得不专业的做法是在 HTML <meta> 标签中来跳转网页。可以这样实现: </p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">meta</span> <span class="attribute">http-equiv</span>=<span class="value">"Location"</span> <span class="attribute">content</span>=<span class="value">"http://example.com/"</span>&gt;</span></span><br></pre></td></tr></table></figure>
<p> 或者加上一个延迟时间</p>
<figure class="highlight html"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="title">meta</span> <span class="attribute">http-equiv</span>=<span class="value">"Refresh"</span> <span class="attribute">content</span>=<span class="value">"2; url=../target.html"</span>&gt;</span></span><br></pre></td></tr></table></figure>
</li>
<li><p>JavaScript 跳转<br> 另一个可选的方法就是使用 <a href="http://stackoverflow.com/questions/503093/how-can-i-make-a-redirect-page-in-jquery-javascript" target="_blank" rel="external">JavaScript 跳转</a> 来实现网页跳转:</p>
<figure class="highlight"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">&#60;script&#62; location.replace(&#34;target.html&#34;); &#60;/script&#62;</span><br></pre></td></tr></table></figure>
</li>
</ul>
<p>这种方式相比较 <meta> 方法起来更兼容 HTML 标准,它只依赖于可以运行 JavaScript 的客户端。</p>
<p>这两种方式在 HTTP header() 调用失败时都提供了可以接受的回退方式。理想化的处理方式应该是将跳转与其它方式结合,给出对用户友好的辅助信息并且提供一个可点的链接以供后续操作。</p>
<h2 id="为什么 -setcookie- 和 -session-start- 都会被影响"><a href="# 为什么 -setcookie- 和 -session-start- 都会被影响" class="headerlink" title="为什么 setcookie() 和 session_start() 都会被影响"></a><strong>为什么 <code>setcookie()</code> 和 <code>session_start()</code> 都会被影响</strong></h2><p><code>setcookie()</code> 和 <code>session_start()</code> 都需要发送一个 <code>set-cookie:</code> 的 HTTP 头信息。这种情况就和前面输出 header() 的情况类似,所以同样会出现由于过早地输出错误信息导致的错误。</p>
<p>(当然它们受影响也有可能是因为客户端禁止了 cookie 导致的,设置可能是代理的问题。很明显,session 也取决去剩余磁盘空间大小或者 php.ini 里的其它设置)</p>
<hr>
<p>原文链接:<a href="http://stackoverflow.com/questions/8028957/how-to-fix-headers-already-sent-error-in-php" target="_blank" rel="external">http://stackoverflow.com/questions/8028957/how-to-fix-headers-already-sent-error-in-php</a></p>
</content>
<summary type="html">
<p>这篇文章是 stackoverflow 上的一个问题的回答,答案集中了其它答案的想法,并做了归纳,所以翻译过来,以后遇到 “headers already sent” 的错误就可以直接一一排查,或者干脆在代码动手及设计上避免这类问题了。</p>
<h2 id="发送 -he
</summary>
<category term="翻译文章" scheme="http://blog.jayxhj.com/categories/%E7%BF%BB%E8%AF%91%E6%96%87%E7%AB%A0/"/>
<category term="HTTP" scheme="http://blog.jayxhj.com/tags/http/"/>
<category term="PHP" scheme="http://blog.jayxhj.com/tags/php/"/>
<category term="header()" scheme="http://blog.jayxhj.com/tags/header/"/>
</entry>
<entry>
<title>理解 Exit Code 并学会如何在 Bash 脚本中使用</title>
<link href="http://blog.jayxhj.com/2016/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/"/>
<id>http://blog.jayxhj.com/2016/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/</id>
<published>2016-02-27T10:51:02.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><h2 id="Exit-Codes- 是什么"><a href="#Exit-Codes- 是什么" class="headerlink" title="Exit Codes 是什么"></a>Exit Codes 是什么 </h2><p> 在 Unix 和 Linux 系统中,程序可以在执行终止后传递值给其父进程。这个值被称为退出码(exit code)或退出状态(exit status)。在 POSIX 系统中,惯例做法是当程序成功执行时传递 0 ,当程序执行失败时传递 1 或比 1 大的值。</p>
<p>传递状态码为何重要?如果你在命令行脚本上下文中查看状态码,答案显而易见。任何有用的脚本,它将不可避免地要么被其他脚本所使用,要么被 bash 单行脚本包裹所使用。特别是脚本被用来与自动化工具 SaltStack 或者监测工具 Nagios 配合使用。这些工具会执行脚本并检查它的状态,来确定脚本是否执行成功。</p>
<p>其中最重要的原因是,即使你不定义状态码,它仍然存在于你的脚本中。如果你不定义恰当的退出码,执行失败的脚本可能会返回成功的状态,这样会导致问题,问题大小取决于你的脚本做了什么。</p>
<h2 id="如果不指定退出码会发生什么"><a href="# 如果不指定退出码会发生什么" class="headerlink" title="如果不指定退出码会发生什么"></a>如果不指定退出码会发生什么 </h2><p> 在 Linux 里,任何在命令行中执行的脚本都有退出码。在 Bash 脚本中,如果脚本里没有指定退出码,退出码将会是脚本最后一个命令执行后产生的状态码。为了更好地解释退出码,下面将给出一个脚本来说明。</p>
<p><strong>脚本</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line"><span class="shebang">#!/bin/bash</span></span><br><span class="line">touch /root/<span class="built_in">test</span></span><br><span class="line"><span class="built_in">echo</span> created file</span><br></pre></td></tr></table></figure>
<p>上面的脚本既会执行 <code>touch</code> 命令也会执行 <code>echo</code> 命令。当我们以非 root 用户执行这个脚本时 touch 命令将会执行失败,理想情况下,当我们执行 touch 命令失败时我们希望通过脚本的退出码来表明有命令执行失败。我们可以通过打印 Bash 的特殊变量 <code>$?</code> 来获取退出码。这个变量将会打印出脚本最后一个命令执行的退出码。</p>
<p><strong>执行输出</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh </span><br><span class="line">touch: cannot touch <span class="string">'/root/test'</span>: Permission denied</span><br><span class="line">created file</span><br><span class="line">$ <span class="built_in">echo</span> $?</span><br><span class="line"><span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>你可以看到,当执行 <code>./tmp.sh</code> 后退出码是 <code>0</code> ,而 0 代表脚本执行成功,虽然 touch 命令执行失败了。上面的脚本执行了两个命令:<code>touch</code>、<code>echo</code>。因为我们没有指定退出码所以脚本以最后一个命令执行后的状态码退出。在这个例子中,最后运行的是 <code>echo</code> 命令,这个命令确实执行成功了。</p>
<p><strong>脚本</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="shebang">#!/bin/bash</span></span><br><span class="line">touch /root/<span class="built_in">test</span></span><br></pre></td></tr></table></figure>
<p>如果我们去掉脚本中的 <code>echo</code> 命令,我们将看到 <code>touch</code> 命令的退出码。</p>
<p><strong>执行结果</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh </span><br><span class="line">touch: cannot touch <span class="string">'/root/test'</span>: Permission denied</span><br><span class="line">$ <span class="built_in">echo</span> $?</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>
<p>你可以看到,因为最后运行的命令是 <code>touch</code> ,所以脚本的退出码正确地反应了脚本的状态:执行失败。</p>
<h2 id="在你的 -Bash- 脚本中使用退出码"><a href="# 在你的 -Bash- 脚本中使用退出码" class="headerlink" title="在你的 Bash 脚本中使用退出码"></a>在你的 Bash 脚本中使用退出码 </h2><p> 当从我们的脚本中去掉 <code>echo</code> 命令后脚本跑通了并且返回了恰当的退出码。当我们想在 <code>touch</code> 命令执行成功执行一个操作,而在执行失败时执行另一个操作会发生什么。比如脚本执行成功时输出至 <code>stdout</code> ,执行失败时输出至 <code>stderr</code> 这种操作。</p>
<h3 id="测试退出码"><a href="# 测试退出码" class="headerlink" title="测试退出码"></a>测试退出码 </h3><p> 前面的代码中,我们使用了特殊变量 <code>$?</code> 来打印脚本的退出码。我们同样可以在脚本中使用它来测试 <code>touch</code> 命令是否执行成功。</p>
<p><strong>脚本</strong><br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="shebang">#!/bin/bash</span></span><br><span class="line">touch /root/<span class="built_in">test</span> <span class="number">2</span>&gt; /dev/null</span><br><span class="line"><span class="keyword">if</span> [$? <span class="operator">-eq</span> <span class="number">0</span> ]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"Successfully created file"</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"Could not create file"</span> &gt;&amp;<span class="number">2</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure></p>
<p>在上面的修改版代码中,如果 <code>touch</code> 的退出码是 <code>0</code> ,脚本将会输出成功的消息。如果退出码是除 0 以外的其他数字,这表示执行失败,脚本将会打印失败的消息到 <code>stderr</code> 。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh</span><br><span class="line">Could not create file</span><br></pre></td></tr></table></figure>
<h3 id="在程序中提供你自己的退出码"><a href="# 在程序中提供你自己的退出码" class="headerlink" title="在程序中提供你自己的退出码"></a>在程序中提供你自己的退出码 </h3><p> 在上面的程序中,虽然 <code>touch</code> 命令执行失败时将会提供一个错误信息提示,但它仍给出了表示执行成功的状态码 0 。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh</span><br><span class="line">Could not create file</span><br><span class="line">$ <span class="built_in">echo</span> $?</span><br><span class="line"><span class="number">0</span></span><br></pre></td></tr></table></figure>
<p>既然脚本执行失败了,但它仍然传递执行成功的退出码给其他需要执行此脚本程序显然很不适合。为添加我们自己的退出码到这个程序里,我们可以简单地通过 <code>exit</code> 来实现。</p>
<p><strong>脚本</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br></pre></td><td class="code"><pre><span class="line"><span class="shebang">#!/bin/bash</span><br><span class="line"></span></span><br><span class="line">touch /root/<span class="built_in">test</span> <span class="number">2</span>&gt; /dev/null</span><br><span class="line"></span><br><span class="line"><span class="keyword">if</span> [$? <span class="operator">-eq</span> <span class="number">0</span> ]</span><br><span class="line"><span class="keyword">then</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"Successfully created file"</span></span><br><span class="line"> <span class="built_in">exit</span> <span class="number">0</span></span><br><span class="line"><span class="keyword">else</span></span><br><span class="line"> <span class="built_in">echo</span> <span class="string">"Could not create file"</span> &gt;&amp;<span class="number">2</span></span><br><span class="line"> <span class="built_in">exit</span> <span class="number">1</span></span><br><span class="line"><span class="keyword">fi</span></span><br></pre></td></tr></table></figure>
<p>通过脚本里的 <code>exit</code> 命令,我们可以在 <code>touch</code> 命令执行成功时输出成功的消息并返回状态码 0 。当 <code>touch</code> 执行失败时我们将打印失败的消息至 <code>stderr</code> 并返回一个表示失败的状态码 1 。</p>
<p><strong>执行结果</strong></p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh</span><br><span class="line">Could not create file</span><br><span class="line">$ <span class="built_in">echo</span> $?</span><br><span class="line"><span class="number">1</span></span><br></pre></td></tr></table></figure>
<h3 id="在命令行中使用退出码"><a href="# 在命令行中使用退出码" class="headerlink" title="在命令行中使用退出码"></a>在命令行中使用退出码 </h3><p> 现在我们的脚本既可以通知用户也能通知程序命令是否执行成功,我们可以使用这个脚本配合其他的管理工具,或者简单地通过 Bash 单行命令使用它。<br><strong>Bash One Liner:</strong><br><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line">$ ./tmp.sh &amp;&amp; <span class="built_in">echo</span> <span class="string">"bam"</span> || (sudo ./tmp.sh &amp;&amp; <span class="built_in">echo</span> <span class="string">"bam"</span> || <span class="built_in">echo</span> <span class="string">"fail"</span>)</span><br><span class="line">Could not create file</span><br><span class="line">Successfully created file</span><br><span class="line">bam</span><br></pre></td></tr></table></figure></p>
<p>上面的命令组使用了在 Bash 里称为 <strong>list constructs</strong> 的工具。它允许你通过 <code>&amp;&amp;</code>(代表 <strong>and</strong>) 和 <code>||</code> (代表 <strong>or</strong>) 将命令串到一起。上面的命令将会执行 <code>./tmp.sh</code> 脚本,如果退出码是 0 命令 <code>echo &quot;bam&quot;</code> 将被执行。但如果 <code>./tmp.sh</code> 的退出码为 1 ,圆括号里的命令将在之后被执行。圆括号里的命令通过 <code>&amp;&amp;</code> 、 <code>||</code> 被串到一起。</p>
<p>list constructs 使用退出码来知晓一个命令是否执行成功。如果脚本不恰当地使用退出码,其他使用更高阶的命令(比如 list constructs)调用此脚本的用户就可能得到与预期不符的结果。</p>
<h2 id="更多关于退出码的信息"><a href="# 更多关于退出码的信息" class="headerlink" title="更多关于退出码的信息"></a>更多关于退出码的信息 </h2><p>Bash 里的 <code>exit</code> 命令接受不了从 <code>0 - 255</code> 的整形数值,大多数情况下 <code>0</code> 和 <code>1</code> 就够用了,不过其他的数值也提供了,为更具体的错误信息做储备。The Linux Documentation Project 有一个不错的 <a href="http://www.tldp.org/LDP/abs/html/exitcodes.html" target="_blank" rel="external"> 保留状态码</a> 列表,告诉大家这些状态码作何使用。</p>
<hr>
<p>原文链接:<a href="http://bencane.com/2014/09/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/" target="_blank" rel="external">http://bencane.com/2014/09/02/understanding-exit-codes-and-how-to-use-them-in-bash-scripts/</a></p>
</content>
<summary type="html">
<h2 id="Exit-Codes- 是什么"><a href="#Exit-Codes- 是什么" class="headerlink" title="Exit Codes 是什么"></a>Exit Codes 是什么 </h2><p> 在 Unix 和 Linux 系统中
</summary>
<category term="翻译文章" scheme="http://blog.jayxhj.com/categories/%E7%BF%BB%E8%AF%91%E6%96%87%E7%AB%A0/"/>
<category term="bash" scheme="http://blog.jayxhj.com/tags/bash/"/>
<category term="exit code" scheme="http://blog.jayxhj.com/tags/exit-code/"/>
<category term="shell" scheme="http://blog.jayxhj.com/tags/shell/"/>
</entry>
<entry>
<title>使用 Chrome 扩展程序 JSON Viewer 进行调试</title>
<link href="http://blog.jayxhj.com/2016/01/using-json-viewer-for-debugging/"/>
<id>http://blog.jayxhj.com/2016/01/using-json-viewer-for-debugging/</id>
<published>2016-01-23T11:05:50.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>PHP 调试时有很多种方法,其中最简单的无非是 echo 与 var_dump 、 print_r 以及 debug_backtrace 了。这篇文章要介绍的是平常用的比较多的,适合在调 HTTP 接口以及平常开发中需要输出变量内容的情况。其实在 IDE 中比较适合使用 XDebug 或者断点调试,这样能在运行时直接查看当前上下文的各个变量,不过这种情况不在此篇讨论之列,后续我也将介绍相关的方法,来完善这个系列。</p>
<p> 现在步入正题。在平常和前端的配合中经常会有一些 RESTful 接口输出,而其中最常用的内容传输格式就是 JSON ,JSON 在 PHP 中可以很方便地转化为对象或者数组,在打印变量时使用 JSON 再配合浏览器的扩展进行解析,无疑能提高开发效率。</p>
<h1 id="JSON"><a href="#JSON" class="headerlink" title="JSON"></a>JSON</h1><p> 先用两张图来了解一下 JSON 的格式,这样就不会混淆其在 PHP 中所代表的意思。</p>
<blockquote>
<p> 对象是一个无序的“‘名称 / 值’对”集合。一个对象以“{”(左括号)开始,“}”(右括号)结束。每个“名称”后跟一个“:”(冒号);“‘名称 / 值’ 对”之间使用“,”(逗号)分隔。</p>
</blockquote>
<p><img src="http://www.json.org/object.gif" alt=""></p>
<blockquote>
<p> 数组是值(value)的有序集合。一个数组以“[”(左中括号)开始,“]”(右中括号)结束。值之间使用“,”(逗号)分隔。</p>
</blockquote>
<p><img src="http://www.json.org/array.gif" alt=""></p>
<blockquote>
<p> 值(value)可以是双引号括起来的字符串(string)、数值 (number)、true、false、 null、对象(object)或者数组(array)。这些结构可以嵌套。</p>
</blockquote>
<p><img src="http://www.json.org/value.gif" alt=""></p>
<h1 id="Chrome- 扩展"><a href="#Chrome- 扩展" class="headerlink" title="Chrome 扩展"></a>Chrome 扩展 </h1><p>JSON viewer 扩展的地址是 <a href="https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh" target="_blank" rel="external">https://chrome.google.com/webstore/detail/json-viewer/gbmdgpbipfallnflgajpaliibnhdgobh</a> </p>
<p>JSON view GitHub 官网介绍了其有如下 feature</p>
<ul>
<li> 语法高亮 </li>
<li>23 个内置的主题 </li>
<li> 节点可折叠 </li>
<li> 可点击的 url</li>
<li> 在控制台「console」输入 json 回车中查看你的 JSON (通过访问 id 为 json 的 HTML 元素)</li>
<li> 显示行号选项 </li>
<li> 在 url 中加入时间戳 header</li>
</ul>
<p> 等等。</p>
<h1 id="PHP- 配合 -JSON-viewer- 进行输出调试"><a href="#PHP- 配合 -JSON-viewer- 进行输出调试" class="headerlink" title="PHP 配合 JSON viewer 进行输出调试"></a>PHP 配合 JSON viewer 进行输出调试 </h1><p>PHP 中用来处理 JSON 使用最多的是 json_encode 和 json_decode ,比如你有 array 有如下结构 <br><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="variable">$arr</span> = <span class="keyword">array</span>(</span><br><span class="line"> <span class="string">'key1'</span> =&gt; <span class="number">1</span>,</span><br><span class="line"> <span class="number">1</span> =&gt; <span class="string">'string'</span>,</span><br><span class="line"> <span class="string">'array'</span> =&gt; <span class="keyword">array</span>(</span><br><span class="line"> <span class="string">'value1'</span>,</span><br><span class="line"> <span class="string">'value2'</span>,</span><br><span class="line"> )</span><br><span class="line"> );</span><br></pre></td></tr></table></figure></p>
<p> 如果使用 var_dump 输出,那么长的是这样 <br><figure class="highlight php"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">Array</span></span><br><span class="line">(</span><br><span class="line"> [key1] =&gt; <span class="number">1</span></span><br><span class="line"> [<span class="number">1</span>] =&gt; string</span><br><span class="line"> [<span class="keyword">array</span>] =&gt; <span class="keyword">Array</span></span><br><span class="line"> (</span><br><span class="line"> [<span class="number">0</span>] =&gt; value1</span><br><span class="line"> [<span class="number">1</span>] =&gt; value2</span><br><span class="line"> )</span><br><span class="line"></span><br><span class="line">)</span><br></pre></td></tr></table></figure></p>
<p> 当层级不够多,数组元素不够多,肉眼观察根本不是问题,但是一旦数据变得复杂,输出将会非常难看且不易查找,这时候一个美观的输出外带查看层级和数据复制功能会显得很方便,此时,你只需要 <code>echo json_encode($arr);exit;</code> ,扩展会自动解析 JSON 。</p>
<p> 最终你得到的可能是这样的 UI:</p>
<p><img src="https://raw.githubusercontent.com/tulios/json-viewer/master/screenshot.png" alt=""></p>
<p> 好了,其实还有很多输出调试工具,比如 symfony 的 <a href="https://github.com/symfony/var-dumper" target="_blank" rel="external">var-dumper</a> 既能输出 HTML 的调试信息,又能在 cli 下输出带格式的调试信息。还有 debug helper <a href="https://github.com/raveren/kint/" target="_blank" rel="external">Kint</a> 、Yii 的 <a href="https://github.com/malyshev/yii-debug-toolbar" target="_blank" rel="external">yii-debug-toolbar</a> 以及强大的 XDebug 。</p>
<hr>
</content>
<summary type="html">
<p>PHP 调试时有很多种方法,其中最简单的无非是 echo 与 var_dump 、 print_r 以及 debug_backtrace 了。这篇文章要介绍的是平常用的比较多的,适合在调 HTTP 接口以及平常开发中需要输出变量内容的情况。其实在 IDE 中比较适合使用 X
</summary>
<category term="PHP" scheme="http://blog.jayxhj.com/categories/php/"/>
</entry>
<entry>
<title>FaceBook 命令行 UI PathPicker</title>
<link href="http://blog.jayxhj.com/2016/01/facebook-command-line-ui-pathpicker/"/>
<id>http://blog.jayxhj.com/2016/01/facebook-command-line-ui-pathpicker/</id>
<published>2016-01-17T09:40:38.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><p>早先在逛 Facebook 后端开源项目时看到了一个命令行工具 PathPicker ,安装后试用了一下感觉还不错,恰好项目开发时在使用 Git ,发现结合 PathPicker 后免去了很多麻烦。</p>
<p>试想以下场景:</p>
<ol>
<li><p>你完成一个功能会涉及到不少文件,你得小心翼翼地一个个文件选择,防止不必要的文件出现在同一个待提交列表中;</p>
</li>
<li><p>你改了很多文件,然后执行了 <code>git add .</code> ,这时你有两个选择,要么 <code>git rest</code> 不想在这次提交中包含的文件,要么一个个选择,然后 添加到 <code>git commit</code> 的后面。</p>
</li>
</ol>
<p>于是 PathPicker 出现了,专为解决此种费力不讨好的事情,UI 选择器,命令行,一起上。</p>
<h2 id="介绍"><a href="# 介绍" class="headerlink" title="介绍"></a>介绍</h2><p>FaceBook PathPicker 是一个简单的命令行工具,用来处理选择 bash 文件输出时的问题。</p>
<p>PathPicker 做以下事情:</p>
<ul>
<li>通过管道处理输入中所有表现出文本特性的文件</li>
<li>在一个方便选择的 UI 容器里展现管道输入</li>
<li><p>然后你可以对输入做以下事情:</p>
<ul>
<li>用你最喜欢的编辑器处理选择的所有文件</li>
<li>利用输入执行任意命令</li>
</ul>
</li>
</ul>
<p>下面通过一段视频来了解 PathPicker:</p>
<script type="text/javascript" src="https://asciinema.org/a/19519.js" id="asciicast-19519" async>
</script>
<h2 id="PathPicker- 是怎么运行的"><a href="#PathPicker- 是怎么运行的" class="headerlink" title="PathPicker 是怎么运行的"></a>PathPicker 是怎么运行的</h2><p>PathPicker 是 bash 脚本和一些 Python 模块 结合后的产物。 它主要有以下三个步骤:</p>
<ol>
<li>首先在 bash 脚本中,它将所有的标准输入重定向到一个 Python 模块,然后通过解析取出其中所包含的文件名候选列表。每个候选文件名都将被用来与文件系统做比对确保其存在,再然后,会把处理结果保存至临时文件并终止 python 脚本的运行。</li>
<li>第二部,bash 脚本会切换至命令行输入模式,并且另一个 python 模块会读取出已保存的条目并使用 <code>curses</code> 在一个 UI 选择器展现它们。</li>
<li>最后,python 脚本会输出一条命令至 bash 文件,最终被原始的 bash 脚本解析执行。</li>
</ol>
<h2 id="使用示例"><a href="# 使用示例" class="headerlink" title="使用示例"></a>使用示例 </h2><p> 当改了很多文件需要提交提交时,查看当前状态 <code>git status</code></p>
<p><img src="//blog.jayxhj.com/images/PathPicker-git-add-before.jpg" alt="git add before"></p>
<p>将输出重定向至 PathPicker <code>git stataus | fpp</code></p>
<p><img src="//blog.jayxhj.com/images/PathPicker-git-statusfpp-after.png" alt="git status|fpp"></p>
<ul>
<li>已选中为绿色</li>
<li>已选中且当前光标悬停显示为红色</li>
<li>未选中且当前光标悬停显示为蓝色</li>
<li>无法选中的光标无法跳到此项,显示为纯文本</li>
</ul>
<p>提交选定的文件 <code>git commit $F</code></p>
<p><img src="//blog.jayxhj.com/images/PathPicker-git-commit-F.png" alt="git commit $F"></p>
<p>PathPicker 官网见 <a href="http://facebook.github.io/PathPicker/" target="_blank" rel="external">http://facebook.github.io/PathPicker/</a></p>
<hr>
</content>
<summary type="html">
<p>早先在逛 Facebook 后端开源项目时看到了一个命令行工具 PathPicker ,安装后试用了一下感觉还不错,恰好项目开发时在使用 Git ,发现结合 PathPicker 后免去了很多麻烦。</p>
<p>试想以下场景:</p>
<ol>
<li><p>你完成一个功
</summary>
<category term="工具技巧" scheme="http://blog.jayxhj.com/categories/%E5%B7%A5%E5%85%B7%E6%8A%80%E5%B7%A7/"/>
</entry>
<entry>
<title>Git 常用操作</title>
<link href="http://blog.jayxhj.com/2015/12/git-commands/"/>
<id>http://blog.jayxhj.com/2015/12/git-commands/</id>
<published>2015-12-10T12:15:55.000Z</published>
<updated>2018-02-06T13:02:10.000Z</updated>
<content type="html"><hr>
<h2 id="Git- 忽略已追踪文件的修改"><a href="#Git- 忽略已追踪文件的修改" class="headerlink" title="Git 忽略已追踪文件的修改"></a>Git 忽略已追踪文件的修改</h2><p><code>git update-index –assume-unchanged /path/to/file</code></p>
<h2 id="Git- 取消忽略文件的修改 - 再次追踪此文件"><a href="#Git- 取消忽略文件的修改 - 再次追踪此文件" class="headerlink" title="Git 取消忽略文件的修改(再次追踪此文件)"></a>Git 取消忽略文件的修改(再次追踪此文件)</h2><p><code>git update-index --no-assume-unchanged /path/to/file</code></p>
<h2 id="导入 -Git- 仓库至 -Bitbucket"><a href="# 导入 -Git- 仓库至 -Bitbucket" class="headerlink" title="导入 Git 仓库至 Bitbucket"></a>导入 Git 仓库至 Bitbucket</h2><ol>
<li>create repository in <a href="https://bitbucket.org/repo/create" target="_blank" rel="external">Bitbucket</a></li>
<li>do upstreaming in local</li>
</ol>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><span class="line"><span class="built_in">cd</span> /path/to/my/repo</span><br><span class="line">git remote add origin https://username@bitbucket.org/username/reponame.git</span><br><span class="line">git push -u origin --all <span class="comment"># pushes up the repo and its refs for the first time</span></span><br><span class="line">git push -u origin --tags <span class="comment"># pushes up any tags`</span></span><br></pre></td></tr></table></figure>
<h2 id="分支重命名"><a href="# 分支重命名" class="headerlink" title="分支重命名"></a>分支重命名</h2><ol>
<li>修改当前分支 <code>git branch -m &lt;newname&gt;</code></li>
<li>未提交至 remote <code>git branch -m &lt;oldname&gt; &lt;newname&gt;</code></li>
<li>已提交至 remote 需新加分支 <code>git push origin &lt;newname&gt;:&lt;newname&gt;</code> 并删除旧的远程分支 <code>git push origin :&lt;newname&gt;</code></li>
</ol>
<h2 id="从某个分支上线"><a href="# 从某个分支上线" class="headerlink" title="从某个分支上线"></a>从某个分支上线 </h2><p> 场景:当需要从某个分支的指定版本上线,又需要保留当前分支的修改</p>
<ol>
<li>checkout 到指定分支 <code>git checkout [revision]</code> [revision] 代表指定 commit 的 hash</li>
<li>以当前 commit 创建分支 <code>git checkout -b [branchName]</code> [branchName] 为分支名</li>
<li>推送至远程 <code>git push origin branchName:branchName</code></li>
</ol>
<h2 id="添加多个远程分支"><a href="# 添加多个远程分支" class="headerlink" title="添加多个远程分支"></a>添加多个远程分支</h2><figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git remote <span class="built_in">set</span>-url origin --push --add &lt;a remote&gt;</span><br><span class="line">git remote <span class="built_in">set</span>-url origin --push --add &lt;another remote&gt;</span><br></pre></td></tr></table></figure>
<h2 id="错误 -merge- 了某个分支后撤销"><a href="# 错误 -merge- 了某个分支后撤销" class="headerlink" title="错误 merge 了某个分支后撤销"></a>错误 merge 了某个分支后撤销 </h2><p> 场景:当某个功能开发并自测完毕,需要 merge 到测试环境进行测试时,如果不小心 merge 错了分支,这时候需要撤销这个 merge 。如果是使用 Pull Request 进行分支合并的话,直接 close 就可以了,但如果是使用命令行进行操作的话,本地的分支实际上已经与待 merge 的分支进行合并了,需要回到 merge 前。</p>
<p>只需要两个一个操作:找到 merge 前的 commit hash ,并 reset –hard</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">git reset --hard &lt;commit <span class="built_in">hash</span>&gt;</span><br></pre></td></tr></table></figure>
<h2 id="强制提交,覆盖错误的提交"><a href="# 强制提交,覆盖错误的提交" class="headerlink" title="强制提交,覆盖错误的提交"></a>强制提交,覆盖错误的提交 </h2><p> 场景:如果不小心将某个提交推送到远程,又不想这个提交留在历史里,就需要强制覆盖远程。</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line">git checkout -b &lt;branchName&gt; &lt;commit <span class="built_in">hash</span>&gt; <span class="comment"># commit hash 为错误提交的前一个提交 hash</span></span><br><span class="line">git push origin branchName <span class="operator">-f</span> <span class="comment"># 强制提交</span></span><br></pre></td></tr></table></figure>
<p>备注:对于个人开发或者比较私密的信息不小心提交了才建议这么做,否则还是直接 revert 错误的提交。</p>
</content>
<summary type="html">
<hr>
<h2 id="Git- 忽略已追踪文件的修改"><a href="#Git- 忽略已追踪文件的修改" class="headerlink" title="Git 忽略已追踪文件的修改"></a>Git 忽略已追踪文件的修改</h2><p><code>git updat