目录

再次把php manual手册读一遍,整理那些好用,但很少人知道的知识点

PHP能做什么

php除了服务脚本还能做什么

PHP: PHP 能做什么? - Manual

三个领域:

  • 网站和 web 应用程序(服务器端脚本)
  • 命令行脚本
  • 桌面(GUI)应用程序

除了作为web服务的服务端脚本外,php可以作为命令行脚本,还可以编写桌面应用程序,或许不是桌面应用程序编写最好的语言,但可以php-gtk扩展编写跨平台应用程序。

安装与配置

  • php

    其实php的编译很简单:

    • 获取并解压 PHP 源代码

      tar zxf php-x.x.x
      
    • 配置并构建PHP

      cd ../php-x.x.x
      ./configure --enable-fpm --with-mysql
      make
      sudo make install
      

      需要配置安装更多php扩展,可以通过configure –help 查看

    • 创建配置文件,并将其复制到正确的位置

      cp php.ini-development /usr/local/php/php.ini
      cp /usr/local/etc/php-fpm.d/www.conf.default /usr/local/etc/php-fpm.d/www.conf
      cp sapi/fpm/php-fpm /usr/local/bin
      

      配置文件根据实际情况,有所出入,大概意思明白即可。

  • nginx

    PHP: Unix 系统下的 Nginx 1.4.x - Manual

    需要着重提醒的是,如果文件不存在,则阻止 Nginx 将请求发送到后端的 PHP-FPM 模块, 以避免遭受恶意脚本注入的攻击。

    将 php.ini 文件中的配置项 cgi.fix_pathinfo 设置为 0 。php7.1中默认值是1。

  • windows

    Windows 下使用 Apache 安装 PHP 应用程序:XAMPP、WampServer 和 BitNami。

  • FPM

    PHP: FastCGI 进程管理器(FPM) - Manual

    官方php-fpm配置中文版:PHP-FPM配置 - Manual

    FPM(FastCGI进程管理器)提供的函数fastcgi_finish_request()具有特殊功能:用于在请求完成和刷新数据后,继续在后台执行耗时的工作(录入视频转换、统计处理等);后续函数章节细讲。

  • PECL扩展库安装

    要使用共享扩展库,必须经过编译,安装,然后加载。

    • 下载pecl扩展库

      • pecl install extname 命令会自动下载扩展代码,所以在这种情况下不需要再次下载。
      • https://pecl.php.net/ PECL 网站包括有 PHP 开发组提供的不同扩展库的信息。这里的信息包括:更新记录,版本说明,需求,以及其它信息。
    • pear编译pecl扩展库

      $ pecl install extname
      

      这将下载 extname 的源代码,编译之,并将 extname.so 安装到 extension_dir 中。然后 extname.so 就可以通过 php.ini 加载了。

      extension_dir可以通过php-config命令查看(下面会提到),比如:

       --extension-dir     [/usr/lib/php/20160303]
      
    • phpize 编译pecl扩展库

      phpize 命令是用来准备 PHP 扩展库的编译环境的。下面例子中,扩展库的源程序位于 extname 目录中:

      $ cd extname
      $ phpize
      $ ./configure
      $ make
      # make install
      

      成功的安装将创建 extname.so 并放置于 PHP 的扩展库目录中。需要调整 php.ini,加入 extension=extname.so 这一行之后才能使用此扩展库。

  • php-config

    php-config 是一个简单的命令行脚本用于获取所安装的 PHP 配置的信息。

    jm@ubuntu:~$ php-config -h
    Usage: /usr/bin/php-config [OPTION]
    Options:
    --prefix            [/usr]
    ...
    --extension-dir     [/usr/lib/php/20160303]
    --include-dir       [/usr/include/php/20160303]
    --man-dir           [/usr/share/man]
    --php-binary        [/usr/bin/php7.1]
    --php-sapis         [apache2handler cgi cli fpm ]
    --phpapi            [20160303]
    ...
    
  • 运行时配置php.ini

    配置文件(php.ini)在 PHP 启动时被读取。对于服务器模块版本的 PHP,仅在 web 服务器启动时读取一次。对于 CGI 和 CLI 版本,每次调用都会读取。

  • .user.ini

    自 PHP 5.3.0 起,PHP 支持基于每个目录的 INI 文件配置。此类文件 仅被 CGI/FastCGI SAPI 处理。如果你的 PHP 以模块化运行在 Apache 里,则用 .htaccess 文件有同样效果。

    除了主 php.ini 之外,PHP 还会在每个目录下扫描 INI 文件,从被执行的 PHP 文件所在目录开始一直上升到 web 根目录($_SERVER[‘DOCUMENT_ROOT’] 所指定的)。如果被执行的 PHP 文件在 web 根目录之外,则只扫描该目录。

    在php.ini中配置.user.ini文件:

    • user_ini.filename 设定了 PHP 会在每个目录下搜寻的文件名;如果设定为空字符串则 PHP 不会搜寻。默认值是 .user.ini。

    • user_ini.cache_ttl 控制着重新读取用户 INI 文件的间隔时间。默认是 300 秒(5 分钟)。

  • 配置生效范围

    模式 含义
    PHP_INI_USER 可在用户脚本(例如 ini_set())或 Windows 注册表
    PHP_INI_PERDIR 可在 php.ini,.htaccess 或 httpd.conf 中设定
    PHP_INI_SYSTEM 可在 php.ini 或 httpd.conf 中设定
    PHP_INI_ALL 可在任何地方设定

    例如 output_buffering 指令是属于 PHP_INI_PERDIR,因而就不能用 ini_set() 来设定。但是 display_errors 指令是属于 PHP_INI_ALL 因而就可以在任何地方被设定,包括 ini_set()。

    注:ini_set()就是可以在用户脚本中修改配置。

  • 修改配置

    修改配置的方式:

    • httpd.conf
    • .htaccess
    • windows修改注册表
    • ini_set函数

    注:ini_set并不能修改所有配置

    哪些指令配置可以在当前模式下修改详见:PHP: php.ini 配置选项列表 - Manual

    比如upload_max_filesize只能在PHP_INI_PERDIR模式下修改,也就是只有php.ini、httpd.conf、.htaccess配置文件中才能修改生效。

语法参考

基本语法

  • PHP标记

    如果文件内容是纯 PHP 代码,最好在文件末尾删除 PHP 结束标记。这可以避免在 PHP 结束标记之后万一意外加入了空格或者换行符,会导致 PHP 开始输出这些空白,而脚本中此时并无输出的意图。

  • PHP文件智能混合内容

    常用于分离HTML文件。如果懂得这个知识点,不仅仅可以用于分离HTML文件。

    凡是在一对开始和结束标记之外的内容都会被 PHP 解析器忽略,这使得 PHP 文件可以具备混合内容。 可以使 PHP 嵌入到 HTML 文档中去。

    <?php if ($expression == true): ?>
    This will show if the expression is true.
    <?php else: ?>
    Otherwise this will show.
    <?php endif; ?>
    
  • 短标记

    短标记仅在通过 php.ini 配置文件中的指令 short_open_tag 打开后才可用,或者在 PHP 编译时加入了 –enable-short-tags 选项。

    
    <? echo 'this is the simplest, an SGML processing instruction'; ?>
    <?= expression ?> This is a shortcut for "<? echo expression ?>"
    

    还会看到有ASP 风格标记,仅在通过 php.ini 配置文件中的指令 asp_tags 打开后才可用。

    <% echo 'You may optionally use ASP-style tags'; %>
    <%= $variable; # This is a shortcut for "<% echo . . ." %>
    

    我们不建议使用短标记。

类型

PHP 支持 10 种原始数据类型。

四种标量类型:

  • bool(布尔型)
  • int(整型)
  • float(浮点型,也称作 double)
  • string(字符串)

四种复合类型:

  • array(数组)
  • object(对象)
  • callable(可调用)
  • iterable(可迭代)

还有两种特殊类型:

  • resource(资源)
  • NULL(无类型)

这里注意到关于“双精度(double)”类型,实际上 double 和 float 是相同的,由于一些历史的原因,这两个名称同时存在。

注意:变量的类型通常不是由程序员设定的,确切地说,是由 PHP 根据该变量使用的上下文在运行时决定的。

弱类型语言php经常被人吐槽的地方,但也会引起很多人喜欢,毕竟不需要人为过多的去考虑一些类型转换。

  • 布尔类型Boolean

    • PHP中true、false,不区分大小写。

    • 转换为布尔值

      要明确地将一个值转换成 boolean,用 (bool) 或者 (boolean) 来强制转换。但是很多情况下不需要用强制转换,因为当运算符,函数或者流程控制结构需要一个 boolean 参数时,该值会被自动转换。详见后续的类型转换判别。

    • 当转换为 boolean 时,以下值被认为是 false:

      布尔值 false 本身
      整型值 0(零)及 -0 (零)
      浮点型值 0.0(零)-0.0(零)
      空字符串,以及字符串 "0"
      不包括任何元素的数组
      特殊类型 NULL(包括尚未赋值的变量)
      从空标记生成的 SimpleXML 对象
      

      所有其它值都被认为是 true(包括任何资源resource 和 NAN)。

  • 整型Integer

    • 支持多种进制整型

      整型值可以使用十进制,十六进制,八进制或二进制表示,前面可以加上可选的符号(- 或者 +)。 可以用 负运算符 来表示一个负的integer。

      要使用八进制表达,数字前必须加上 0(零)。要使用十六进制表达,数字前必须加上 0x。要使用二进制表达,数字前必须加上 0b。

    • 7.4后支持下划线

      $a = 1_234_567; // 整型数值 (PHP 7.4.0 以后)

    • 整型溢出会被解释为float。

      如果给定的一个数超出了 integer 的范围,将会被解释为 float。同样如果执行的运算结果超出了 integer 范围,也会返回 float。

    • php没有整除运算符的

      PHP 中没有整除的运算符。1/2 产生出 float 0.5。 值可以舍弃小数部分,强制转换为 integer,或者使用 round() 函数可以更好地进行四舍五入。

      注意: 从 PHP 7.0.0 开始,函数 intdiv() 可以用于整数除法

    • 转换为整型

      要明确地将一个值转换为 integer,用 (int) 或 (integer) 强制转换。还是那句话,大多情况下,不需要强制转换类型,因为当运算符,函数或流程控制需要一个 integer 参数时,值会自动转换。还可以通过函数 intval() 来将一个值转换成整型。类型转换的判别

      null会转化为0。

      false 将产生出 0(零),true 将产生出 1。

      当从浮点数转换成整数时,将向下取整。

      PHP 7.0.0 起,NaN 和 Infinity 在转换成 integer 时,不再是 undefined 或者依赖于平台,而是都会变成0。

  • Float浮点型

    永远不要相信浮点数结果精确到了最后一位,也永远不要比较两个浮点数是否相等。如果确实需要更高的精度,应该使用任意精度数学函数PHP: BC 数学 函数 - Manual或者 PHP: GMP 函数 - Manual

  • String字符串

    一个字符串 string 就是由一系列的字符组成,其中每个字符等同于一个字节。 这意味着 PHP 只能支持 256 的字符集,因此不支持 Unicode 。(一个字节8位,只能支持256的字符集,对于多字节的unicode,我们经常会用到mbstring的扩展)

    注意: string 最大可以达到 2GB。

    字符串以及为什么说 PHP 不支持 Unicode_zhoucy-CSDN博客_php字符串unicode

    • 语法

      • 单引号:不解析变量,不解析转义字符

      • 双引号:解析变量,解析转义字符

      • Heredoc

        以«<开头定义一个结构:«<LABEL

        中间是字符串

        结束时所引用的标识符必须在该行的第一列,并以定义的结构字符串为结尾LABEL;,注意分号;。

        $bar = <<<LABEL
        i am a chinese
        LABEL;
        

        Heredoc效果类似于双引号

      • Newdoc

        Newdoc效果类似于单引号,不解析变量不解析转义字符。

        $bar = <<<'LABEL'
        i am a chinese
        LABEL;
        
  • Array数组

    PHP 中的数组实际上是一个有序映射。映射是一种把 values 关联到 keys 的类型。此类型在很多方面做了优化,因此可以把它当成真正的数组,或列表(向量),散列表(是映射的一种实现),字典,集合,栈,队列以及更多可能性。由于数组元素的值也可以是另一个数组,树形结构和多维数组也是允许的。

    • key与value

      php的数组实在是万能,key 可以是 integer 或者 string。value 可以是任意类型,而且key相同时,会覆盖保留最后一个。当然key是可选项,未指定时,PHP 将自动使用之前用过的最大 integer 键名加上 1 作为新的键名。

    • 转换数组

      (array)$value

      对于任意 integer,float,string,boolean 和 resource 类型,如果将一个值转换为数组,将得到一个仅有一个元素的数组,其下标为 0,该元素即为此标量的值。

      如果一个 object 类型转换为 array,则结果为一个数组,其单元为该对象的属性,经常会有一些不可预知的行为。

    • 数组函数

      数组函数

  • Iterable可迭代对象

    通俗易懂PHP迭代生成器 - 9ong

  • Object对象

    不建议将数组转换成Object对象,在7.2前后的版本对于数组转换成Object对象有不同的属性值输出。

  • Resource资源类型

    资源 resource 是一种特殊变量,保存了到外部资源的一个引用。

    资源类型变量保存有为打开文件、数据库连接、图形画布区域等的特殊句柄。

    • 资源释放

      引用计数系统是 Zend 引擎的一部分,可以自动检测到一个资源不再被引用了(和 Java 一样)。这种情况下此资源使用的所有外部资源都会被垃圾回收系统释放。因此,很少需要手工释放内存。

      注意: 持久数据库连接比较特殊,它们不会被垃圾回收系统销毁。参见数据库永久连接一章。

    • 资源函数

      资源是通过专门的函数来建立和使用的。

      PHP: 资源类型列表 - Manual

  • NULL

    NULL的值就一个:不区分大小写的null。

  • callback类型

    回调函数:普通函数、匿名函数、对象的方法、静态类方法

    PHP是将函数以string形式传递的。

    PHP是通过数组array传递对象的方法和静态类的方法。

    比如:一些函数如 call_user_func() 或 usort() 可以接受用户自定义的回调函数作为参数:

    // 普通定义的函数
    call_user_func('my_callback_function'); 
    
    // 静态类方法
    call_user_func(array('MyClass', 'myCallbackMethod')); 
    
    // 实例化对象方法
    $obj = new MyClass();
    call_user_func(array($obj, 'myCallbackMethod'));
    
    // 静态类方法2
    call_user_func('MyClass::myCallbackMethod');
    
    //匿名函数
    call_user_func(function(...){....});
    
  • 强制类型转换

    (int), (integer) - 转换为整形 integer
    (bool), (boolean) - 转换为布尔类型 boolean
    (float), (double), (real) - 转换为浮点型 float
    (string) - 转换为字符串 string
    (array) - 转换为数组 array
    (object) - 转换为对象 object
    (unset) - 转换为 NULL // 不建议再使用,在php8中已经移除
    

变量

  • 传值赋值与引用赋值

    变量默认总是传值赋值。

    PHP 也提供了另外一种方式给变量赋值:引用赋值。

    使用引用赋值,简单地将一个 & 符号加到将要赋值的变量前(源变量)

    $foo = 'Bob';              // 将 'Bob' 赋给 $foo
    $bar = &$foo;              // 通过 $bar 引用 $foo
    

    注意:引用赋值,而不是传址赋值,虽然两者效果一样。后面我们会说到这两个区别

  • 初始化变量

    虽然在 PHP 中并不需要初始化变量,但对变量进行初始化是个好习惯。

    PHP: 预定义变量 - Manual,比如超全局变量

  • 变量范围

    • 首先我们不建议使用global关键字来引入函数/方法以外的变量,可以的话通过传参来实现。

    • 有个特别注意的地方:大部分的 PHP 变量只有一个单独的范围。这个单独的范围跨度同样包含了 include 和 require 引入的文件。例如:

      $a = 1;
      include 'b.php';    
      

      变量$a将在b.php文件中生效。

    • 递归中常用的静态变量

      通常会在一个递归中统计或累加数值,但由于递归函数属于局部函数,普通变量在函数再次被调用时,会被释放重新初始化。为了累计或累加数值,我们需要一个在函数再次被调用时不会被初始化的变量,那就是static的静态变量

      function test()
      {
          static $count = 0;
      
          $count++;
          echo $count;
          if ($count < 10) {
              test();
          }
          $count--;
      }
      
  • 可变变量

    个可变变量获取了一个普通变量的值作为这个可变变量的变量名。在上面的例子中 hello 使用了两个美元符号($)以后,就可以作为一个可变变量的变量了。例如:

    $$a = 'world';
    

    这时,两个变量都被定义了:$a 的内容是“hello”并且 $hello 的内容是“world”。

    注意了:$$a[1] 这怎么解析?是一种模棱两可的变量,需要人为的定界:

    
    ${$a[1]}
    //或
    ${$a}[1]
    

    再注意了:超全局变量和$this都是特殊变量,不能被动态引用为可变变量。

常量

  • 定义常量

    除了define函数外,还可以通过关键字const定义常量。一般为大写。

    使用 const 关键字定义常量必须处于最顶端的作用区域,因为用此方法是在编译时定义的。这就意味着不能在函数内,循环内以及 if 或 try/catch 语句之内用 const 来定义常量。

  • 支持数组及表达式

    php5.6之后,允许将常量定义为表达式。注意和类中的常量比较区别。

    const HELO = "hello";
    const HWWW = HELO." World";//表达式
    const AAAA = ["a",'b'];
    
  • 魔术常量

    大家可能听过php的魔术方法,现在我们看看魔术常量。

    有些魔术常量的值随着他们在代码中的位置改变而改变,比如__line__,__file__等,他们不区分大小写。

    php令人恼的就是不统一的有些大小写敏感有些不区分大小写。

    PHP: 魔术常量 - Manual

表达式

PHP 是一种面向表达式的语言,从这一方面来讲几乎一切都是表达式。

PHP: 表达式 - Manual

没有什么太多让人意外的知识点。

运算符

运算符,通常会提到一元运算符,比如与或非++–;二元运算符,比如加减乘除;三元运算符(条件运算符),比如也是唯一的三元运算符:?: 。

  • 运算符优先级

  • 算数运算符

    • 整除。php中是没有整除运算符的,/ 只是除法,总是会返回浮点数,除非两个操作数都是整数,否则需要通过round或ceil再次取整获得整数,也可以通过intdiv函数。

    • 求幂。5.6之后引入了求幂运算符 ** , $a ** $b ,求$a的$b次方的值。

  • 赋值运算符

    • = 并不是等于的意思,而是将右边表达式的值赋值给左边的运算数。

      除非特别说明的Object对象外,php中的赋值都是传值赋值,也就是拷贝赋值。即左右变量的变量互不影响。

    • 引用赋值

      这里又要先把object对象和其他类型分开说,object对象的赋值表达式默认是传址赋值,而其他类型默认是传值赋值,需要通过&=需要才能达到引用赋值效果(传址赋值与引用赋值虽然效果一样,但原理是不一样的),object对象需要通过clone关键字才能做到传值赋值(拷贝传值)。

      注意:new关键字就是返回一个引用,也就是引用赋值。

  • 位运算符

    看理论都很好理解,但实际应用,多少会有点懵逼的位运算。

    有一个php自带的很好的案例,我们在工作设计中也可以参考借鉴:

    PHP 的 ini 设定 error_reporting 使用了按位的值,
    提供了关闭某个位的真实例子。要显示除了提示级别
    之外的所有错误,php.ini 中是这样用的:
    
    E_ALL & ~E_NOTICE
    
    具体运作方式是先取得 E_ALL 的值:
    
    00000000000000000111011111111111
    再取得 E_NOTICE 的值:
    00000000000000000000000000001000
    然后通过 ~ 将其取反:
    11111111111111111111111111110111
    最后再用按位与 AND(&)得到两个值中都设定了(为 1)的位:
    00000000000000000111011111110111
    
    error_reporting 也可用来演示怎样置位。只显示错误和可恢复
    错误的方法是:
    E_ERROR | E_RECOVERABLE_ERROR
    
    也就是将 E_ERROR
    00000000000000000000000000000001
    和 E_RECOVERABLE_ERROR
    00000000000000000001000000000000
    用按位或 OR(|)运算符来取得在任何一个值中被置位的结果:
    00000000000000000001000000000001
          
    
  • 比较运算符

    • 全等 ===

      全等大家都知道,再强调下,除了值相同外,类型也要相同。

      也就是说php的==是在类型转换后再比较两个变量的值。而===不转换类型,直接比较值和类型。

    • 太空船(组合比较符) <=>

      php7提供。太空船运算符,不仅仅比较是否相等,还给出大小,返回-1、0、1的值,而不是Boolean值。

      echo 1.5 <=> 1.5; // 返回0  相等
      echo 1.5 <=> 2.5; // 返回-1 小于
      echo 2.5 <=> 1.5; // 返回1 大于
      

      当然还可以比较字符串、数组、对象,但我们建议不要比较这些复杂的复合类型变量。

    • 浮点数

      在php中,不建议通过运算符来比较浮点数,特别是和金钱有关的浮点数,金钱的设计,尽量不要出现浮点数,通过单位放大避免浮点数设计。

      不得已下,浮点数要采用BC函数或专门的数学计算扩展。

    • 三元运算符(条件判断运算符)

      ? :

      在5.3以后,支持省去中间部分表达式:

      //$a = expr1 ? expr1 : expr3
      $a = expr1 ? : expr3
      

      上面的表达式意思是:如果expr1为true,则返回expr1,否则返回expr3。直接省略了中间表达式部分。

      注意:表达式返回的是值,而不是引用。

    • NULL合并运算符

      php7提供。支持简单嵌套。

      这个表达式对于输入频繁检查判断的地方太有用了:

            
      // NULL 合并运算符的例子
      $default = "default";
      $action = $_POST['action'] ?? $default ?? "default";
      
      // 以上例子等同于于以下 if/else 语句
      if (isset($_POST['action'])) {
          $action = $_POST['action'];
      } else {
          $action = 'default';
      }
            
      

      注意:表达式返回的是值,而不是引用。

    • 错误控制运算符

      @ 当将其放置在一个 PHP 表达式之前,该表达式可能产生的任何错误信息都被忽略掉。注意是表达式之前。

      在现代编程思想中,我们不建议常使用他,尽量避免。

    • 执行运算符

      反引号(``)。注意这不是单引号!PHP 将尝试将反引号中的内容作为 shell 命令来执行,并将其输出信息返回(即,可以赋给一个变量而不是简单地丢弃到标准输出)。使用反引号运算符“`”的效果与函数 shell_exec() 相同。

      我们可以将其等同于shell_exec函数,因为关闭了 shell_exec() 时反引号运算符是无效的。所以我们还是建议使用shell_exec函数显式的执行更好些。

    • 逻辑运算符

      逻辑运算符,很多phper想到就是与或非: &&、 ||、 !这些符号,其实还有and、or、xor(异或,两个操作数不一样时返回true)

    • 数组运算符

      • 数组联合+

        合并两个数组,如果存在相同的key,则用左边第一个数组的key和value

        $arr = $array1 + $array2;
        
      • 数组全等判断

        === , 数组的全等条件:1、具有相同键值对;2、顺序一致;3、类型一样。

    • 类型运算符

      • instanceof

        我们都知道instanceof可用于检测实例是否某个类或其父类。

        instanceof还可以检测实例是否实现了某个接口。

流程控制

顺序、条件、循环

if、elseif、else、while、for、foreach、continue、break、switch、case、return、include、require、goto等
  • 流程控制替代语法

    PHP 提供了一些流程控制的替代语法,包括 if,while,for,foreach 和 switch。替代语法的基本形式是把左花括号({)换成冒号(:),把右花括号(})分别换成 endif;,endwhile;,endfor;,endforeach; 以及 endswitch;。

    我们还是建议使用花括号方式,了解这个特别之处为了方便阅读旧php代码。

  • do-while

    do-while 循环和 while 循环非常相似,区别在于表达式的值是在每次循环结束时检查而不是开始时。和一般的 while 循环主要的区别是 do-while 的循环语句保证会执行一次。

    适用于至少执行一次的循环。

  • foreach

    特别指出的是在foreach中可以通过&引用符号修改数组或对象的元组。

    $arr = array(1, 2, 3, 4);
    foreach ($arr as &$value) {
        $value = $value * 2;
    }
    // $arr is now array(2, 4, 6, 8)
    unset($value); // 最后取消掉引用
    
  • break

    php中的break可以接受一个可选的数字参数来决定跳出几重循环。也就是说默认break是跳出当前循环也可以这么写:

    $a = 10;
    $arr = [....];
    while($a<100>){
        foreach($arr as $k=>$v){
            if($v===2){
                break 1;//和break;一样
            }elseif($v === 10){
                break 2;//跳出foreach,还跳出while循环
            }
            //do something
        }
        $a += 10;
    }
    
  • continue

    continue 接受一个可选的数字参数来决定跳过几重循环到循环结尾。默认值是 1,即跳到当前循环末尾。

    $a = 10;
    $arr = [....];
    while($a<100>){
        foreach($arr as $k=>$v){
            if($v===2){
                continue 1;//和continue;一样,跳到当前foreach循环结尾,执行下一次foreach循环
            }elseif($v === 10){
                continue 2;//跳出foreach,并跳到while循环的结尾,执行下一次while循环
            }
            //do something
        }
        $a += 10;
    }
    
  • switch

    为避免错误,理解 switch 是怎样执行的非常重要。switch 语句一行接一行地执行(实际上是语句接语句)。开始时没有代码被执行。仅当一个 case 语句中的值和 switch 表达式的值匹配时 PHP 才开始执行语句,直到 switch 的程序段结束或者遇到第一个 break 语句为止。如果不在 case 的语句段最后写上 break 的话,PHP 将继续执行下一个 case 中的语句段。

    所以break在switch是很重要的,也是很容易犯错的。

    如果switch嵌套在循环中,continue作用就类似于break,continue 1跳出switch结构(可以把switch看做一次循环,continue和break在一次循环中是一样的效果的),continue 2跳出switch,并跳到外面一层循环的尾部,开始下一次外面一层循环。break 2同理,但是直接跳出switch和外面一层循环。

  • match

    php8提供。

    很不错的控制流程,与switch很相似,但又不一样,match是强类型检测,switch/case是弱类型检测;match支持范围判断,switch只能是精确判断;match结构中条件表达式在花括号内。switch/case是传入条件,在case中检测处理。

    
    $return_value = match (subject_expression) {
        single_conditional_expression => return_expression,
        conditional_expression1, conditional_expression2 => return_expression,
    };
    
      
    $expressionResult = match ($condition) {
        1, 2 => foo(),//当$condition值为1或2时,执行foo(),并返回结果
        3, 4 => bar(),
        default => baz(),//不满足以下条件1,2,3,4时,默认执行default条件
    };
    
    
    $age = 23;
    
    $result = match (true) {
        $age >= 65 => 'senior',
        $age >= 25 => 'adult',
        $age >= 18 => 'young adult',
        default => 'kid',
    };
    
    //string(11) "young adult"
    
  • declare

    declare 结构用来设定一段代码的执行指令。目前仅支持两个指令:ticks及encoding

    Tick(时钟周期)是一个在 declare 代码段中解释器每执行 N 条可计时的低级语句就会发生的事件。N 的值是在 declare 中的 directive 部分用 ticks=N 来指定的。

    注意:是ticks条可计时的低级语句。不是所有语句都可计时。通常条件表达式和参数表达式都不可计时。

    从register_tick_function(‘tick_handler’)设定起开始算1条。

    declare(ticks=1);
    //declare(ticks=4);
    
    // A function called on each tick event
    function tick_handler()
    {
        echo "tick_handler() called\n";
    }
    
    register_tick_function('tick_handler');
    
    $a = 1;
    
    if ($a > 0) {
        $a += 2;
        print($a);
    }
    
    
    declare(encoding='ISO-8859-1');
    
    
  • return

    return看似很简单,但就是因为简单,而导致有一些混乱,专业的phper需要搞清楚return的本质:

    return是一个语言结构,默认返回值而不是返回变量,如果需要返回变量,需要通过引用。参考函数/方法引用传参,或函数返回一个引用的语法,参见函数章节的返回值。

    return后面的参数不需要的用括号,也不应该用,可以减轻php的负担。

    • 如果在全局范围中调用,则当前脚本文件中止运行。
    • 如果当前脚本文件是被 include 的或者 require 的,则控制交回调用文件。
    • 如果当前脚本是被 include 的,则 return 的值会被当作 include 调用的返回值。
    • 如果在主脚本文件中调用 return,则脚本中止运行。
    • 如果当前脚本文件是在 php.ini 中的配置选项 auto_prepend_file 或者 auto_append_file 所指定的,则此脚本文件中止运行。
    • return并不终止后续函数、类、方法的定义,也就是说在return之前可以调用在return代码之后的函数方法块。
  • require与include

    require 和 include 几乎完全一样,除了处理失败的方式不同之外。require 在出错时产生 E_COMPILE_ERROR 级别的错误。

    换句话说将导致脚本中止而 include 只产生警告(E_WARNING),脚本会继续运行。

    include_once 语句在脚本执行期间包含并运行指定文件。与 include 语句类似,唯一区别是如果该文件已经被包含过,则不会再次包含,只会包含一次。同require_once;

  • goto

    在学c语言时,都会被告知不提倡使用goto操作符。

    PHP 中的 goto 有一定限制,目标位置只能位于同一个文件和作用域,也就是说无法跳出一个函数或类方法,也无法跳入到另一个函数。也无法跳入到任何循环或者 switch 结构中。可以跳出循环或者 switch,通常的用法是用 goto 代替多层的 break。

    在php中,我们建议使用场景:

    • 从多重循环中直接跳出 ;
    • 出错时清除资源;
    • 可增加程序的清晰度的情况;

    建议:小范围内使用,不要神出鬼没。还是可以使用的,特别是在模块化做的比较好的项目中。

函数

  • 用户自定义函数

    • 通常情况下,函数无需在调用前定义,除了有条件定义函数。

    • 有条件定义的函数

      除非有条件定义的函数。

      //one();//这里就还不能调用函数one,因为one函数还不存在。
      two();//这里是可以调用two函数的,定义一般在编译后就存在。
      if(xx){
          function one(){
              //...
          }
      }
      one();
      
      
      function two(){
      
      }
      
      

      还有一种有条件定义函数,就是函数中的函数,函数中的函数,也是一个令phper意外的语法:

      function outer(){
          function inner(){
      
          }
      }
      
      //inner();这里还不能调用inner函数,因为还不存在
      outer();//调用outer函数后,才真正定义了inner函数
      inner();
      
    • 作用域。php的函数和类都具有全局作用域。

      不论定义在哪里,只要加载可访问即可调用。

    • PHP 不支持函数重载,也不可能取消定义或者重定义已声明的函数。

    • 递归函数

      在php中,我们除了要注意无限递归,还要避免递归调用超过100-200层,因为可能会使堆栈崩溃从而使当前脚本终止。

  • 函数参数

    • 函数参数默认传值方式

    • 允许参数默认值

    • 可变数量的参数列表

      PHP 在用户自定义函数中支持可变数量的参数列表。由 … 语法实现函数接收参数列表。

      注意:旧php版本经常使用这些函数来获取可变参数 func_num_args()、 func_get_arg() 和 func_get_args(),但我们不再建议使用此方式,以后建议使用 … 来替代。

      function sum(...$numbers) {
      $acc = 0;
      foreach ($numbers as $n) {
          $acc += $n;
      }
      return $acc;
      }
      
      echo sum(1, 2, 3, 4);
      

      灵活起来,扩展起来,胆大心细用起来。

    • … 语法还可以用来传递参数

      主要用于传递数组,映射到函数参数列表

      function add($a, $b) {
      return $a + $b;
      }
      
      echo add(...[1, 2])."\n";
      
      $a = [1, 2];
      echo add(...$a);
      
    • php8有个逗号的小细节,最后一个参数尾部可以追加逗号,这个逗号会被忽略,只为了方便垂直的书写很多的参数。

      function a($aaa,
                  $bbb,
                  $ccc,
                  $ddd,
                  $eee,
                  $fff,
              ){
              //do something
      }
            
      
  • 返回值

    • 省略return。PHP中如果省略了 return,则返回值为 null。

    • 返回多个值。php不支持返回多个值,可以考虑返回数组等符合类型。

    • 返回一个引用。

      没有深刻本质的理解引用的话,慎用函数返回引用。

      function &returns_reference()
      {
          return $someref;
      }
      
      $newref =& returns_reference();
      
  • 可变函数

    PHP 支持可变函数的概念。

    也就是说一个变量名后有圆括号(),PHP 将寻找与变量的值同名的函数,并且尝试执行它。可变函数可以用来实现包括回调函数,函数表在内的一些用途。

    经常用于回调函数。

      
    function a($info,$callback){
        function_exists($callback) && $callback($info);
    }
    
    function b($data){
        var_dump($data);
    }
      
    a([1,2],"b");
    

    可变函数,支持函数也支持类中的方法调用。

  • 内置函数

    php内置很多很好用的函数,也有一些需要扩展才能用的函数,比如image、mysqli等。

    我们只想吐槽的是:php的内置函数总让我们很难记得住,我们觉得一些原因是参数原则、返回原则不统一导致的,我们有时不清楚这个函数是否有返回值,还是直接作用于参数变量上。

  • 匿名函数

    匿名函数也叫闭包函数,有javascript爱好的小伙伴,肯定很熟悉,也很经常使用。

    匿名函数目前是通过 Closure 类来实现的

    闭包还可以作为变量的值来使用,和可变函数相似。

    常用于回调。

  • 箭头函数

    箭头函数是 PHP 7.4 的新语法,是一种更简洁的 匿名函数 写法。

    箭头函数的基本语法为

    fn (argument_list) => expr
    

    箭头函数支持与 匿名函数 相同的功能,只是其父作用域的变量总是自动的

    $y = 1;
    
    $fn1 = fn($x) => $x + $y;
    // 相当于 using $y by value:
    $fn2 = function ($x) use ($y) {
        return $x + $y;
    };
    

类与对象

很多人都停留在php是过程语言,不能面向对象,php已经8了,在5就已经逐步与面向对象接轨了。

PHP 具有完整的对象模型。特性包括: 访问控制,抽象类和 final 类与方法,附加的魔术方法,接口,对象复制。

对于面向对象的基本知识,不需要我们介绍了,大家都懂。

PHP: OOP 变更日志 - Manual

  • final

    被继承的方法和属性可以通过用同样的名字重新声明被覆盖。但是如果父类定义方法时使用了 final,则该方法不可被覆盖。可以通过 parent:: 来访问被覆盖的方法或属性。

    注意: 属性不能被定义为 final,只有类和方法才能被定义为 final。

    基本的知识,但很少人能用上,也许是场景不需要用到,但也许哪天就真香,时刻牢记。

  • var

    虽然有些版本允许使用var定义属性,会被视为public,但我们不建议使用。

  • const常量

    常量的值必须是一个定值,不能是变量,类属性,数学运算的结果或函数调用。

    要区别于外部const/define定义的常量。

    PHP 7.1.0 开始,类的常量可以定义为公有、私有或受保护。如果没有设置这些关键字,则该常量默认为公有,也就是之前的版本只能使用const关键字定义常量,默认这些常量是公开可访问的,7.1之后允许public、protect、private的访问控制关键字。

  • 类自动加载

    现代php框架都引入自动加载机制了,特别是composer第三方库使用。

    spl_autoload_register() 函数可以注册任意数量的自动加载器,当使用尚未被定义的类(class)和接口(interface)时自动去加载。

    不建议使用__autoload函数,后续版本会被弃用。

    spl_autoload_register函数用于注册触发加载类,提供回调加载,达到按需加载。

    spl_autoload_register(function ($class_name) {
        require_once $class_name . '.php';
    });
    
    $obj  = new MyClass1();
    $obj2 = new MyClass2();
    
  • 构造函数与析构函数

    • 子类的构造函数不会默认或隐性的调用父类构造函数,有需要时,要手动调用父类构造函数。

    • 析构函数即使在使用 exit() 终止脚本运行时也会被调用。但在析构函数中调用 exit() 将会中止其余关闭操作的运行。

    • 在析构函数(在脚本终止时被调用)中抛出一个异常会导致致命错误。

  • ::,只是认识下这个符号叫:范围解析操作符

    在访问类常量、静态属性、静态方法,我们常会用到双冒号,也就是范围解析操作符。

  • 关于抽象类和接口

    如果对抽象类和接口有不清楚的可以看官方文档介绍

    PHP: 抽象类 - Manual

    PHP: 对象接口 - Manual

  • trait

    Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。

    Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。

    • 优先顺序是当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法。
    
    class Base {
        public function sayHello() {
            echo 'Hello ';
        }
    }
    
    trait HelloWorld {
        public function sayHello() {
            echo 'Hello World!';
        }
    }
    
    class MyHelloWorld extends Base {
        use SayWorld;
    }
    
    
    $o = new MyHelloWorld();
    $o->sayHello();
    //输出:Hello World!
    //因为trait覆盖了基类的方法
    
    class TheWorldIsNotEnough {
        use HelloWorld;
        public function sayHello() {
            echo 'Hello Universe!';
        }
    }    
    
    $o = new TheWorldIsNotEnough();
    $o->sayHello();    
    
    //输出:Hello Universe!
    //因为当前类覆盖trait的方法
    
    • 一个类中支持use多个trait,用逗号隔开

    • 方法冲突解决

      如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。

      为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。

      以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。

      use A, B {
          B::smallTalk insteadof A;//相当于排除A中的smallTalk
          A::bigTalk insteadof B;
          B::bigTalk as talk;//定义了talk别名
      }
      
    • 修改use trait中的方法访问控制

      直接看范例:

      
      // 修改 sayHello 的访问控制
      class MyClass1 {
          use HelloWorld { sayHello as protected; }
      }
      
      // 给方法一个改变了访问控制的别名
      // 原版 sayHello 的访问控制则没有发生变化
      class MyClass2 {
          use HelloWorld { sayHello as private myPrivateHello; }
      }
      
    • trait组合

      trait和class一样也可以组合其他多个trait。

    • trait支持抽象方法,用于强制实体类实现trait的抽象方法

    • trait支持静态方法

    • trait支持属性,但注意冲突,一般是不能定义同样名称的属性。我们建议避免trait和class出现相同名称的属性,即使访问可见性、初始值都一样(允许的),我也不建议,会导致混乱。

  • 匿名类

    php7开始支持匿名类,用于创建一次性简单对象。

    $util->setLogger(new class {
        public function log($msg)
        {
            echo $msg;
        }
    });
    //也就是不需要先定义一个logger的对象,再new给setLogger方法。
    
  • 魔术方法

    PHP: 魔术方法 - Manual

    PHP所提供的重载(overloading)是指动态地创建类属性和方法。我们是通过魔术方法(magic methods)来实现的。

    PHP中的重载与其它绝大多数面向对象语言不同。传统的重载是用于提供多个同名的类方法,但各方法的参数类型和个数不同。

    PHP: 重载 - Manual

  • clone

    对象复制可以通过 clone 关键字来完成(如果可能,这将调用对象的 __clone() 方法)。

    当对象被复制后,PHP 5 会对对象的所有属性执行一个浅复制(shallow copy)。所有的引用属性 仍然会是一个指向原来的变量的引用。如果有少量及简单的引用属性,可以在魔术方法__clone中手动实现引用属性的复制。

    class AA{
        public $object1;
        function __clone()
        {
          
            // 强制复制一份this->object, 否则仍然指向同一个对象
            $this->object1 = clone $this->object1;
        }
    }
    
    $a = new AA();
    
    $a2 = clone $a;//通过__clone方法,达到对$a对象的深拷贝。
    
  • static::后期静态绑定

    self、parent、static

    我们都知道parent的用法,就是从当前类(子类)往上追溯基类的存在的这个方法;static则是从当前类(父类、基类)往下追溯至当前执行实例(子类)中存在的方法;

    class A {
        public static function who() {
            echo __CLASS__;
        }
        public static function test() {
            static::who(); // 后期静态绑定从这里开始,这里会执行调用test方法的实例的who方法,也就是B类实例的who方法。
            self::who();//这里只会调用当前类中的who方法,如果有的话,也就是当前代码行所在类A
            parent::who();
        }
    }
    
    class B extends A {
        public static function who() {
            echo __CLASS__;
        }
    }
    
    B::test();//输出:BA
    
  • 对象与引用 及 传址赋值与引用赋值

    在php5 的对象编程经常提到的一个关键点是“默认情况下对象是通过引用传递的”。但其实这不是完全正确的。

    PHP 的引用是别名,就是两个不同的变量名字指向相同的内容。

    在 PHP 5以后,一个对象变量已经不再保存整个对象的值。而是保存一个标识符来访问真正的对象内容。

    当对象作为参数传递,作为结果返回,或者赋值给另外一个变量,另外一个变量跟原来的不是引用的关系,只是他们都保存着同一个标识符的拷贝,这个标识符指向同一个对象的真正内容。

    这标识符我们可以理解为存储地址,所以php的对象正常情况下的复制、传递都是通过传址的方式,而不是引用,虽然效果是一样的。

    class A {
        public $foo = 1;
    }  
    
    $a = new A;
    $b = $a;     // $a ,$b都是同一个标识符的拷贝,意味着a、b指向同一个地址
                // ($a) = ($b) = <id>
    $b->foo = 2;
    echo $a->foo."\n";//输出:2
    
    
    $c = new A;
    $d = &$c;    // $c ,$d是引用,d是c的一个别名,其实他们还是一样的
                // ($c,$d) = <id>
    
    $d->foo = 2;
    echo $c->foo."\n";//输出:2
    
    
    $e = new A;
    
    function foo($obj) {//e和obj都是同一个标识符的拷贝
        // ($obj) = ($e) = <id>
        $obj->foo = 2;
    }
    
    foo($e);
    echo $e->foo."\n";//输出:2
    

引用

  • 引用理解

    在 PHP 中引用意味着用不同的名字访问同一个变量内容。

    这并不像 C 的指针:例如你不能对他们做指针运算,他们并不是实际的内存地址。

    引用是符号表别名。

    注意:变量名和变量内容是不一样的, 因此同样的内容可以有不同的名字。

    最接近的比喻是 Unix 的文件名和文件本身——变量名是目录条目,而变量内容则是文件本身。引用可以被看作是 Unix 文件系统中的硬链接hard link。

  • 引用赋值

    看一个引用赋值例子:

    $a =& $b;
    

    注意:$a 和 $b 在这里是完全相同的,这并不是 $a 指向了 $b 或者相反,而是 $a 和 $b 指向了同一个地方, $a 和 $b 指向了同一个变量容器。

  • 引用传参

    看一个引用传参例子:

    function foo(&$var)
    {
        $num = 1;
        $var =& $num;//这里改变了$var,$var不再作为传参$a的引用别名,而变成了$num的引用别名了,也就和$a无关系了
        // $var = $num;//这里是传值赋值,如果没有上一句$var =& $num,则将改变$var与$a的值,因为$var还是传参$a的引用别名。
        echo $num."\n";
    }
    
    $a = 2;
    foo($a);
    echo $a."\n";
    
    

    如果是 $var = $num; 则输出:1 1

    如果是 $var =& $num; 则输出:1 2

  • 引用返回

    不要用返回引用来增加性能,php引擎足够聪明来自己进行优化。仅在有合理的技术原因时才返回引用!

    要在方法或函数用&指明要返回引用,在调用地方通过&指明获得引用。

    ...
    public $value = 20;
    public function &getValue() {
        return $this->value;
    }
    ...
    
    $myValue = &$obj->getValue();//$myValue将绑定类中的属性$value,修改类中的$value值,也就是修改了$myValue
      
    
    
  • 取消引用

    $a = 1;
    $b =& $a;
    
    unset($a);
    
    

    由于有一个值同时被$a和$b绑定,所以这里只是解除了$a对这个值的绑定/引用,这个值还有一个引用$b指向值,所以值不会被销毁,只是取消了$a这个引用。如果再unset($b),就再也没有引用指向这个值了,这个时候引用$b取消了,值也同时被销毁了。

    如果你对unix系统下的文件硬链接、软链接足够熟悉,就能很好理解了。

    Linux硬链接与符号链接之link - 9ong

  • global其实是引用

    global $var 等同于 var $var =& $GLOBALS['var']

    所以unset($var),并不会unset全局变量,$GLOBALS[‘var’]仍然存在。

  • $this是调用他的对象的引用

命名空间

  • 命名空间可以类比unix系统文件路径。在操作系统中文件有文件路径,用于区分相同文件名的不同文件。命名空间接地气点解释就是类库、类、函数等的一个路径,解决命名冲突,提高代码可读性。

  • 虽然任意合法的PHP代码都可以包含在命名空间中,但只有以下类型的代码受命名空间的影响,它们是:类(包括抽象类和traits)、接口、函数和常量。

    
    namespace MyProject\Sub\Level;
    
    const CONNECT_OK = 1;
    class Connection { /* ... */ }
    function connect() { /* ... */  }
    
    
    //namespace除了支持类、接口外还支持普通函数与脚本中常量定义
    require_once 'functions.php';//functions.php定义namespace为namespace extend;,且定义cli_log函数
      
    \extend\cli_log(__FILE__."\t".__LINE__);//来自functions.php的函数
    cli_log("我是谁,我来自哪里,我要做什么");//当前文件下函数
    
    
    function cli_log($msg){
        echo $msg." -- from local file function.\n";
    }
    
    
  • 同一个命名空间可以定义在多个文件中,即允许将同一个命名空间的内容分割存放在不同的文件中。

  • 一个文件中定义多个命名空间

    namespace MyProject\Sub\Level {
    
        const CONNECT_OK = 1;
        class Connection { /* ... */ }
        function connect() { /* ... */  }
    }
    
    namespace AnotherProject {
    
        const CONNECT_OK = 1;
        class Connection { /* ... */ }
        function connect() { /* ... */  }
    }
    

    上面的例子创建了常量MyProject\Sub\Level\CONNECT_OK,类 MyProject\Sub\Level\Connection和函数 MyProject\Sub\Level\connect。

    还有另外一个命名空间:AnoterhProject。

    不建议在一个文件中定义多个命名空间。

  • 命名空间使用

    命名空间的使用也可以类比unix系统文件路径使用。

    相对路径、绝对路径

    namespace A\B\C;
    
    /* 这个函数是 A\B\C\fopen */
    function fopen() { 
        /* ... */
        $f = \fopen(...); // 调用全局的fopen函数
        return $f;
    } 
    
    namespace A\B\C;
    class Exception extends \Exception {}
    
    $a = new Exception('hi'); // $a 是类 A\B\C\Exception 的一个对象
    $b = new \Exception('hi'); // $b 是类 Exception 的一个对象
    
    $c = new ArrayObject; // 致命错误, 找不到 A\B\C\ArrayObject 类
    
    namespace A;
    use B\D, C\E as F;
    
    // 函数调用
    
    foo();      // 首先尝试调用定义在命名空间"A"中的函数foo()
                // 再尝试调用全局函数 "foo"
    
    \foo();     // 调用全局空间函数 "foo" 
    
    my\foo();   // 调用定义在命名空间"A\my"中函数 "foo" 
    
    F();        // 首先尝试调用定义在命名空间"A"中的函数 "F" 
                // 再尝试调用全局函数 "F"
    
    // 类引用
    
    new B();    // 创建命名空间 "A" 中定义的类 "B" 的一个对象
                // 如果未找到,则尝试自动装载类 "A\B"
    
    new D();    // 使用导入规则,创建命名空间 "B" 中定义的类 "D" 的一个对象
                // 如果未找到,则尝试自动装载类 "B\D"
    
    new F();    // 使用导入规则,创建命名空间 "C" 中定义的类 "E" 的一个对象
                // 如果未找到,则尝试自动装载类 "C\E"
    
    new \B();   // 创建定义在全局空间中的类 "B" 的一个对象
                // 如果未发现,则尝试自动装载类 "B"
    
    new \D();   // 创建定义在全局空间中的类 "D" 的一个对象
                // 如果未发现,则尝试自动装载类 "D"
    
    new \F();   // 创建定义在全局空间中的类 "F" 的一个对象
                // 如果未发现,则尝试自动装载类 "F"
    
    // 调用另一个命名空间中的静态方法或命名空间函数
    
    B\foo();    // 调用命名空间 "A\B" 中函数 "foo"
    
    B::foo();   // 调用命名空间 "A" 中定义的类 "B" 的 "foo" 方法
                // 如果未找到类 "A\B" ,则尝试自动装载类 "A\B"
    
    D::foo();   // 使用导入规则,调用命名空间 "B" 中定义的类 "D" 的 "foo" 方法
                // 如果类 "B\D" 未找到,则尝试自动装载类 "B\D"
    
    \B\foo();   // 调用命名空间 "B" 中的函数 "foo" 
    
    \B::foo();  // 调用全局空间中的类 "B" 的 "foo" 方法
                // 如果类 "B" 未找到,则尝试自动装载类 "B"
    
    // 当前命名空间中的静态方法或函数
    
    A\B::foo();   // 调用命名空间 "A\A" 中定义的类 "B" 的 "foo" 方法
                // 如果类 "A\A\B" 未找到,则尝试自动装载类 "A\A\B"
    
    \A\B::foo();  // 调用命名空间 "A\B" 中定义的类 "B" 的 "foo" 方法
                // 如果类 "A\B" 未找到,则尝试自动装载类 "A\B"
    
    

    如果不清楚的,可以参考官方文档:

    PHP: 使用命名空间:基础 - Manual

    PHP: 名称解析规则 - Manual

  • 命名空间导入与别名

    use My\Full\Classname as MFC;
    
    

    PHP: 使用命名空间:别名/导入 - Manual

错误与异常

  • Error异常

    PHP 7 改变了大多数错误的报告方式。不同于传统(PHP 5)的错误报告机制,现在大多数错误被作为 Error 异常抛出。

    这种 Error 异常可以像 Exception 异常一样被第一个匹配的 try / catch 块所捕获。如果没有匹配的 catch 块,则调用异常处理函数(事先通过 set_exception_handler() 注册)进行处理。 如果尚未注册异常处理函数,则按照传统方式处理:被报告为一个致命错误(Fatal Error)。

    Error 类并非继承自 Exception 类,所以不能用 catch (Exception $e) { … } 来捕获 Error。你可以用 catch (Error $e) { … },或者通过注册异常处理函数( set_exception_handler())来捕获 Error。

    php7后我们都建议更多采用异常处理机制,但只有php8对Error异常处理才更完善,php7还是会出现一些无法抛出的我们认为是Error异常的错误。

  • 预定义错误异常

    这里主要介绍php7开始提供的内置Error异常,主要处理php内置的一些错误异常。但还是要注意:php7对内置的Error异常处理还不够完善,在php8会得到更完善的支持,比如 echo 1/0; ,仅警告,不会抛出Error异常。

    • Exception

      Exception是所有异常的基类。但Error不继承于Exception。

    • ErrorException

      继承于Exception。

      错误异常。

    • Error

      php7版本开始提供。Error异常弥补php的内部错误采用异常机制处理,当然错误的处理仍然支持set_error_handler()自定义处理。

      不继承于Exception。实现了Throwable接口。

      Error 是所有PHP内部错误类的基类。

    • TypeError

      继承于Error。

      有三种情况会抛出 TypeError。第一种,传递给函数的参数类型与函数预期声明的参数类型不匹配;第二种,函数返回的值与声明的函数返回类型不匹配;第三种,调用 PHP 内置函数时,传递了非法的数字参数(仅限在严格模式下 / strict mode)。

    • CompileError

      继承于Error。

      CompileError 是针对一些编译错误抛出的,之前是会发出致命错误。

    • ArgumentCountError

      继承于TypeError。

      当传递给用户定义的函数或方法的参数太少时被抛出。

    • ArithmeticError

      继承于Error。

      当执行数学运算时发生错误时被抛出。

    • AssertionError

      继承于Error。

      在函数 assert() 断言失败时被抛出。

    • DivisionByZeroError

      继承于ArithmeticError。

      当除数为零时被抛出。

      try {
          echo 1/0; // 仅警告
          intdiv(1, 0);
          echo 1%0;
      } catch (DivisionByZeroError $e) {
          echo $e->getMessage();
      }
      

    注意:

  • 参考

    PHP: 异常 - Manual

    PHP: 扩展(extend) 异常处理类 - Manual

    PHP 中 Error 和 Exception 两种异常的特性及日志记录或显示-CSDN博客

后续我们再仔细看看错误与异常相关函数。

迭代生成器

利用生成器,php也可以做到协程的效果。

  • 迭代生成器概念原理

    生成器提供了一种更容易的方法来实现简单的对象迭代,相比较定义类实现 Iterator 接口的方式,性能开销和复杂性大大降低。

    一个生成器被调用的时候,它返回一个可以被遍历的对象(迭代器)。当你遍历这个对象(迭代)的时候(例如通过一个foreach循环),PHP 将会在每次需要值的时候调用生成器函数,并在产生一个值之后保存生成器的状态,这样它就可以在需要产生下一个值的时候恢复调用状态。(目前看还是官方这段话解释最合适最容易理解,结合实际例子,再多看几遍。)

    生成器允许你在 foreach 代码块中写代码来迭代一组数据而不需要在内存中创建一个数组, 那会使你的内存达到上限,或者会占据可观的处理时间。相反,你可以写一个生成器函数,就像一个普通的自定义函数一样, 和普通函数只返回一次不同的是, 生成器可以根据需要 yield 多次,以便生成需要迭代的值。

    看一个简单例子(重写range函数):

    function xrange($start, $limit, $step = 1) {
        for ($i = 0; $i < $limit; $i += $step) { 
            yield $i + 1 => $i;//yield除了可以生成简单值外,还可以生成键值对 key=>value
        }
    }
    foreach (xrange(0, 9) as $key => $val) {
        printf("%d %d \n", $key, $val);
    }
    // 输出
    // 1 0
    // 2 1
    // 3 2
    // 4 3
    // 5 4
    // 6 5
    // 7 6
    // 8 7
    // 9 8
    
    
  • 关键字yield

    生成器函数的核心是yield关键字。它最简单的调用形式看起来像一个return申明,不同之处在于普通return会返回值并终止函数的执行,而yield会返回一个值给循环调用此生成器的代码并且只是暂停执行生成器函数。

    注意:如果在一个表达式上下文(例如在一个赋值表达式的右侧)中使用yield,你必须使用圆括号把yield申明包围起来。 例如这样是有效的:

    $data = (yield $value);

  • 生成器函数支持返回键值对

  • 生成器函数支持返回引用

  • yield from

    function three(){
        yield 1;
        yield from two();
        yield from [3,4];
          
    }
    function two(){
        yield 2;
    }
    
    foreach(three() as $num){
        echo $num."\n";
    }
    
  • send传递值

    @todo

  • 更多参考

PHP协程实现 迭代器 & 生成器 - 9ong

通俗易懂PHP迭代生成器 - 9ong

注解

php8刚引入注解,在php8之前symfony、laravel、hyperf等框架都通过反射实现了所谓的“注解”,满足AOP编程。

在官方注解未出来之前,大部分框架的注解是通过反射实现,先把类或函数的注释取到,用语法解析或者正则之类的方式匹配注解符号,假装是注解,然后处理“注解”。

官方注解的符号也一直在变:从@ 到 «» 到 @@ 再到 #[]

很需要更多的应用场景来深入理解php的注解,注解目前更多用于配置去中心化。

预定义变量

超全局变量是在全部作用域中始终可用的内置变量。

  • $GLOBALS — 引用全局作用域中可用的全部变量

  • $_SERVER — 服务器和执行环境信息

  • $_GET — HTTP GET 变量

    注意:GET 是通过 urldecode() 传递的。

  • $_POST — HTTP POST 变量

    当 HTTP POST 请求的 Content-Type 是 application/x-www-form-urlencoded 或 multipart/form-data 时,会将变量以关联数组形式传入当前脚本。

  • $_FILES — HTTP 文件上传变量

  • $_REQUEST — HTTP Request 变量

    默认情况下包含了 $_GET,$_POST 和 $_COOKIE 的数组。

    注意:以命令行方式运行时,将不包含 argv 和 argc 信息;它们将存在于 $_SERVER 数组。

  • $_SESSION — Session 变量

  • $_ENV — 环境变量

  • $_COOKIE — HTTP Cookies

  • $http_response_header — HTTP 响应头

  • $argc — 传递给脚本的参数数目

  • $argv — 传递给脚本的参数数组

上下文

上下文主要用于文件系统或数据流封装协议。

上下文(Context)由 stream_context_create() 创建。选项可通过 stream_context_set_option() 设置,参数可通过 stream_context_set_params() 设置。

我们以HTTP协议上下文为例:


$postdata = http_build_query(
    array(
        'var1' => 'some content',
        'var2' => 'doh'
    )
);

$opts = array('http' =>
    array(
        'method'  => 'POST',
        'header'  => 'Content-type: application/x-www-form-urlencoded',
        'content' => $postdata
    )
);

$context = stream_context_create($opts);

$result = file_get_contents('http://example.com/submit.php', false, $context);

参考:PHP: HTTP context 选项 - Manual

除了HTTP上下文,还有套接字、FTP、SSL、CURL、Phar、MongoDB等上下文选项与参数设置,详见:PHP: 上下文(Context)选项和参数 - Manual

支持的协议和封装协议

用于描述一个封装协议的 URL 语法仅支持 scheme://… 的语法

  • file:// — 访问本地文件系统

  • http:// — 访问 HTTP(s) 网址

  • ftp:// — 访问 FTP(s) URLs

  • php:// — 访问各个输入/输出流(I/O streams)

    重点关注php://input:

    • php://input

      php://input是个可以访问请求的原始数据的只读流。 POST 请求的情况下,最好使用 php://input 来代替 $HTTP_RAW_POST_DATA,因为它不依赖于特定的 php.ini 指令。

      注意:enctype="multipart/form-data” 的时候 php://input 是无效的。

    • php://output

      php://output是一个只写的数据流, 允许以 print 和 echo 一样的方式 写入到输出缓冲区。

  • zlib:// — 压缩流

  • data:// — 数据(RFC 2397)

    语法:data://text/plain;base64,

    // 打印 "I love PHP"
    echo file_get_contents('data://text/plain;base64,SSBsb3ZlIFBIUAo=');
    

    经常会把小的图片通过data://协议直接将图片内容放到代码里,省去加载文件或请求链接的时间消耗。

  • glob:// — 查找匹配的文件路径模式

  • phar:// — PHP 归档

  • ssh2:// — Secure Shell 2

  • rar:// — RAR

  • ogg:// — 音频流

  • expect:// — 处理交互式的流

安全

php常被说不安全,是php不安全吗?肯定不存在绝对安全的语言,但php也足够安全,很多 PHP 程序所存在的重大弱点并不是 PHP 语言本身的问题,而是编程者的安全意识不高而导致的。因此,必须时时注意每一段代码可能存在的问题,去发现非正确数据提交时可能造成的影响。

  • 比如错误日志导致的安全问题

    比如:一个php错误日志可能会暴露web服务器具有什么权限、正在使用的php版本、数据库、文件组织结构、框架等信息。这些暴露的信息往往会提供攻击者推敲的可能。

    有三个常用的办法处理这些问题。第一个是彻底地检查所有函数,并尝试弥补大多数错误。第二个是对在线系统彻底关闭错误报告。第三个是使用 PHP 自定义的错误处理函数创建自己的错误处理机制。根据不同的安全策略,三种方法可能都适用。

    一个能提前阻止这个问题发生的方法就是利用 error_reporting() 来帮助使代码更安全并发现变量使用的危险之处。在发布程序之前,先打开 E_ALL 测试代码,可以帮你很快找到变量使用不当的地方。一旦准备正式发布,就应该把 error_reporting() 的参数设为 0 来彻底关闭错误报告或者把 php.ini 中的 display_errors 设为 off 来关闭所有的错误显示以将代码隔绝于探测。当然,如果要迟一些再这样做,就不要忘记打开 ini 文件内的 log_errors 选项,并通过 error_log 指定用于记录错误信息的文件。

  • 比如用户提交数据导致安全问题

    必须时常留意我们的代码,以确保每一个从客户端提交的变量都经过适当的检查,然后问自己以下一些问题:

    • 此脚本是否只能影响所预期的文件?
    • 非正常的数据被提交后能否产生作用?
    • 此脚本能用于计划外的用途吗?
    • 此脚本能否和其它脚本结合起来做坏事?
    • 是否所有的事务都被充分记录了?

    记住:用户提交的数据是不安全的,我们需要适当检查过滤用户提交的数据。

  • 尽量隐藏php相关环境信息

    • 在 php.ini 文件里设置 expose_php = off ,可以减少他们能获得的有用信息。

      expose_php=Off
      display_errors=Off
      
    • web 服务器用 PHP 解析不同扩展名

      # 使 PHP 代码看上去像 HTML 页面
      AddType application/x-httpd-php .htm .html
      
    • 隐藏语言及错误日志

      error_reporting(0);
      header("X-Powered-By: ASP.NET");
      
    • nginx或apache中rewrite设置

    • 框架路由

特点

  • 用php进行http认证

    可以用 header() 函数来向客户端浏览器发送“Authentication Required”信息,使其弹出一个用户名/密码输入窗口。当用户输入用户名和密码后,包含有 URL 的 PHP 脚本将会加上预定义变量 PHP_AUTH_USER,PHP_AUTH_PW 和 AUTH_TYPE 被再次调用,这三个变量分别被设定为用户名,密码和认证类型。

    $valid_passwords = array ("user1" => "passwd1","user2" => "passwd2");
    $valid_users = array_keys($valid_passwords);
    
    $user = $_SERVER['PHP_AUTH_USER'];
    $pass = $_SERVER['PHP_AUTH_PW'];
    
    $validated = (in_array($user, $valid_users)) && ($pass == $valid_passwords[$user]);
    
    if (!$validated) {
        header('WWW-Authenticate: Basic realm="My Realm"');
        header('HTTP/1.0 401 Unauthorized');
        die ("Not authorized");
    }
    
    // If arrives here, is a valid user.
    echo "<p>Welcome $user.</p>";
    echo "<p>Congratulation, you are into the system.</p>";
    
  • cookie

    通俗理解session与cookie运行机制 - 9ong

  • 文件上传

    • 支持上传多文件

      数组 $_FILES[‘userfile’],$_FILES[‘userfile’][‘name’] 和 $_FILES[‘userfile’][‘size’] 将被初始化。

      PHP: POST 方法上传 - Manual

    • 前端MAX_FILE_SIZE设置

      MAX_FILE_SIZE 隐藏字段(单位为字节)必须放在文件输入字段之前,其值为接收文件的最大值。这是对浏览器的一个建议,PHP 也会检查此项。

      当然浏览器端可以简单绕过此设置,因此不要指望用此特性来阻挡大文件。

      实际上,PHP 设置中的上传文件最大值是不会失效的。但是最好还是在表单中加上此项目,因为它可以避免用户在花时间等待上传大文件之后才发现文件过大上传失败的麻烦。

      所以我们建议前端要设置MAX_FILE_SIZE,在后端也要配置允许接收的文件最大值,甚至在逻辑代码中需要检查。

    • 上传文件常见错误信息

      PHP: POST上传文件 - 错误信息说明 - Manual

      $_FILES[‘userfile’][‘error’] 值 为0 时,表示无错误,文件上传成功。

    • 上传文件失败的常见原因

      • 前端MAX_FILE_SIZE 设置的值,不能大于 ini 设置中 upload_max_filesize 选项设置的值。其默认值为 2M。
      • 如果 post_max_size 设置的值太小,则较大的文件会无法被上传。因此,请保证 post_max_size 的值足够的大。
      • 如果 max_execution_time 设置的值太小,脚本运行的时间可能会超过该设置。因此,也请保证 max_execution_time 足够的大。
      • 如果内存限制设置被激活,可能需要将 memory_limit 设置的更大些,请确认 memory_limit 的设置足够的大。
  • 使用远程文件

    只要在 php.ini 文件中激活了 allow_url_fopen 选项,就可以在大多数需要用文件名作为参数的函数中使用 HTTP 和 FTP 的 URL 来代替文件名。同时,也可以在 include、include_once、require 及 require_once 语句中使用 URL。

    所以像fopen、file_get_content、include等还可以打开远程文件的。

    $file = fopen ("http://www.example.com/", "r");
    

    使用远程文件,既可以打开远程文件用来写入数据,比如远程日志,也可以打开远程URL获取远程数据。

命令行模式

  • 运行指定php文件

    php script.php
    
  • 在命令行直接运行PHP代码

    php -r 'print_r(get_defined_constants())'
    
  • shell方式

    #!/usr/bin/php
    <?php
        var_dump($argv);
    ?>
    

    现在我们就可以像shell脚本或perl脚本一样执行php脚本了:

    ~$ chmod +x test
    ~$ ./test -a -b 
    array(3) {
        [0]=>
        string(6) "./test"
        [1]=>
        string(2) "-a"
        [2]=>
        string(2) "-b"            
    }
    

php内置web服务

PHP 5.4.0起, CLI SAPI 提供了一个内置的Web服务器。

这个内置的Web服务器主要用于本地开发使用,不可用于线上产品环境。

URI请求会被发送到PHP所在的的工作目录(Working Directory)进行处理,除非你使用了-t参数来自定义不同的目录。

如果请求未指定执行哪个PHP文件,则默认执行目录内的index.php 或者 index.html。如果这两个文件都不存在,服务器会返回404错误。

当你在命令行启动这个Web Server时,如果指定了一个PHP文件,则这个文件会作为一个“路由”脚本,意味着每次请求都会先执行这个脚本。如果这个脚本返回 false ,那么直接返回请求的文件(例如请求静态文件不作任何处理)。否则会把输出返回到浏览器。

jm@ubuntu:~$ php -S 192.168.8.130:8000 -t /var/www/html/demo

PHP 7.1.26-1+ubuntu16.04.1+deb.sury.org+1 Development Server started at Sun Jan 17 16:32:31 2021
Listening on http://192.168.8.130:8000
Document root is /var/www/html/demo

访问

http://192.168.8.130:8000/x.php

垃圾回收机制

虽然php有自动回收机制,但作为php开发人员还是很有必要了解php的垃圾回收机制:

PHP: 引用计数基本知识 - Manual

PHP: 回收周期(Collecting Cycles) - Manual

PHP: 性能方面考虑的因素 - Manual