分类
php

你需要了解的 PHP 语法特性

经过前面系列文档,我们已经快速熟悉了 PHP 当中的基本语法。这些基本的语法足够让我们写一些基本的片段、Demo 了,但仍旧无法让我们写出优雅、工程化的代码。所以这一篇文章将着重讲述如何将 PHP 中的代码组织起来,写得好看一些。起流程

框架的流程

我们以登录注册为例,来说说框架的流程是什么样子的?如下图所示:

文件引入(Require And Include)

计算机领域中最核心的思想概念就是分而治之,分层分模块。换句话说就是将代码划分到不同的文件中,你难以想象一个代码文件中有成千上万行代码,如何阅读?如何维护?所以,真实的项目中,我们会将代码划分在不同的文件中,每个文件中不同的存放相同的代码。

那么在 PHP 中,如何引入其他文件中的代码呢?有两组四个语句: require 和 require_once , include 和 include_once 。它们之间有什么区别呢?

  • require 和 include 的区别在于主要在于对待错误的不同态度:
  • require 如果引入的文件中存在错误,则会停止执行剩下的程序
  • include 如果引入的文件中存在错误,则会继续执行剩下的程序
  • 所以,优先使用 require 
  • require_once 和 include_once 中的 once 指的是相同的文件只会引入一次,而不会重复引入,优先使用 require_once ,而不是使用 require 。

示例代码如下:

<?php

require_once "/path/filename";

本质上,引入文件就是执行文件中的所有代码。

命名空间(Namespace)

很多语言中都存在命名空间的概念,作用是类似的,就是为了防止程序中的实体(变量、函数、类)重名。你可能会奇怪,代码都是我们自己写的,怎么会重名呢?换一个名字不就好了吗?不是的,真实的项目中都是团队协作,而且我们还引入了大量的第三方代码库,难以保证不重名。

我们来看下面的代码,这个是一个错误的示例:

<?php

class Person {}
class Person {}		
// 执行代码会给出 “Fatal error: Cannot declare class Person, because the name is already in use in /Volumes/project/demo/mantu-/project/test.php on line 4” 的错误,意思就是重复定义了同名的类

这就说明类是不能够重复定义的,但是我们可以给 类加上前缀 ,比如在一个年级段中如果有人重名,我们就会说某某班级某某人,当中某某班级就是前缀,用作区分不同的人。比如说我的老家所在的村子叫做新坊村,在全国一共有九个同名的存在。所以当我们在国内说起这个地名的时候,就需要加上前缀,比如说,温州市永嘉县枫林镇新坊村,或者福建上杭县旧县乡新坊村。这样的例子在生活中比比皆是。

不同的语言对命名空间的实现是不一样的,比如在 Java 中是以目录层级为命名空间的,但是 在 PHP 中,命名空间从语法层面上来说是和目录层级无关的 。

我们来处理上面这个错误的例子:

<?php

namespace HangZhou;
class Person {}
echo Person::class . PHP_EOL;   // 输出: HangZhou\Person
namespace ShangHai;
class Person {}
echo Person::class . PHP_EOL;   // 输出: ShangHai\Person

从上面的例子中可以看到,我们使用 echo 输出的类名。完整的类名实际上是“命名空间+类名”,这样不同的人、不同的团队、不同的项目都会使用命名空间来区分名称。这样我们就不会重名了。

在 PHP 中,提供了 use 来简化命名空间的编写:

<?php
namespace HangZhou\Person;

class Bob{}
class Tom{}
class John{}

namespace Workspace;

use HangZhou\Person\Bob;

$bob = new Bob;

如果我们要引入多个相同命令空间的类呢?可以有两种写法,第一种如下:

<?php
namespace Workspace;

use HangZhou\Person\Bob;
use HangZhou\Person\Tom;
use HangZhou\Person\John;

上面的写法也可以简化成为一行代码,如下:

<?php
namespace Workspace;

use HangZhou\Person\{Bob, Tom, John};		// 很明显,这种写法更加的简洁

如果引入不同的命名空间的相同名称的类,也会发生重名,这时候我们可以采用 as 关键字给引入的类名设定别名(Alias):

<?php

namespace ShangHai;
class Tom {}

namespace HangZhou;
class Tom{}

namespace Workspace;

use ShangHai\Tom as ShangHaiTom;
use HangZhou\Tom as HangZhouTom;

所有的实体,都有命名空间。如果没有特殊声明,那么命名空间就是 \ ,所有声明了命名空间的实体,也是以 \ 开始的,只是这个根命名空间可以省略。比如上面例子中的 ShangHai\Tom 实际上完整的写法是 \ShangHai\Tom ,这个和 Linux 下的目录是类似的,都是从根(root)开始的。

类的自动加载(Class Autoload)

前面我们已经铺垫了文件引入以及命名空间的概念,在这个基础上,我们还需要引入一个新的概念,叫做类的自动加载。我们来思考一个问题,在真实的项目中,可能会有成百上千的代码文件,这些代码文件难道都需要用如下的方式手动引入吗?

<?php
require_once '/path/file1.php';
require_once '/path/file2.php';
require_once '/path/filen.php';

估计你这个代码要写到手残,所以我们肯定有更高级的做法,我们可以向 PHP 注册一个函数,当类没有找到的时候,PHP 会主动执行这个函数去自动引入这个类所在的代码文件。如下示例:

<?php
// 注册一个匿名函数,然后在这个函数中引入文件
spl_autoload_register(function () {
    require_once 'file1.php';
    require_once 'file2.php';
    require_once 'filen.php';
});
// 假设这个类写在 file1.php 中,PHP 根本不知道 Bob 在那个文件里,但是我们事先注册了匿名函数,所以 PHP 会先去执行那个匿名函数,引入了我们的`file1.php`,所以 PHP 也知道了 Bob 这个类所在的文件
$bob = new Bob();       
$bob = new John();       // 假设这个类写在 file2.php 中,和 file1.php 同理

你看了这个代码,估计会觉得奇怪,使用 spl_autoload_register 注册一个函数,在注册的函数中我们仍旧要一行行的导入文件啊。对的,但是仔细观察,其实命名空间路径很像,只是文件路径使用 / ,而命名空间使用 \ 。所以,如果我们的命名空间和文件路径保持一致,是不是就可以根据命名空间来找到文件所在的路径了?是的,我们来写写看:

<?php
spl_autoload_register(function ($class) {
  	// 把命名空间中的`\`替换成`/`,然后拼接上文件后缀`.php`就成了文件的路径了。
    require_once str_replace('\\', '/', $class) . '.php';
});

只要所有的类都按照这个规范来编写,我们就可以事先类的自动加载了。比如:

<?php
use App\Controller\Users\Bob;  // 这个类在`App/Controller/Users/Bob.php`文件中
use App\Service\Coupon;				// 这个类在`App/Service/Coupon.php` 文件中

异常和错误处理(Exception And Error)

在 PHP 中,大部分的都是异常,可以使用 try...catch... 进行捕获,但有些是错误,比如说语法错误、除数为 0 等。我们需要对程序中的异常和错误进行处理,处理的方法和类的自动加载非常相似:

<?php
set_exception_handler(function (Exception $exception) {
    // 发送了错误进行处理
    header('Content-Type:application/json');
  	// 将异常转换为 json 返回给调用方
    echo json_encode([
        'message' => $exception->getMessage(),
        'code' => $exception->getCode(),
    ]);
  	die();		// 终止程序执行
});

错误处理也是一样的,只是使用 set_error_handler 函数进行注册。

静态延迟绑定

什么是静态延迟绑定呢?就是我们可以在父类中调用子类中的静态方法或者属性:

<?php
class Model 
{
    protected static $table = '';
    public static function init()
    {
        echo 'table:' . self::$table;       // 非静态延迟绑定: table:
        echo 'table:' . static::$table;     // 静态延迟绑定: table: User
    }
}
class User extends Model
{
    protected static $table = 'User';
}
User::init();

那么实例对象呢?不需要,在父类中通过 $this 关键字就可以获取子类中的属性或方法。如果是在子类中获取父类的成员呢?不管是实例还是静态,都使用 parent:: 这样的语法。

静态延迟绑定在框架中非常得常见,要掌握的。

发表评论

邮箱地址不会被公开。 必填项已用*标注