调试线上 JS 的几种姿势

线上某个页面出问题时,我们需要查看日志,需要看页面表现,需要定位问题在哪里以便及时修复。一般后端会通过打日志的形式来确定 bug 的起源,前端就不一样了,上线前 JS 经过混淆、压缩、合并,代码已经不是原来的样子了,无疑给调试带来了困难。那么如何定位问题呢?本文希望从两方面解释,一方面是如何去复现问题,第二步是根据第一步的信息,如何更快更科学地 debug 。

问题的定位

本地复现、调试、解决、上线

有些问题其实不用在线上调试,如果场景容易满足,case 容易在线下构造,那么直接在本地改就好了,这种方式比较适合简单的问题,无需特定场景和环境,bug 就能修复。

使用 Chrome 浏览器进行 debug

调试页面的问题,使用最多的浏览器就是 Chrome 和 Firefox ,而我习惯于使用 Chrome ,就介绍一下使用 Chrome debug JS 的姿势。

Google 官方给出了 DevTools 的介绍,里面有很多案例介绍及使用方法介绍,以博客的形式给出,很方便。

下面介绍的内容实际上就是从 Set Breakpoints 这个小节学习的。摘录某个调试方法。

设置 DOM 改变时的断点

你代码的某个地方出现了 bug ,它错误地改变、删除或者增加一个 DOM 点。DevTools 提供了一个快速找到 bug 根源的工具:针对 DOM 改变的断点。

DevTools 让你可以在某个节点上打断点,让你不用手动地在代码里四处寻找是哪里导致了 DOM 的变化。无论在哪个节点,或是节点的子节点,当它们背添加、删除、改变时,DevTools 都能让页面暂停,然后把你带到引起 DOM 改变的具体代码行。

添加 DOM 改变的断点 的方式: 右键单击要监控的节点,选择要监控的行为,当节点发生改变时,JS 将停止执行,页面将显示引起监控的 DOM 节点改变的 JS 代码行。另外,从 Call Stack 可以查看引发当前监控 DOM 发生改变的 JS 调用栈。

监控节点断点类型:

以下是各个类型的 DOM 节点改变时的断点详细说明:

  • Subtree modifications .

    当当前选择的节点的子节点被删除、添加、或者内容发生变化时触发。当子节点的属性改变或者当前节点发生改变时不会触发。

  • Attributes modifications.

    当当前节点的属性被添加或者删除,或者当前节点的某个属性的值发生变化时触发。

  • Node Removal.

    当当前节点被删除时触发。

设置 DOM change 相关的断点最大的好处在于,能准确定位到是哪里引起了改变,缺点在于,当出现不对 DOM 产生改变的 bug 时,这种方法就失效了。这种情况,就得确定 bug 的代码范围,再使用断点逐步追踪。

JS 的调试

众所周知,静态文件会存在缓存问题。一个页面的加载,如果包含多个 JS ,如何定位到具体的 JS 以及如何使用修改后的 JS 来查看页面效果就很是问题了。下面介绍的方式可以解决前面的问题,原理都是使用本地的 JS 做代理,来接管线上的 JS ,这样线上的调试就转移到本地了。由于线上的 JS 一般都经过压缩、混淆、合并,故最好本地有合成线上 JS 的子 JS ,下面介绍的方式是直接用本地的 JS 替代线上的 JS ,如果是前面说的那种情况,用合并后的 JS 替换线上 JS 即可。

使用代理软件

macOS 系统下,Web 调试代理软件一般使用 Charles , 使用代理软件的好处是,网络请求的所有资源,请求的细节,都能记录下来,另外它能作为系统代理接管浏览器的网络访问,将这些请求记录下来,是 Web debugging 的利器。

下面要介绍的方式就是 Charles 的代理功能,使用本地的 JS 代理线上的 JS 。

  1. 设置 Charles 为系统代理,代理 Chrome 的网络连接。

    这个步骤,需要开启 Charles ,并设置 Charles 为系统代理。Firefox 可以直接设置,Chrome 网络设置实际上就是修改的系统的网络设置,步骤为 系统偏好设置 -> 网络 -> 高级 -> 代理 一图胜千言 系统代理

  2. Map Local

    使用 Chrome 请求网页, 即可在 Charles 中查看到当前网页所有资源对应的请求,将某个 JS 文件映射到要用作代理的本地 JS 文件,或者粗暴点,直接代理目录,就可以代理文件夹下的所有 JS 了。

    map local

这个方式下,当在本地修改 JS 后,能直接在线上环境看到效果,最重要的是,免去了修改 JS 后上线 JS 不生效的问题。归根结底,本地修改,上线再看效果这种原始的方式,本该淘汰。

但此方式有个缺点,需要来回切换,你需要切换到编辑器或者 IDE 改好后再回 Chrome 刷新页面看效果,而且不能实时编辑,下面的使用 Chrome workspace 的方式无疑是效果最好而且最省时省力的。

使用 Chrome workspace

此方式实际上也是使用本地的文件做代理,但区别于其他方式的是,它与 Chrome 无缝集成,是 Chrome DevTools 提供的功能,支持断点,支持将修改持久化到本地。下面介绍配置方式。

  1. 将本地文件添加到 workspace

    • 开启调试面板,切换至 Sources 选项卡
    • 右击选择 Add Folder to Workspace Add Folder to Workspace
    • 选择要代理的本地的文件夹
    • 点击地址栏下方弹出来的窗口,选择 允许 ,Chrome 才能获得访问本地文件的权限
  2. Map to File System Resource

    选择要代理的文件,Chrome 将自动列出可以映射的文件,此时就建立了远程文件与本地文件的映射。 Map to File System Resource

设置好之后当 debug 好之后,可以直接将修改持久化到本地的文件,再也不用复制粘贴了。

其他用法见官方介绍:https://developers.google.com/web/tools/setup/setup-workflow

将文件内的 tab 转化为空格

tab 与 space 孰好孰坏这个问题是程序界的圣战之一。StackExchange 旗下的 programmers 就有关于这个问题的 讨论 , 没有必要大打出手,倒是可以了解下各种情况下使用这两者的差异及各自的优势。不过本篇不准备讨论这个问题,本篇只想解决日常开发中的需求:将 tab 转化为空格。

总结了一下,大概有以下几种思路:

  1. 通过程序进行替换
  2. 通过重定向输入替换,生成新文件

第一个思路适合大批量的替换,也适合大文件的替换,第二个思路实际上是读取文件替换后生成新文件,和第一种差别其实不大,但是很多软件支持指定参数,就非常适合通过管道及重定向来进行替换了。

通过 vim

通过配置去格式化

在 ~/.vimrc 中加入以下配置

1
set tabstop=4
set shiftwidth=4
set expandtab

打开要格式化的文件,运行 :retab 即可格式化。

或者一行搞定::set tabstop=4 shiftwidth=4 expandtab | :retab

这种配置方式对所有的文件都生效,如果需要添加例外,添加下面的配置即可 autocmd FileType make setlocal noexpandtab FileType 后接文件类型名称。

这种方式是通过配置的方式来格式化文本,定制性比较高。

通过正则替换

打开文件,直接运行下面的命令即可::%s/\t/ /g

通过 expand

由于 expand 是通过读取文件或标准输入并重定向至标准输出,故要达到更改的效果,需重新写入原文件。

1
expand -t 4 input > output # 间隔默认为 8

通过 col

原理同 expand :

1
cat -vt tab_file | col -x > output

通过 sed

大杀器登场,通过 sed 来处理文本替换这种事情无疑是科学有效又简洁的姿势。

1
sed -iE 's/\t/    /g' file # 从头换到底

不过在我看来,为什么这么做,比怎么做到更有价值,网上的讨论还是很多的,在我看来最主要的是习惯问题,以及涉及到团队合作时,统一格式及风格,再就是涉及到文件交换及接口约定时,使用符合特定领域约定俗成的规范。基本就是这样的原则。

看看西乔给的问卷及统计结果 缩进圣战的统计结果

在 PHPStorm 中配置 Xdebug

使用 Chrome 扩展程序 JSON Viewer 进行调试 这片文章中我曾介绍过,使用 echo json_encode() 的方式进行调试,再配合 Chrome 浏览器插件 JSON Viewer (建议使用 JSON Handler 代替)。

调试方式的对比

手动调试的方式实际上局限性很大,缺点很明显:

  1. 复杂的程序变量的中间状态无法跟踪,只能得到最终结果
  2. 要 debug 的变量或者对象较多时不方便打印
  3. 调试代码与程序代码混杂在一起,容易出错,而且来回切换成本还挺高,效率上就低多了

上面的缺点中,

1 可以通过 Xdebug 的单步调试解决,通过打断点,一步步追踪,可以深入某个 function 或者一步步执行,来查看变量的整个状态变化。

2 这个缺点可以通过 error_log() 函数写入文件中,再配合 tail -f log_file 来调试,当然在 Xdebug 中也能一步步查看变量状态。

3 也是我决定用 Xdebug 的原因,打断点很方便管理,不用的断点暂时反选,这样可以在需要启用时启用,而且无需写调试代码,这种调试方式是非侵入式的。

开发中的痛点及思考

以上是我在开发过程中遇到的痛点,而解决方法很简单:

  1. 类与类之间低耦合,这样可以将 bug 缩小范围,且代码也更健壮,方便后续修改
  2. SOLID 原则
  3. 引入单元测试覆盖大部分的功能及类的测试

其中 2 是需要在类的设计上下功夫的,需要长时间的代码编写与逐步改善,1 则可以使用设计模式来解决,3 则可以让你将注意力放在各个功能的衔接点上,通过上面的三个方法,基本能解决平常的小 bug 了。

下面来介绍 debug 工具 Xdebug ,并介绍如何在 PHPStorm 中配置使用。

Xdebug 的安装与配置

Xdebug 有很多安装的方式,这个页面 https://xdebug.org/docs/install 给出了常用的安装方式。 另外有个更好的页面,给出了一步步的步骤,https://xdebug.org/wizard.php ,会告诉你如何编译安装指定的版本。

安装步骤如下:

  1. 下载对应的 Xdebug 压缩包;
  2. 编译

    1
    2
    3
    4
    cd /path/to/xdebug
    phpize
    ./configure
    make
  3. 复制 so 文件到扩展目录

  4. 修改 php.ini 加上以下配置

    1
    2
    3
    4
    5
    6
    [Xdebug]
    zend_extension = /path/to/extensions/xdebug.so
    xdebug.remote_enable=1
    xdebug.profiler_enable=1
    xdebug.remote_port=9000
    xdebug.profiler_output_dir= /tmp/xdebug

phpize 命令是用来准备 PHP 扩展库的编译环境的。

使用 php -m 查看扩展是否加载,再重启 php-fpm 。

下面介绍如何在 PHPStorm 中集成 Xdebug 。

PHPStorm 里配置 Xdebug

PHPStorm 文档里介绍了如何配置 Xdebug https://www.jetbrains.com/help/phpstorm/10.0/configuring-xdebug.html

介绍下几个主要的步骤:

  1. 设置 PHP 解释器。位于 Preferences > Languages & Frameworks > PHP 右侧的 Interpreter ,点击 … 按钮,设置本地的 PHP 环境
  2. 配置 Xdebug 的行为。

    1. 配置 Debug Port ,让其与 php.ini 中的 xdebug.remote_port端口号相同。
    2. 接收 Xdebug 与 PHPStorm 的连接,Can accept external connections
  3. 监听外部的 debug 连接,开启这个选项 Run | Start Listening for PHP debug connections

通过上面的配置,就可以在 PHPStorm 运行 Xdebug 了。不过这还不够,需要配置具体的项目,这样 Xdebug 才知道如何运行,并在指定的断点处停止并通知 PHPStorm 。

配置实例运行具体的 debug 项目

下图中显示了如何配置某个 debug 项目。

debug configuration

选择 PHP Web Application 即可配置某个 web 项目,设置项目的 url 及当前配置的名称即可。

以上这些步骤走完,一个 web 项目的 Xdebug 配置就做好了,只需设置断点,再点击 Run -> Debug ‘你的 debug 项目名称’ ,即可开始 debug 之旅了。

Enjoy it.

PHP 发起 curl POST 请求时传递数组

使用 PHP 的 curl 可以发起 HTTP 外部请求,但是发起 POST 请求时,是无法直接传递数组的,从 curl 层面来说,也没有所谓的数组的概念,而更加通用也更合理的传递数据的格式其实是键值对(key value pair)。

那么,我们先得知道,为什么要传递数组。

HTTP 协议规定了 HTTP 请求的三个部分:状态行、请求头、消息主体。消息主体实际上是没有规定格式的。平常主要用到的几个请求头 Content-Type 为

1
2
3
4
application/x-www-form-urlencoded
multipart/form-data
application/json
text/xml

所以问题的答案很明白了,传递什么样的数据类型得看需要发送什么样的请求。

一个典型的 curl POST 请求是下面这样:

1
curl -X POST --data 'params[]=check1&params[]=check2' 'http://jayxhj.com/test/curl.php'

上面的请求将发送一个 Content-Type 为 application/x-www-form-urlencoded 的请求,请求的 body 为 params[]=check1&params[]=check2 ,在服务端,只需使用 $_POST 即可获取。

那么回到 curl ,我们只需设置 option 为 CURLOPT_POSTFIELDS 的 VALUE 为 key/value pair ,即可将数组以字符串的形式传递至服务端,并直接由 $_POST 获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<?php
$curl = curl_init();

curl_setopt($curl, CURLOPT_URL, 'http://jayxhj.com/test/curl.php');
curl_setopt($curl, CURLOPT_HEADER, 0);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);

$array = [
'jayxhj',
'pt'
];
$str = http_build_query($array);

curl_setopt($curl, CURLOPT_POSTFIELDS, $str);
$data = curl_exec($curl);

curl_close($curl);

var_dump($data);

常用 Chrome 扩展介绍及使用

Chrome 浏览器和 Firefox 浏览器为何大受欢迎,其中一个重要原因就是因为有数量巨大的扩展库,而 360 之类的浏览器内核也是基于 Chromium 内核和 IE 内核,因为这些扩展,可以让应用(浏览器就是应用)的功能变得无比丰富和强大。

这篇文章会列举本人平常使用的插件,并介绍常用的用法,但由于扩展使用的主观性太强,每个人都有不同的偏好,所以此篇会把重点放在扩展的使用上,并会不定时更新。扩展排名不分先后。

每个扩展介绍会包含其简要介绍,下载地址及基本用法,默认给出的是 Chrome webstore 的下载地址,如果有托管在 GitHub 会给出其托管地址。

Avatars for Github

一句话介绍:在 GitHub feed 页展示你关注人的头像。

地址:https://chrome.google.com/webstore/detail/avatars-for-github/pgjmdbklnfklcjfbonjfkdhaonlfogbb

开发者网站:https://github.com/anasnakawa/chrome-github-avatars

GitHub-feed-before

GitHub-feed-after

Axure RP Extension for Chrome

一句话介绍:web 版的 Axure 客户端。

地址:https://chrome.google.com/webstore/detail/dogkpdfcklifaemcdfbildhcofnopogp

开发者网站:http://www.axure.com/

使用:安装此插件后,即可在不安装 Axure 下的情况下,打开原型,直接在浏览器中运行。

Chrome Sniffer Plus

一句话介绍:网页下的类库探测器。

地址:https://chrome.google.com/webstore/detail/fhhdlnnepfjhlhilgmeepgkhjmhhhjkh

开发者网站:http://justjavac.com/

使用:探测当前网页正在使用的开源软件或者 JS 类库。其实一个网站或者应用,技术栈一般会涉及到很多的技术,这个扩展可以检测一些基本的开源类库,作为日常的探测够用了。

hosts 文件管理工具

一句话介绍:host 管理及切换工具。

地址:https://chrome.google.com/webstore/detail/kpfmckjjpabojdhlncnccfhkfhbmnjfi

开发者网站:https://github.com/gbk/chrome-hosts-manager

使用:主要用来进行 ip 与 host 之间的绑定,另外可以对域名 ip 进行分组,非常适合用来进行切换环境。另外一个主要功能就是显示当前 ip ,在没有挂代理的情况下,可以显示当前页面的服务器 ip 。

显示当前页面 ip

JSON Editor

一句话介绍:JSON 在线解析及编辑器。

地址:https://chrome.google.com/webstore/detail/lhkmoheomjbkfloacpgllgjcamhihfaj

开发者网站:http://jsoneditoronline.org/

使用:http://jsoneditoronline.org/ 的 chrome 扩展版本,可以用来修改 JSON 数据,并实时验证 JSON 数据的合法性,构造复杂的 JSON 数组、对象、字符串。

JSONView

一句话介绍:JSON 查看器。

地址:https://chrome.google.com/webstore/detail/chklaanhfefbnpoihckbnefhakgolnmc

开发者网站:https://github.com/gildas-lormeau/JSONView-for-Chrome

使用:适合用来查看 JSON ,但是由于在解析当前页面时,如果发现是 JSON ,会再次请求一次页面,所以不适合查看、测试对多次请求结果不一致的 api 。之前写过一篇文章,介绍的就是 使用 JSONView 配合进行 Debug

OneTab

一句话介绍:可以节省 95% 内存使用的标签页管理工具。

地址:https://chrome.google.com/webstore/detail/chphlpgkkbolifaimnlloiipkdnihall

开发者网站:http://one-tab.com/

用法:使用 OneTab 可以将暂时不需要处理的网页集中到一起,大大减小 Chrome 吃内存的问题。使用它可以对网页进行分组,可以随意更改标签页顺序,能分享自己的标签页给他人,并且让人放心的是,即使关闭浏览器,或者重启电脑, OneTab 里的内容还在,可以放心地使用,开发团队表示,后续将引入云端功能,保持多设备的同步,大快人心。使用 Alt + Shift + 1 即可呼出 OneTab 。

Proxy SwitchyOmega

一句话介绍:代理管理及切换工具。

地址:https://chrome.google.com/webstore/detail/padekgcemlokbadohgkifijomclgjgif

开发者网站:https://github.com/FelisCatus/SwitchyOmega

Search by Image (by Google)

一句话介绍:Google 搜图工具。

地址:https://chrome.google.com/webstore/detail/search-by-image-by-google/dajedkncpodkggklbegccjpmnglmnflm

开发者网站:https://chrome.google.com/webstore/category/ext/15-by-google

使用:选中某个图片,右键,点击 Search Google with this image,即可搜索与此图类似的图片。其实就是调用的 Google Images 的服务。国内百度的 以图搜图 做的也挺不错。

Wikiwand: Wikipedia Modernized

一句话介绍:更美的维基百科。

地址:http://www.wikiwand.com/#/install

开发者网站:http://www.wikiwand.com/

印象笔记·剪藏

一句话介绍: 智能归类、剪辑、收藏、悦读并提供关键字搜索功能的印象笔记工具。

地址:https://chrome.google.com/webstore/detail/evernote-web-clipper/pioclpoplcdbaefihamjohnefbikjilc

开发者网站:http://evernote.com/

动手开发自己的第一个 composer 包

composer 是 PHP 的依赖管理工具,本篇文章就来说明如何构建一个包,并提交到 Packagist ,这样别人就可以方便地通过 composer 使用你的包了。

开发 composer 包有以下几个步骤:

  1. 初始化 composer.json 文件
  2. 定义命名空间及包名
  3. 实现包需要实现的功能
  4. 提交到 GitHub
  5. 在 Packagist 注册包

初始化 composer.json 文件

安装好 composer 后即可在本地运行 composer init 通过交互式命令行设置 composer.json 。

下面介绍其中的几个属性,以及常规的设置:

  1. name

    此属性定义包名,以 / 隔开,前面的为供应商名字,后面为包名,供应商代表 Packagist 网站为开发者提供的唯一的名字,用来组织包以及防止命名冲突。所以提交时最好先访问 https://packagist.org/packages/yourvendorname 将 url 中的 yourvendorname 替换为你想要取的名字,如果页面没有 404 ,说明已经被注册了。
  2. license

    许可证。关于许可证,建议看两篇文章,开源项目 license 介绍 如何选择 license
  3. require

    安装当前包所需的依赖。只有所有依赖被安装当前包才会被安装。
  4. autoload

    此配置下主要是 PSR-4 或者 PSR-0 设置,更推荐使用 PSR-4 标准。

http://json-schema.org/ 上介绍了 JSON Schema 的定义以及各个语言对其各种功能的实现,有 validator 的实现,其中 JSON Schema Validator 是在线的验证服务。其实最简单的就是使用 composer validate composer.json 来验证文件是否是有错误。

这是我演示的设置 composer.json 的视频

项目结构

以我开发的 单点登录 SDK 为例,此项目基于 Laravel ,实现了站点接入单点登录系统的简单接入,应用只需在服务端注册并实现指定接口,即可接入 SSO 。

项目结构是典型的 MVC 结构,

1
.
└── geo
    └── geosso
        ├── LICENSE
        ├── README.md
        ├── composer.json
        └── src
            ├── Contracts
            ├── Http
            │   ├── Controllers
            │   ├── Middleware
            │   └── Requests
            ├── ParamsBean
            ├── Providers
            ├── Support
            └── config

12 directories

LICENSE、README.md 及 composer.json 是运行 tree -d 之后手工添加上去的。

项目根目录定义在 src 下,在 composer.json 中也有定义,这样当 composer 加载这个包时就知道如何通过命名空间去解析文件路径。

Http 目录代表请求响应,之下的 Controllers 表示合法请求的控制器,Middleware 代表请求的第一道关卡,通过中间件去拦截请求,Requests 去获取前端请求并对请求过滤。

Contracts 代表接口定义。ParamsBean 代表应用层与底层服务沟通时的参数封装,通过 Bean 去获取各个参数,而不是传递 array 使得调用一致,并且强制接口调用时做类型检测,可以很大程度上统一各层之间的参数传递。

Providers 代表 Laravel 的服务容器,通过服务容器,可以注册路由与配置,加载助手类,绑定接口与其实现。

Support 就是一些助手类,对常用的与逻辑无关的功能的封装,config 代表应用自己的配置,通过 config 可以方便地将配置设置并使用全局函数 config() 调用。

提交至 GitHub

按照前面的步骤,一个包就有了基本的骨架,接下来就是上传至 GitHub ,配置项目,集成持续集成服务,发布开源项目许可证。

GitHub 初始化项目时可以选择生成 .gitignore 文件,选择许可证,初始化 README.md 文件,切换至本地的项目目录后,按如下步骤即可将目录上传至 GitHub:

1
2
3
4
5
>git init # 初始化仓库
>git remote set-url origin --push --add git@github.com:jayxhj/geosso.git # 添加远程追踪仓库地址
> git add .
> git commit
> git push origin master

提交至 Packagist

Packagist 为 composer 默认获取包元数据信息的地址,从 Packagist 获取到元数据信息后,再从 GitHub 上拉取代码。因此,当把你开发的包上传至 GitHub 后还需要将其在 Packagist 注册,这样全世界的人都能通过 composer 去拉去你的代码了。

提交至 Packagist 只需三个步骤:

  1. 注册帐号
  2. https://packagist.org/packages/submit 提交开发包
  3. 设置 webhook 以便提交包更新后能及时地同步至 Packagist

自此,一个基本的包开发就结束了。通过 composer 来管理 PHP 的依赖,通过编写 composer package 去扩展自己的类库,通过引入其他的类库来填充自己的功能,就不用重复造轮子了。

获取当前日期在本月的第几周

获取当前日期在本月的第几周

使用 date() 函数可以获取某个日期的当前星期数,此星期数为当前日期在一整年中的星期数。假如要获取当前日期在当前月份的星期数,只需用当前日期的星期数减去当前月份第一天的星期数加 1 即可。

获取当前日期的星期数

1
2
<?php
echo date('W',time());

获取当前日期是本月的第几周

1
2
3
4
5
6
7
8
9
10
<?php
/**
* @param int $date 时间戳
* @return int 当前日期在本月的第几周
*
* */

function weekOfMonth($date) {
$firstOfMonth = strtotime(date("Y-m-01", $date));
return intval(date("W", $date)) - intval(date("W", $firstOfMonth)) + 1;
}

Lumen 常用开发技巧

密码加密与验证

加密

1
2
3
4
5
6
7
8
9
10
11
<?php
/**
* 密码入库加密
* @param string $password
* @return string
*
* */

public function passwordEncrypt($password)
{

return app('hash')->make($password);
}

密码验证

1
2
3
4
5
6
7
8
9
10
11
12
<?php
/**
* 密码验证
* @param string $password
* @param string $hashedPassword 加密后的密码
* @return bool
*
* */

public function passwordValidate($password, $hashedPassword)
{

return app('hash')->check($password, $hashedPassword);
}

查询数据库判断是否有记录

如果使用 Eloquent 的 Query Scopes ,查询时使用链式方法调用,通常是这么查询,$modelObj = Model::id($id)->get() 来查询指定条件的结果集。但是这么查出来的,实际上返回的是 Illuminate\Support\Collection 对象。那么下面的方法,比较适合判断查出来的结果是否存在。

1
2
3
4
5
6
7
8
9
10
<?php
$modelObj = Model::id($id)->get();

if (! $modelObj->isEmpty()) {
$modelObj->toArray();
}
// 或者这样
if ($modelObj->count()) {
$modelObj->toArray();
}

在 lumen 中设置日期与数据库时区

项目使用了 Laravel 的精简版 Lumen 来做 api 和 sso 的服务。今天在提交代码时发现,生成的 database migration 代码时间相差八个小时。看来是时区设置的问题,查找了一下,发现有好几个地方都有时区的设置,那就简单总结一下。

PHP 时区设置

PHP 里可以在 php.ini 中设置 date.timezone 选项来设置时区,也可以在脚本中动态制定时区,如使用 date_default_timezone_set() 来动态指定时区。使用脚本指定时会覆盖 php.ini 中的设置。时区列表可以使用 \DateTimeZone::listIdentifiers() 来获取。也可以到 timezonemap.h 来查看对应的可选项。

中国的时区设置可以使用以下选项:

1
2
3
4
5
6
7
8
9
{ "cst",   0,  28800, "Asia/Chongqing"                },
{ "cst", 0, 28800, "Asia/Chungking" },
{ "cst", 0, 28800, "Asia/Harbin" },
{ "cst", 0, 28800, "Asia/Macao" },
{ "cst", 0, 28800, "Asia/Macau" },
{ "cst", 0, 28800, "Asia/Shanghai" },
{ "cst", 0, 28800, "Asia/Taipei" },
{ "cst", 0, 28800, "PRC" },
{ "cst", 0, 28800, "ROC" },

lumen 时区设置

lumen 的时区设置有数据库操作的时区设置以及与使用时间相关的函数的时区设置。

日期相关设置

在 lumen 中应用程序的入口,会 new 一个 Application 实例,这个实例会读取 env 配置文件中的 APP_TIMEZONE 配置,若没有配置则会使用 UTC 时间。Application 位于 /path/to/project/vendor/laravel/lumen-framework/src/Application.php 。所以最好是在 .env 中设置时区 APP_TIMEZONE=PRC

数据库

数据库的时区设置可以在 config 的 database 文件中设置。database 文件分两个,一个是框架级别,一个是项目级别。框架级别的文件位于 /path/to/project/vendor/laravel/lumen-framework/config/database.php ,而项目级别的文件位于 /path/to/project/config/database.php

此配置是和 MySQL 相关的,所以关键字当然是去 MySQL 中找。在 MySQL 时区设置 文档中可以看到,有以下三种配置

1
2
3
1. SYSTEM 表示与系统时区相同
2. '+10:00' or '-6:00' 表示与 UTC 时间的一个偏移量
3. 'Europe/Helsinki', 'US/Eastern', or 'MET' 表示命名时区,命名时区必须在 mysql 库下的 time_zone_name 有注册

导入时区的命令为

1
shell> mysql_tzinfo_to_sql /usr/share/zoneinfo | mysql -u root --database=mysql -p

建议是在项目级别设置时区。在 .env 中指定 DB_TIMEZONE=+08:00 和在 database 中指定 'timezone' => env('DB_TIMEZONE', '+08:00') 均可。

总结

时区设置分为 PHP 时区设置和 MySQL 的时区设置。PHP 时区设置涉及到 php.ini 配置以及运行时配置,而在 lumen 中还涉及到环境变量的配置。lumen 中既可以在 config 目录下的文件中指定,也可以在 .env 文件中指定。建议既指定 PHP 的配置又指定环境变量的配置,环境变量的配置可供整个应用程序使用,是项目级别的共享配置。