PHP的错误和异常处理总结

PHP的错误和异常处理总结

PHP内置了一批与错误和异常处理相关的函数,本文会对其中部分函数进行详细说明。

set_error_handlerrestore_error_handler

set_error_handler可以设定当程序出现错误时,将对应的错误交给用户自定义的逻辑来处理。PHP

但是并不是所有的错误都可以被set_error_handler所指定的处理逻辑捕获,例如:E_ERRORE_PARSEE_CORE_ERRORE_CORE_WARNINGE_COMPILE_ERRORE_COMPILE_WARNING,简单来说就是原本就会导致程序终止的错误都无法被捕获。

set_error_handler只能设置一个错误处理逻辑,多次调用set_error_handler只有最后一次的那生效:http://leo108.com/pid-2216.asp

set_error_handler(function($errno, $errstr){
    echo 'catch error1';
});
set_error_handler(function($errno, $errstr){
    echo 'catch error2';
});
trigger_error('something error');

输出的是

PHP

catch error2

在编写一个第三方代码库的时候,如果希望能够捕获代码库中的错误,又不影响调用方对错误的处理,可以使用restore_error_handler函数:

function third_party_function() {
	//第三方类库的错误处理逻辑
	set_error_handler(function() {
		//一些代码
	});
	//该第三方类库的逻辑
	//一些代码
	//复原错误处理逻辑
	restore_error_handler();
}

这样,第三方代码库中出现的错误就会被第三方代码库的错误处理逻辑捕获,而在这个第三方代码库之外的错误还是由原本的逻辑处理。推酷是个无耻的网站

如果错误处理逻辑的返回值是false,则PHP会调用内置的错误处理逻辑(例如根据error_reporting的值判断是否打印错误信息,写错误日志等);而返回true则不会调用内置处理逻辑,在执行完自定义的错误处理逻辑之后,会返回触发错误的地方继续往下执行。http://leo108.com/pid-2216.asp

error_reporting以及@符号不会影响错误处理逻辑的调用:http://leo108.com/pid-2216.asp

error_eporting(0);
set_error_handler(function($errno, $errstr){
    echo 'catch error';
});
@trigger_error('something error');

上述代码会输出:

leo108's blog

catch error

对于无法被set_error_handler捕获的错误常见的只有E_ERRORE_PARSE两个。前者可能是内存使用超过设定的值、实例化一个不存在的类或者是调用一个不存在的函数;后者通常是语法解析错误例如漏写一个分号。对于E_PARSE,由于是语法层面的错误,所以没有办法去捕获处理。但对于E_ERROR则可以通过register_shutdown_function以及error_get_last两个函数来处理,示例代码如下:

PHP

error_reporting(0);
register_shutdown_function(function() {
 	$error = error_get_last();
 	if ($error != null && $error['type'] == E_ERROR) {
		echo "fatal error catched:" . var_export($error, true);
 	}
});

new test();

输出:leo108's blog

fatal error catched:array (

‘type’ => 1,

‘message’ => ‘Class ‘test’ not found’,

‘file’ => ‘/tmp/error.php’,PHP的错误和异常处理总结

‘line’ => 20,异常

)

PHP的错误和异常处理总结

set_exception_handlerrestore_exception_handler

这两个函数和set_error_handler的两个函数差不多,同样是只能注册一个异常处理逻辑,多次注册只有最后一个生效;可以通过restore_exception_handler来恢复之前的异常处理逻辑。

PHP

一些小实验

在错误处理逻辑中触发错误

set_error_handler(function($errno, $errstr){
    echo 'catch error:' . $errstr . PHP_EOL;
    trigger_error('error in error handler');
});
trigger_error('origin error');

输出:PHP的错误和异常处理总结

catch error:origin error

PHP

PHP Notice: error in error handler in /tmp/error.php on line 15

结论:在错误处理逻辑中的错误是无法被再次捕获。

leo108's blog

在异常处理逻辑中抛出异常

set_exception_handler(function(Exception $e){
    echo 'catch exception:' . $e->getMessage() . PHP_EOL;
    throw new Exception('exception in exception handler');
});
throw new Exception('origin exception');

输出:

leo108's blog

catch exception:origin exception

PHP的错误和异常处理总结

PHP Fatal error: Uncaught exception ‘Exception’ with message ‘exception in exception handler’ in /tmp/error.php:15

PHP

Stack trace:推酷是个无耻的网站

0 [internal function]: {closure}(Object(Exception))

1 [main]

thrown in /tmp/error.php on line 15PHP

结论:在异常处理逻辑中抛出的异常不会被捕获

PHP的错误和异常处理总结

同时定义了异常和错误处理逻辑,在错误处理逻辑中抛出异常,在异常处理逻辑中触发错误

set_exception_handler(function(Exception $e){
    echo 'catch exception:' . $e->getMessage() . PHP_EOL;
    trigger_error('error in exception handler');
});
set_error_handler(function($errno, $errstr){
    echo 'catch error:' . $errstr . PHP_EOL;
    throw new Exception('exception in error handler');
});

外部触发了错误

trigger_error('origin error');

输出:

http://leo108.com

catch error:origin error

PHP

catch exception:exception in error handler

catch error:error in exception handler

PHP Fatal error: Uncaught exception ‘Exception’ with message ‘exception in error handler’ in /tmp/error.php:9

Stack trace:

PHP的错误和异常处理总结

0 [internal function]: {closure}(1024, ‘error in except…’, ‘/tmp/error.php’, 5, Array)

1 /tmp/error.php(5): triggererror(‘error in except…’)

2 [internal function]: {closure}(Object(Exception))

3 [main]

thrown in /tmp/error.php on line 9

结论:调用了两次错误处理逻辑,一次异常处理逻辑。异常

外部抛出异常

throw new Exception('origin exception');

输出:

PHP

catch exception:origin exceptionPHP

catch error:error in exception handler

PHP Fatal error: Uncaught exception ‘Exception’ with message ‘exception in error handler’ in /tmp/error.php:9

Stack trace:http://leo108.com/pid-2216.asp

0 [internal function]: {closure}(1024, ‘error in except…’, ‘/tmp/error.php’, 5, Array)

1 /tmp/error.php(5): triggererror(‘error in except…’)

2 [internal function]: {closure}(Object(Exception))

3 [main]

thrown in /tmp/error.php on line 9http://leo108.com/pid-2216.asp

结论:异常处理逻辑和错误处理逻辑各被调用一次。leo108's blog

Centos下使用mock构建rpm

在centos下执行rpmbuild -ba package.spec的时候,通常会要求系统也安装对应的依赖包,这样就会导致系统装上许多没用的软件包,占用空间。于是搜索了一下,发现可以通过mock命令来解决这个问题。

rpmbuild

首先是安装mock,由于mock是在epel仓库里的,所以还需要先装epel仓库

yum -y install epel-release
yum -y install mock

通常情况下使用rpmbuild会新开一个用户,比如builder,这样就不会污染系统环境。我们需要把builder用户加入mock用户组

leo108's blog

usermod -a -G mock builder

mock下使用rebuild需要src.rpm文件,所以如果只有.spec文件的话,需要先生成src.rpm文件http://leo108.com/pid-2207.asp

rpmbuild -bs package.spec

这样在SRPM目录下就会生成一个src.rpm文件了,然后就可以通过mock命令来rebuild rpm文件

推酷是个无耻的网站

首先需要初始化mock环境,在/etc/mock文件夹下有各个环境的配置文件,比如centos 6就是epel-6-x86_64,初始化命令就是:

http://leo108.com

mock -r epel-6-x86_64 --init

初始化完毕之后就可以开始构建了Centos下使用mock构建rpm

mock -r epel-6-x86_64 rebuild package-1.1-1.src.rpm

构建完毕,rpm文件会存放在/var/lib/mock/epel-6-x86_64/result目录下。当然我们可以通过–resultdir参数来指定rpm文件的生成目录http://leo108.com/pid-2207.asp

mock -r epel-6-x86_64 rebuild package-1.1-1.src.rpm --resultdir=/home/builder/rpms

最后执行clean命令清理环境

rpmbuild

mock -r epel-6-x86_64 --clean

 

Centos下使用mock构建rpm

Symfony支持多个站点(应用)

对于一个网页系统来说,通常会需要面向用户的站点和面向管理员的站点,有的甚至还需要面向App的api站点。这些站点拥有不同的域名,但却共享核心业务逻辑。Symfony支持多个站点(应用)

Symfony的标准发行版只支持一个站点,虽然可以通过路由系统中的Host配置,根据不同的域名使用不同的路由规则,这样也可以实现类似多站点的功能,但缺点也非常明显:leo108's blog

  • 如果希望某个Service在不同的站点有不同的表现,就没办法实现(DI不能直接注入Request)。
  • 静态文件没办法很好拆分开来
  • 每个页面请求都需要加载所有站点的配置(bundle、路由规则、Service等等),影响性能
  • 不同的站点的异常处理逻辑不同(例如对于NotFoundHttpException,在Api站点可能需要输出一个json串而在网页端需要输出一个404页面)

经过搜索,发现也有人有相同的困惑,也给出了一个初步的解决方案。但是还是有一些细节方面的问题,比如标准发行版自带的Composer post-install-cmd/post-update-cmd(清文件缓存、生成bootstrap.cache.php、发布静态文件到web根目录等)不能正常使用。那篇文章只是通过软链解决了bootstrap.cache.php的问题,但并没有提到清文件缓存等。

http://leo108.com

这个问题只能自己写代码来解决了,新建一个composer项目,依赖于sensio/distribution-bundle,新建一个ScriptHandler类,代码如下:leo108's blog

namespace Dreamore\DreamoreBundle\Composer;

use Composer\Script\CommandEvent;
use Sensio\Bundle\DistributionBundle\Composer\ScriptHandler as Base;

class ScriptHandler extends Base
{
    /**
     * Composer variables are declared static so that an event could update
     * a composer.json and set new options, making them immediately available
     * to forthcoming listeners.
     */
    protected static $options = array(
        'dm-apps'           => array(),
        'dm-assets-install' => 'hard',
        'dm-cache-warmup'   => false,
    );

    /**
     * Builds the bootstrap file.
     *
     * The bootstrap file contains PHP file that are always needed by the application.
     * It speeds up the application bootstrapping.
     *
     * @param $event CommandEvent A instance
     */
    public static function buildBootstrap(CommandEvent $event)
    {
        $options = static::getOptions($event);
        foreach ($options['dm-apps'] as $config) {
            $bootstrapDir = $config['app-dir'];
            $autoloadDir  = $config['autoload-dir'];

            if (!static::hasDirectory($event, 'app-dir', $bootstrapDir, 'build bootstrap file')) {
                return;
            }

            if (!static::hasDirectory($event, 'autoload-dir', $autoloadDir, 'build bootstrap file')) {
                return;
            }

            static::executeBuildBootstrap($event, $bootstrapDir, $autoloadDir, $options['process-timeout']);
        }
    }

    /**
     * Clears the Symfony cache.
     *
     * @param $event CommandEvent A instance
     */
    public static function clearCache(CommandEvent $event)
    {
        $options = static::getOptions($event);
        foreach ($options['dm-apps'] as $config) {
            $consoleDir = $config['app-dir'];
            if (!static::hasDirectory($event, 'app-dir', $consoleDir, 'execute command')) {
                return;
            }

            $warmup = '';
            if (!$options['dm-cache-warmup']) {
                $warmup = ' --no-warmup';
            }

            static::executeCommand($event, $consoleDir, 'cache:clear'.$warmup, $options['process-timeout']);
        }
    }

    /**
     * Installs the assets under the web root directory.
     *
     * For better interoperability, assets are copied instead of symlinked by default.
     *
     * Even if symlinks work on Windows, this is only true on Windows Vista and later,
     * but then, only when running the console with admin rights or when disabling the
     * strict user permission checks (which can be done on Windows 7 but not on Windows
     * Vista).
     *
     * @param $event CommandEvent A instance
     */
    public static function installAssets(CommandEvent $event)
    {
        $options = static::getOptions($event);
        foreach ($options['dm-apps'] as $config) {
            $needAssets = isset($config['need-assets']) ? $config['need-assets'] : true;
            if (!$needAssets) {
                continue;
            }

            $consoleDir = $config['app-dir'];
            if (!static::hasDirectory($event, 'app-dir', $consoleDir, 'execute command')) {
                return;
            }

            $webDir = $config['web-dir'];

            $symlink = '';
            if ($options['dm-assets-install'] == 'symlink') {
                $symlink = '--symlink ';
            } elseif ($options['dm-assets-install'] == 'relative') {
                $symlink = '--symlink --relative ';
            }

            if (!static::hasDirectory($event, 'web-dir', $webDir, 'install assets')) {
                return;
            }

            static::executeCommand($event, $consoleDir, 'assets:install '.$symlink.escapeshellarg($webDir), $options['process-timeout']);
        }

    }

    protected static function getOptions(CommandEvent $event)
    {
        $options = array_merge(static::$options, $event->getComposer()->getPackage()->getExtra());
        $options['process-timeout'] = $event->getComposer()->getConfig()->get('process-timeout');

        return $options;
    }
}

这里就重写buildBootstrap\clearCache\installAssets这三个方法的逻辑,同时为了避免冲突,我重新命名了配置项,composer.json的配置如下:leo108's blog

"autoload": {
    "psr-4": {
        "": "src/"
    },
    "files": [
        "apps/api/ApiKernel.php",
        "apps/admin/AdminKernel.php",
        "apps/wap/WapKernel.php"
    ]
},
"scripts": {
    "post-install-cmd": [
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::buildBootstrap",
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::clearCache",
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::installAssets"
    ],
    "post-update-cmd": [
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::buildBootstrap",
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::clearCache",
        "Dreamore\\DreamoreBundle\\Composer\\ScriptHandler::installAssets"
    ]
},
"extra": {
    "dm-apps": [
        {
            "app-dir": "apps/api",
            "autoload-dir": "apps",
            "need-assets": false
        },
        {
            "app-dir": "apps/admin",
            "autoload-dir": "apps",
            "web-dir": "web/admin"
        },
        {
            "app-dir": "apps/wap",
            "autoload-dir": "apps",
            "web-dir": "web/wap"
        }
    ],
    "dm-assets-install": "relative"
}

autoload中的file加入各个站点的kernel文件,这样就不需要手动require了。leo108's blog

scripts替换成我们自己的ScriptHandler

leo108's blog

dm-apps是一个数组,每个站点一项,每个站点的配置有app-dir、autoload-dir、web-dir、need-assets,app-dir代表kernel文件所在的目录;autoload-dir代表autoload.php文件所在的目录,由于各个站点的autoload.php完全一致,所以我就把这个文件放到apps目录下,所有站点共享(因此需要修改每个站点的app.php app_dev.php和console这3个文件);web-dir代表发布静态文件的目标目录;need-assets代表是否需要发布静态文件(比如api这个站点就不需要发布静态文件)。

这样每次composer update或者composer install之后,我们的ScriptHandler就会对每一个站点执行生成bootstrap文件、清理文件缓存和发布静态文件的操作。

vagrant环境中symfony程序速度慢解决方案

最近在写一个symfony程序,最开始是直接在Mac下通过console server:run命令启动一个简单的web服务器来访问,但是Mac本身自带的php没有memcached扩展,所以就把这个程序放到vagrant中,然而发现访问的速度非常慢,一个极其简单的页面也需要消耗10秒左右,于是搜索了一下,发现之前有人遇到相同的问题http://leo108.com/pid-2196.asp

该文章中列出的几个方案:

  1. 使用vagrant1.2版本(目测不合适,现在都已经1.7+了)
  2. 使用NFS方式挂载目录
  3. vagrant虚拟机中的Vbox Guest Additions版本与virtual box版本一致(我当前的环境就是一致的)
  4. 使用opcache扩展(我当前的环境已经安装了apc)
  5. 关闭xdebug和xphrof扩展(我当前环境已经关闭)

看这情况,只能尝试一下NFS方式了,根据vagrant的文档配置,还好OS X自带了nfsd,省去了安装的麻烦,只需要修改vagrantfile即可。

在vagrantfile中增加两行:symfony

config.vm.network :private_network, type: :dhcp
config.vm.synced_folder ".", "/vagrant", type: "nfs"

然后重启vagrant虚拟机vagrant reload

由于启用NFS需要修改/etc/exports文件,所以在启动的过程中会要求输入OS X用户的密码,输入即可。

vagrant

再次尝试访问symfony程序,发现耗时已经降到200ms左右,完美解决。vagrant环境中symfony程序速度慢解决方案

PHP程序配置文件(最佳?)实践

最原始的方式

写php程序时会直接把数据库、缓存的连接信息放在config.php文件里。这样做有两个弊端,1.在开发调试时必须先把连接信息改成本地的,要提交代码时再改回远程的,麻烦,也容易遗漏。2.开发人员可以直接看到线上数据库的连接地址、账号、密码,不安全。leo108's blog

怎么办?

线上运行PHP程序时通常需要一个Http服务器,例如apache、nginx。以nginx为例,在配置文件中可以通过fastcgi_param指令来给PHP传递变量

fastcgi_param  DB_HOST  "192.168.1.1";

这样就可以在PHP代码里通过$_SERVER[‘DB_HOST’]来获取到对应的值。

所以开发环境和线上环境只要配好nginx的配置,就可以实现不改代码执行程序了。

leo108's blog

更进一步,可以传递一个标示当前环境的变量,例如

fastcgi_param  CODE_ENV  "production";

然后在程序里可以根据$_SERVER[‘CODE_ENV’]的不同来执行一些不同的逻辑(比如开发环境会打印所有错误信息而线上环境不显示)

这样完美了吗?

并没有,在实际应用中发现有两个问题:1.如果这个PHP程序不仅仅提供web服务,还提供了cli工具(例如数据库升级脚本),这个时候nginx传递的变量就过不来了。2.貌似不支持传递数组变量。PHP程序配置文件(最佳?)实践

解决思路

要同时支持web和cli,通过nginx配置传变量已经行不通了,那能不能走php自己的配置呢?

于是在php.ini的末尾加上如下配置:

[userconf]
userconf.db_host=127.0.0.1
userconf.db_name=test

重启php-fpm之后,发现通过ini_get(‘userconf.db_host’)取到的数据是空,于是详细查看了php的文档,发现对于自定义的配置项,需要通过get_cfg_var()函数来获得。经过测试,在web和cli模式下,通过get_cfg_var(‘userconf.db_host’)可以拿到正确的值。

再优化

但这个方案还是有弊端:1.修改配置需要重启php-fpm。2.虽然php.ini里面支持数组的数据,但是还是不够灵活,最好是能直接用php配置。

所以我们可以在php.ini里面只配置一个配置目录的路径,这个目录下放置各个程序的配置文件,php程序先从php.ini获取到这个目录的路径,再从这个目录下读取php格式的配置文件。示例代码:

http://leo108.com

$path = get_cfg_var('userconf.dir');
$conf = include($path . '/test.php');

这样配置文件是每次访问都会重新读取,变更时不需要重启php-fpm;而且不同的站点只要选择不同的配置文件名,就可以在一台服务器上共存。

leo108's blog

但这样还是有一个问题没有解决,那就是无法在同一台服务器上部署两个相同的站点,不过这个场景也不多,不解决也没关系。

vagrant centos升级内核版本、升级VBoxGuestAdditions版本

在vagrant的centos中,如果直接执行yum install kernel-devel,会提示”No matches found for: kernel-devel”,仔细观察了一下yum的输出,发现加载了一个versionlock的插件,于是猜测与这个插件有关,禁用了内核版本的更新,所以把这个插件禁用掉即可。

centos

编辑/etc/yum/pluginconf.d/versionlock.conf文件,将enable的值改成0。然后再执行yum update kernel就可以将内核更新到最新版本。virtualbox

但是这个时候如果重启了vagrant虚拟机,会发现vagrant报错

Failed to mount folders in Linux guest. This is usually because
the “vboxsf” file system is not available. Please verify that
the guest additions are properly installed in the guest and
can work properly.推酷是个无耻的网站

搜索了下,发现可以通过执行/etc/init.d/vboxadd setup重新安装VBoxGuestAdditions来解决。

===============================================

http://leo108.com/pid-2180.asp

但是centos自带的VBoxGuestAdditions版本比较低,在启动vagrant的时候也会提示

The guest additions on this VM do not match the installed version of VirtualBox! In most cases this is fine, but in rare cases it can prevent things such as shared folders from working properly. If you see shared folder errors, please make sure the guest additions within the virtual machine match the version of VirtualBox you have installed on your host and reload your VM.

virtualbox

所以可以更新一下VBoxGuestAdditions版本。访问http://download.virtualbox.org/virtualbox/,找到你当前virtualbox版本的目录,进去之后可以找到对应版本的VBoxGuestAdditions的iso文件,将这个iso文件下载到vagrant虚拟机中,然后执行以下命令:leo108's blog

mount VBoxGuestAdditions_5.0.10.iso -o loop /mnt
cd /mnt/
sh VBoxLinuxAdditions.run --nox11

执行完毕之后退出虚拟机,再次执行vagrant reload即可

vagrant centos升级内核版本、升级VBoxGuestAdditions版本

将git源码打包成rpm安装包(centos)

centos6系统里通过yum安装的git版本只有1.7.1,好多新特性都没有。而使用编译安装的话,每台服务器都要编译一遍太麻烦,以后更新起来也麻烦,所以决定自己打一个rpm包。git

首先需要安装rpm-build

yum install -y rpm-build

然后创建一个rpmbuild目录:centos

cd ~ && rpmdev-setuptree

https://github.com/git/git/releases下载git源码包,一定要下载.tar.gz版本的,rpmbuild需要这种格式。

http://leo108.com

先下载到home目录下

将git源码打包成rpm安装包(centos)

wget https://github.com/git/git/archive/v2.6.4.tar.gz -O ~/git-2.6.4.tar.gz

复制git-2.6.4.tag.gz到rpmbuild目录下的SOURCES目录http://leo108.com/pid-2172.asp

cp ~/git-2.6.4.tar.gz ~/rpmbuild/SOURCES

解压tar包后,在git-2.6.4目录中执行

推酷是个无耻的网站

make git.spec && rpmbuild -ba git.spec

如果在rpmbuild过程中提示错误,通常是因为编译依赖的原因,这个时候只需要执行yum install 依赖包,然后再次执行rpmbuild -ba git.spec。leo108's blog

等到编译打包完成后,rpm包位于~/rpmbuild/RPMS/x86_64目录下,通常只需要git-2.6.4-1.el6.x86_64.rpm和perl-Git-2.6.4-1.el6.x86_64.rpm两个包。

git

在需要安装新版git的服务器上,先通过yum卸载旧包

yum remove -y perl-Git git

然后将那两个rpm包复制过来,再通过yum安装:

yum localinstall git-2.6.4-1.el6.x86_64.rpm perl-Git-2.6.4-1.el6.x86_64.rpm

这个时候执行git –version就可以看到系统里已经是新版本的git了。yum

===================12月10日补充===================

leo108's blog

这种方法适用于大多数程序,例如ganglia就在源码目录下提供了对应的spec文件。

rpm

参考资料:

将git源码打包成rpm安装包(centos)

  1. https://gist.github.com/fernandoaleman/1376973
  2. http://stackoverflow.com/a/33439452/2013307
  3. 制作php的RPM包:https://blog.linuxeye.com/431.html

如何在linux的终端输出二维码

脑子突然间蹦出来的想法,就迫不及待试试看能否实现。

需求很简单,就是在linux的终端中输入一个字符串(可以是以命令行参数形式,也可以是通过交互式输入),然后就会输出对应的二维码。

首先PHP已经有现成的QrCode类库phpqrcode,可以将一个字符串转成PNG格式的图片,但是PNG图片是没法在终端里展示的,于是仔细翻看文档和demo,发现该类库也可以输出0和1组成的矩阵(实际上该方法返回的是一个PHP的二维数组)。

已经有了0和1的矩阵,接下来要做的就是输出黑白色块,为了操作方便,我引入了symfony项目中的console组件。通过console组件可以非常方便的创建一个Cli命令,而且内置了大量输入和输出方法。

终端

根据console的文档,我们可以新建两个OutputFormatStyle:推酷是个无耻的网站

$black = new OutputFormatterStyle('black', 'black');
$output->getFormatter()->setStyle('blackc', $black);
$white = new OutputFormatterStyle('white', 'white');
$output->getFormatter()->setStyle('whitec', $white);

定义了文字颜色和背景颜色分别是白色和黑色的两个样式。QrCode

这样就可以输出白色和黑色的色块了:

终端

$output->writeln('<whitec>  </whitec><blackc>  </blackc><whitec>  </whitec>');

上面的代码就会输出两个白色块中间隔着一个黑色块。

黑白色块输出搞定之后,只需要根据二维码的0-1矩阵输出对应色块就行。

所以核心代码如下:

如何在linux的终端输出二维码

protected function execute(InputInterface $input, OutputInterface $output)
{
    $lrPadding = 1;
    $udPadding = 1;
    $text = 'http://leo108.com';
    $map = array(
        0 => '<whitec>  </whitec>',
        1 => '<blackc>  </blackc>',
    );
    $this->initStyle($output);
    $text   = QRcode::text($text);
    $length = strlen($text[0]);

    $paddingLine = str_repeat($map[0], $length + $lrPadding * 2) . "\n";
    $after = $before = str_repeat($paddingLine, $udPadding);
    $output->write($before);
    foreach ($text as $line) {
        $output->write(str_repeat($map[0], $lrPadding));
        for ($i = 0; $i < $length; $i++) {
            $type = substr($line, $i, 1);
            $output->write($map[$type]);
        }
        $output->writeln(str_repeat($map[0], $lrPadding));
    }
    $output->write($after);
}

其中$lrPadding和$udPadding分别用来配置左右和上下白边的长度。

最终代码已托管github

最后来张效果图:

如何在linux的终端输出二维码

二维码

专注于技术,切不可沉湎于技术