分类 PHP 下的文章

Dockerfile

在原来镜像的 Dockerfile 中添加如下定义

RUN pecl install xdebug-3.0.2
RUN docker-php-ext-enable xdebug

RUN { \
        echo 'zend_extension=xdebug.so'; \
        echo 'xdebug.mode=profile'; \
        echo 'xdebug.start_with_request=trigger'; \
        echo 'xdebug.output_dir=/tmp/xdebug'; \
    } > /usr/local/etc/php/conf.d/docker-php-ext-xdebug.ini;

重新打包镜像就能得到一个包含了 xdebug 扩展的 php 镜像了,如果不确定是否添加成功,可以在容器运行起来之后,执行docker exec -i container_name php -m | grep xdebug 检查 xdebug 扩展是否安装成功

配置解析

这里我们采用的是 xdebug3,配置与 xdebug2 不太一样,具体区别可以看这里,网上能搜到的大部分都是 xdebug2 的配置。

为了不影响其他请求的性能,也为了减少日志数量,我们采用 start_with_request=trigger,手动触发 xdebug profile

xdebug.mode=profile 表示使用 xdebug 的 profile 模式
xdebug.start_with_request=trigger 表示默认不开启 xdebug profile,需要通过 http 请求中,添加 > XDEBUG_PROFILE=1 参数来开启,GET/POST 均可
xdebug.output_dir 指定 xdebug 日志的存放位置,如果指定的文件夹不存在,是无法生成日志的

由于我们是在 docker 中使用 xdebug,xdebug.output_dir 对应的目录最好从宿主机挂载进去,方便查看

开始分析

构造一个请求,例如:http://127.0.0.1:8080/xdebug?XDEBUG_PROFILE=1

之后可以在上述配置的文件夹中找到文件名类似cachegrind.out.20的文件,就是我们要的 profile 文件了。

Mac 用户可以下载 qcachegrind 来查看 profile 文件 brew install qcachegrind

下载完之后打开文件就能看到图形化的分析结果了。

参考文献

之前面试被问到的一个题目,想起来了就记录下

题目大概是这样:

商店规定 3 个空瓶可以换一瓶饮料,问买 10 瓶饮料可以喝到多少瓶?

提供两种解法

1. 循环

<?php

// 循环实现
function drink($full, $empty = 0) {
    // 3 个空瓶换一瓶饮料
    $exchange = 3;
    $drink = 0;
    while ($full > 0) {
        // 喝
        $drink += $full;    // 喝过的总数增加
        $empty = $empty + $full;    // 空瓶数增加

        // 换
        $full = intval($empty / $exchange);
        $empty = $empty % $exchange;
    }


    // 考虑特殊情况,剩余空瓶数 + 1 如果可以兑换一瓶的话,就还可以跟老板预支一瓶,喝完再把空瓶给他
    if ($empty + 1 == $exchange) {
        $drink++;
    }

    return $drink;
}

var_dump(drink(10));

2. 递归

<?php

// 递归实现
function drink_recursive($full, $empty = 0, $drink = 0) {
    // 3 个空瓶换一瓶饮料
    $exchange = 3;

    // 终止条件 - 没有满瓶 && 空瓶不够兑换
    if ($full < 1 && $empty < $exchange) {
        // 考虑特殊情况,剩余空瓶数 + 1 如果可以兑换一瓶的话,就还可以跟老板预支一瓶,喝完再把空瓶给他
        if ($empty + 1 == $exchange) {
            $drink++;
        }
        return $drink;
    }

    // 空瓶兑换
    if ($empty > 0) {
        $full += intval($empty / $exchange);
        $empty = $empty % $exchange;
    }

    // 喝
    if ($full > 0) {
        $drink += $full;
        $empty += $full;
        $full = 0;
    }

    return drink_recursive($full, $empty, $drink);
}

var_dump(drink_recursive(10));

用 deployer 发布 laravel 项目的最简配置

<?php
namespace Deployer;

require 'recipe/laravel.php';

// Set configurations
set('app_name', 'app_name');    // 应用名称
set('writable_mode', 'chown');
set('writable_use_sudo', true);
set('writable_recursive', true);

set('repository', 'ssh://[email protected]:/xxx.git');  // git 地址,要能从目标机器上访问到

// Configure servers
host('prod')
    ->hostname('host')  // 域名或者 ip
    ->user('user')  // 发布的用户名
    //->identityFile('~/.ssh/id_rsa')   // 公钥
    ->stage('production')
    ->set('deploy_path', '/data0/{{app_name}}/{{stage}}')   // 路径随便修改
    ->set('branch', 'master'); // 要发布的分支

// 加速 composer install
desc('Copy vendor directory optimized the composer install');
task('deploy:copy', function () {
    if (has('previous_release')) {
        run('cp -R {{previous_release}}/vendor {{release_path}}/vendor');
    }
});

desc('Restart php-fpm on success deploy');
task('php-fpm:restart', function () {
    // 这个命令按照实际情况修改
    run('service php7.2-fpm restart');
});

before('deploy:vendors', 'deploy:copy');
after('deploy:symlink', 'php-fpm:restart');

// 如果需要的话开启
// after('php-fpm:restart', 'artisan:horizon:terminate');

1. 先添加 ppa:ondrej/php 源

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update

如果找不到 add-apt-repository 命令则执行

sudo apt-get install software-properties-common

2. 安装 PHP7.1

sudo apt-get install php7.1 php7.1-common
sudo apt-get install php7.1-fpm php7.1-curl php7.1-xml php7.1-zip php7.1-gd php7.1-mysql php7.1-mbstring

3. 验证

php -v

4. 移除旧的 PHP 版本(可选)

sudo apt-get purge php7.0 php7.0-common
.....

参考 https://ayesh.me/Ubuntu-PHP-7.1

PHP 官网文档在介绍 RecursiveArrayIterator 时候举了一个例子,其中用到了 iterator_apply 这个函数。

然后我就去查了下 iterator_apply 的用法,介绍是这样说的:

iterator_apply — 为迭代器中每个元素调用一个用户自定义函数

当然 回调函数中如果不返回 true 迭代就会终止。

那个例子是这样写的

$myArray = array( 
    0 => 'a', 
    1 => array('subA','subB',array(0 => 'subsubA', 1 => 'subsubB', 2 => array(0 => 'deepA', 1 => 'deepB'))), 
    2 => 'b', 
    3 => array('subA','subB','subC'), 
    4 => 'c' 
); 

$iterator = new RecursiveArrayIterator($myArray); 
iterator_apply($iterator, 'traverseStructure', array($iterator)); 

function traverseStructure($iterator) { 
    
    while ( $iterator -> valid() ) { 

        if ( $iterator -> hasChildren() ) { 
        
            traverseStructure($iterator -> getChildren()); 
            
        } 
        else { 
            echo $iterator -> key() . ' : ' . $iterator -> current() .PHP_EOL;    
        } 

        $iterator -> next(); 
    } 
}

所以我有点懵逼,既然函数里面做了循环,并且没返回 true,说白了函数只调用了一次,为啥不能好好调一次函数,非要搞这些骚东西。。。

如果把例子中的函数改成这样的话,虽然看起来更复杂了,但是这样才能体现出使用 iterator_apply 函数的意义。如下:

function traverseStructure($iterator) {
    if ( $iterator -> hasChildren() ) {
        $children = $iterator->getChildren();
        iterator_apply($children, 'traverseStructure', [$children]);
    } else {
        echo $iterator -> key() . ' : ' . $iterator -> current() .PHP_EOL;
    }

    return true;
}