前言

归功于php相对宽松的语法特性,可以说每个phper都有着自己与他人完全不同的代码风格。同样写代码如何在实现功能的同时又能不失优雅的拉高自身的逼格,当然这也得按照代码规范的基本法,以下就分享音风编码这几年在php方面的一些代码技巧,希望能对你姿势水平的提升有所帮助~

技巧

1. 妙用“逻辑短路”,精炼代码。


// 优化前
$round++;
if($round % 1000){
    sleep(1);
}

// 优化后
(++$round) && $round % 1000 || sleep(1);

2. 尽量避免 if 多重嵌套。


// 优化前 if 多重嵌套
if(isset($map[$row['apid']])){
    // if...more
    if($workflow_writer->query($sql)){
        exit('wtf? update workflow_event error!');
    }
}
// 优化后
if(!isset($map[$row['apid']])){
    // handling...
    return
}
if($workflow_writer->query($sql)){
    exit('wtf? update workflow_event error!');
}
// 究极优化...
$map[$row['apid']] && !$workflow_writer->query($sql) && exit('wtf update workflow_event error!');

3. 避免产生超大数组,擅用yield实现迭代器,节省大量内存


// 优化前
function get_bigger_array($nums){
    $arr = [];
    for($i=0;$i<$nums;$i++){
        $arr[] = $i;
    }
    return $arr;
}
echo 'memory used: '.memory_get_usage().PHP_EOL;
$data = get_bigger_array(10000000);
echo 'memory used: '.memory_get_usage().PHP_EOL;
// 优化后
function get_bigger_array_yield($nums){
    for($i=0;$i<$nums;$i++){
        yield $i;
    }
}
echo 'memory used: '.memory_get_usage().PHP_EOL;
$data = get_bigger_array_yield(10000000);
echo 'memory used: '.memory_get_usage().PHP_EOL;
var_dump($data->current());

进阶

上面代码片段讲到了如何用yield关键字返回生成器实现迭代。但是yield的应用场景还远远不止如此,接下来我们试作封装迭代器的send()使得生成器的迭代更加自如方便,这样的封装我们称之为调度器。有了Generator(生成器)、Task(任务)、Scheduler(调度器)三要素才能真正实现协程。了解了yield的工作机制,你才能更好的了解什么是PHP的协程。

注意:协程不是并发多线程,而是一种在用户态程序自身的管理调度协作方式!


use Jamlee\Coroutine\Scheduler;
use Jamlee\Coroutine\SystemCall;

require '../vendor/autoload.php';

function loop($max){
    $tid = yield SystemCall::getTaskId();
    for($i=0; $i<$max; $i++){
        printf('loop[%d]:$i = %d'.PHP_EOL, $tid, $i);
        yield;
    }
}
$scheduler = new Scheduler;                                     
$scheduler->NewTask(loop(10));
$scheduler->NewTask(loop(10));
$scheduler->run();

class Task {
    protected $taskId;
    protected $coroutine;
    protected $sendValue = null;
    protected $beforeFirstYield = true;
 
    public function __construct($taskId, Generator $coroutine) {
        $this->taskId = $taskId;
        $this->coroutine = $coroutine;
    }
 
    public function getTaskId() {
        return $this->taskId;
    }
 
    public function setSendValue($sendValue) {
        $this->sendValue = $sendValue;
    }
 
    public function run() {
        if ($this->beforeFirstYield) {
            $this->beforeFirstYield = false;
            return $this->coroutine->current();
        } else {
            $retval = $this->coroutine->send($this->sendValue);
            $this->sendValue = null;
            return $retval;
        }
    }
 
    public function isFinished() {
        return !$this->coroutine->valid();
    }
}

class Scheduler {
    protected $maxTaskId = 0;
    protected $taskMap = []; // taskId => task
    protected $taskQueue;
 
    public function __construct() {
        $this->taskQueue = new SplQueue();
    }
 
    public function newTask(Generator $coroutine) {
        $tid = ++$this->maxTaskId;
        $task = new Task($tid, $coroutine);
        $this->taskMap[$tid] = $task;
        $this->schedule($task);
        return $tid;
    }
 
    public function schedule(Task $task) {
        $this->taskQueue->enqueue($task);
    }
 
    public function run() {
        while (!$this->taskQueue->isEmpty()) {
            $task = $this->taskQueue->dequeue();
            $task->run();
 
            if ($task->isFinished()) {
                unset($this->taskMap[$task->getTaskId()]);
            } else {
                $this->schedule($task);
            }
        }
    }
}

class SystemCall {
    protected $callback;
 
    public function __construct(callable $callback) {
        $this->callback = $callback;
    }
 
    public function __invoke(Task $task, Scheduler $scheduler) {
        $callback = $this->callback;
        return $callback($task, $scheduler);
    }

    public static function getTaskId(){
        return new self(function (Task $task, Scheduler $scheduler) {
            $task->setSendValue($task->getTaskId());
            $scheduler->schedule($task);
        });
    }
}

view source code by Github


一介布衣