3.1. php

3.1.1. php7功能点

  1. 花括号 {}

    1. 表示{}里面的是一个变量  ,执行时按照变量来处理
    2. 在字符串中引用变量使用的特殊包括方式,这样就可以不使用.运算符,从而减少代码的输入量了。
    $s = "Di, ";
    echo ("${s}omething");  == echo $s."omething";
    //Output: Di, omething
    
    PHP 变量后面加上一个大括号{},里面填上数字,就是指 PHP 变量相应序号的字符。
    例如:
    $str = 'hello';
    echo $str{0}; // 输出为 h
    echo $str{1}; // 输出为 e
    如果要检查某个字符串是否满足多少长度,可以考虑用这种大括号(花括号)加 isset 的方式替代 strlen 函数,因为 isset 是语言结构,strlen 是函数,所以使用 isset 比使用 strlen 效率更高。
    比如判断一个字符串的长度是否小于 5:
    if ( !isset ( $str{5} ) ) 就比 if ( strlen ( $str ) < 5 ) 好。
    
  2. call_user_func & call_user_func_array

    区别 调用方式不同
        call_user_func(array($class,$method),param1,param2);
        call_user_func_array(array($class,$method),array(param1,param2));
    注意如果上面$class为字符串,$method为非静态方法,则必须要先实例化,比如 $class = new \app\class;
    
  3. method_exists & is_callable

    调用方式不同,is_callable
        if ( is_callable( array( $obj, $method ) ) )
        {
        /*要操作的代码段*/
        }
        method_exists($obj,$method)
        $result = is_callable(['\app\sms\model\NoDisturbNumber','clearData']);
        dump($result);
        $result = method_exists('appsms\model\NoDisturbNumber', "clearData");
        dump($result);
    其他
        php函数method_exists()与is_callable()的区别在于在php5中,一个方法存在并不意味着它就可以被调用。对于 private,protected和public类型的方法,method_exits()会返回true,但是is_callable()会检查存在其是否可以访问,如果是private,protected类型的,它会返回false。
    
  4. trait

    属性:如果 trait 定义了一个属性,那类将不能定义同样名称的属性,否则会产生一个错误。
    如果类的定义是兼容的(同样的可见性和初始值)则错误的级别是 E_STRICT,否则是一个致命错误。
    
  5. php7新功能

    1、运算符(NULL 合并运算符)
        $a = $_GET['a'] ?? 1; 相当于 $a = isset($_GET['a']) ? $_GET['a'] : 1;
    2、函数返回值类型声明
      # declare(strict_types=1);
        function foo($a):int{
            return $a
        }
        foo(1.0); foo 函数返回 int 1,没有任何错误
        去掉上面代码的注释,采用严格模式,则会出发一个 TypeError 的 Fatal error。
    3、标量类型声明
        function sumOfints(int ...$ints){
            return array_sum($ints)
        }
        var_dump(sumOfInts(2, '3', 4.1));
    4、use 批量声明
        use some/namespace/{ClassA, ClassB, ClassC as C};
        use function some/namespace/{fn_a, fn_b, fn_c};
        use const some/namespace/{ConstA, ConstB, ConstC};
    5、spaceship(<=>)操作符
        用来比较两个表达式,左边小于、等于、大于右边时分别返回-1,0,1
    6、常量array可以使用define定义
        define('ANIMALS', [
    'dog',
    'cat',
    'bird'
        ]);
        echo ANIMALS[1]; // outputs "cat"
    7、匿名类
        interface Logger {
    public function log(string $msg);
        }
        class Application {
            private $logger;
    
            public function getLogger(): Logger {
                 return $this->logger;
            }
    
            public function setLogger(Logger $logger) {
                 $this->logger = $logger;
            }
        }
        $app = new Application;
        $app->setLogger(new class implements Logger {
            public function log(string $msg) {
                echo $msg;
            }
        });
        var_dump($app->getLogger());
        The above example will output:
        object(class@anonymous)#2 (0) {
        }
    8、闭包( Closure)增加了一个 call 方法
        class A {private $x = 1;}
        // Pre PHP 7 code
        $getX = function() {return $this->x;};
        $getXCB = $getX->bindTo(new A, 'A'); // intermediate closure
        echo $getXCB();
    
        // PHP 7+ code
        $getX = function() {return $this->x;};
        echo $getX->call(new A);
    

3.1.2. tp5

3.1.2.1. queue

  • refer

    https://github.com/coolseven/notes/blob/master/thinkphp-queue/README.md
    
  • install

    composer require topthink/think-queue
    
  • 例子

    • 配置

      application/extra/queue.php
      return [
              'connector'  => 'Redis',         // Redis 驱动
              'expire'     => 60,             // 任务的过期时间,默认为60秒; 若要禁用,则设置为 null
              'default'    => 'default',      // 默认的队列名称
              'host'       => '127.0.0.1',        // redis 主机ip
              'port'       => 6379,           // redis 端口
              'password'   => '',             // redis 密码
              'select'     => 2,                  // 使用哪一个 db,默认为 db1
              'timeout'    => 0,              // redis连接的超时时间
              'persistent' => false,          // 是否是长连接
      ];
      
    • 建立生产者

      public function test(){
          // 1.当前任务将由哪个类来负责处理。
          //   当轮到该任务时,系统将生成一个该类的实例,并调用其 fire 方法
          $jobHandlerClassName  = 'app\job\Import';
          // 2.当前任务归属的队列名称,如果为新队列,会自动创建
          $jobQueueName     = "importQueue";
          // 3.当前任务所需的业务数据 . 不能为 resource 类型,其他类型最终将转化为json形式的字符串
          //   ( jobData 为对象时,需要在先在此处手动序列化,否则只存储其public属性的键值对)
          $jobData          = [ 'ts' => time(), 'bizId' => uniqid() , 'a' => 1 ] ;
          // 4.将该任务推送到消息队列,等待对应的消费者去执行
          $isPushed = Queue::push( $jobHandlerClassName , $jobData , $jobQueueName );
          // database 驱动时,返回值为 1|false  ;   redis 驱动时,返回值为 随机字符串|false
          if( $isPushed !== false ){
              echo date('Y-m-d H:i:s') . " a new Hello Job is Pushed to the MQ [$isPushed]"."<br>";
          }else{
              echo 'Oops, something went wrong.';
          }
      }
      
    • 查看queue

      llen "queues:helloJobQueue" 长度
      lpop "queues:helloJobQueue" 出栈
      lrange "queues:helloJobQueue" 0 -1 查询队列内容
      
    • 建立消费者

      namespace app\job;
      use think\queue\Job;
      use think\Log;
      
      class Import{
      
          /**
           * fire方法是消息队列默认调用的方法
           * @param Job            $job      当前的任务对象
           * @param array|mixed    $data     发布任务时自定义的数据
           */
          public function fire(Job $job,$data){
              $isJobDone = $this->doHelloJob($data);
      
              if ($isJobDone) {
                  //如果任务执行成功, 记得删除任务
                  $job->delete();
                  print("<info>Hello Job has been done and deleted"."</info>\n");
              }else{
                  if ($job->attempts() > 3) {
                      //通过这个方法可以检查这个任务已经重试了几次了
                      print("<warn>Hello Job has been retried more than 3 times!"."</warn>\n");
                      $job->delete();
                      // 也可以重新发布这个任务
                      //print("<info>Hello Job will be availabe again after 2s."."</info>\n");
                      //$job->release(2); //$delay为延迟时间,表示该任务延迟2秒后再执行
                  }
              }
          }
      
          /**
           * 根据消息中的数据进行实际的业务处理
           * @param array|mixed    $data     发布任务时自定义的数据
           * @return boolean                 任务执行的结果
           */
          private function doHelloJob($data) {
              // 根据消息中的数据进行实际的业务处理...
              print("<info>Hello Job Started. job Data is: ".var_export($data,true)."</info> \n");
              print("<info>Hello Job is Fired at " . date('Y-m-d H:i:s') ."</info> \n");
              print("<info>Hello Job is Done!"."</info> \n");
              return true;
          }
      
          public function failed($data){
      
              // ...任务达到最大重试次数后,失败了
              print("<info>Hello Job is failed!"."</info> \n");
              Log::write("has fail in import:");
          }
      }
      
    • 全局错误类

      1. tags.php 标签配置
      // 任务失败统一回调,有四种定义方式
      'queue_failed'=> [
                  ['app\\common\\behavior\\MyQueueFailedLogger', 'logAllFailedQueues']
              ],
      2. fail方法
          namespace app\common\behavior;
          use think\Log;
          class MyQueueFailedLogger{
              const should_run_hook_callback = true;
      
              /**
               * @param $jobObject   \think\queue\Job   //任务对象,保存了该任务的执行情况和业务数据
               * @return bool     true                  //是否需要删除任务并触发其failed() 方法
               */
              public function logAllFailedQueues(&$jobObject){
      
                  /* $failedJobLog = [
                          'jobHandlerClassName'   => $jobObject->getName(), // 'application\index\job\Hello'
                          'queueName' => $jobObject->getQueue(),             // 'helloJobQueue'
                          'jobData'   => $jobObject->getRawBody()['data'],  // '{'a': 1 }'
                          'attempts'  => $jobObject->attempts(),            // 3
                  ];
                  var_export(json_encode($failedJobLog,true)); */
                  Log::write("i am in failed method");
                  //Log::write("failed in ".json_encode($failedJobLog,true));
                  // $jobObject->release();     //重发任务
                  //$jobObject->delete();         //删除任务
                  //$jobObject->failed();   //通知消费者类任务执行失败
      
                  return self::should_run_hook_callback;
              }
          }
      
    • 测试

      1. https://10.0.0.42:1066/home/index/test
          2017-09-23 10:04:18 a new Hello Job is Pushed to the MQ [A25ZVpTcRIDUMyUgwSiRy76ebkLPuck8]
      2. php think queue:work --queue="importQueue" --daemon
      
    • 使用supervisor管理进程

      [program:import]
      directory=/home/cxl/git-svn/spi/spi-php
      command=php think queue:work --queue="importQueue" --tries=1 --daemon
      process_name=import_%(process_num)s
      numprocs=2
      numprocs_start=1
      autostart=true
      startsecs=5
      autorestart=true
      startretries=3
      user=www-data
      ;redirect_stderr=true
      ;stdout_logfile_maxbytes = 20MB
      ;stdout_logfile_backups = 20
      ;stdout_logfile = /data/logs/usercenter_stdout.log
      ;environment=PYTHONPATH=$PYTHONPATH:/path/to/somewhere
      导入队列,使用两个进程处理,循环处理
      kill一个进程会自动重启