Category Archives: PHP

PHP是一个拥有众多开发者的开源软件项目。PHP是在1994年由Rasmus Lerdorf创建的 ,1995年,PHP(Personal Home Page Tools)对外发表第一个版本PHP1。经过二十多年的发展,随着php-cli相关组件的快速发展和完善,PHP已经可以应用在 TCP/UDP服务、高性能Web、WebSocket服务、物联网、实时通讯、游戏、微服务等非 Web 领域的系统研发。

用 PHP 8 里的枚举特性来优化 API 错误码管理

​在使用 PHP 开发 API 时,经常会遇到需要返回错误信息的场景。前端根据返回的错误码,再进行相应的操作。

发现问题

这里用 ThinkPHP8 举个例子,这是一个用户登录接口的例子:

public function login()
{
    $username = $this->request->param('username');
    $password = $this->request->param('password');

    if (!$username || !$password) {
        $result = ['code' => 1001, 'msg' => '参数不完整'];
    } else if ($username !== 'admin') {
        $result = ['code' => 1002, 'msg' => '用户不存在'];
    } else if ($password !== '123456') {
        $result = ['code' => 1003, 'msg' => '密码错误'];
    } else {
        $result = ['code' => 1, 'msg' => '登录成功'];
    }

    return json($result);
}

上面的代码里,对提交的参数进行一些简单的判断,定义了 $result 数组,并为每个不同的错误都提供了一个错误码和提示信息。最后返回给前端。为了让前端能理解错误码,后端开发人员需要把上面的 code 和 msg 都写在 api 文档的错误码里,以供前端参考。

不过大家是否觉得这种直接在代码里零散地写 code 和 msg 不够直观和优雅(在实际开发过程中,不大可能会如此集中地显示错误信息),而且容易不小心把 code 写错或者重复?或许我们可以换一种方式来表达错误码。

解决方案

我先把调整后的代码贴出:

public function login()
{
    $username = $this->request->param('username');
    $password = $this->request->param('password');

    if (!$username || !$password) {
        $status = LoginStatus::PARAM_INCOMPLETE;
    } else if ($username !== 'admin') {
        $status = LoginStatus::USER_NOT_FOUND;
    } else if ($password !== '123456') {
        $status = LoginStatus::PASSWORD_WRONG;
    } else {
        $status = LoginStatus::SUCCESS;
    }

    return json($status->apiResult());
}

上面代码没有了 code 和 msg 信息,但是却不难理解 $status 所表达的含义,让代码看上去清爽、直观很多。

Continue reading →

基于ThinkPHP里模型搜索器的高效数据查询解决方案

在项目开发时,特别是后台管理功能里,数据搜索几乎是无处不在的。

发现问题

​例如我们在开发一个项目时,需要在后台增加一个用户搜索的功能。以ThinkPHP(下面简称tp)为例,如果在没有使用开源的后台开发框架或者追求快速开发时,我们可能会采用下面这段代码:

public function index()
    {
        //姓名关键字搜索
        $name = $this->request->get('name');
        //手机号搜索
        $mobile = $this->request->get('mobile');
        $paginate = User::where('mobile',$mobile)
            ->where('name', 'like', "%{$name}%")
            ->paginate();
        return json($paginate->toArray());
    }

这种解决方法的弊端就是一个查询页面就要写一段代码。写的多了,你就会发现,你的编码时间被这些不断重复的代码所占据,枯燥且乏味。于是你肯定会想办法把这个查询功能提取出来,统一写成一个方法。

于是乎,我们就想到了再新建一个控制器父类,写一个统一的index方法,让控制器子类继承:

class MyController extends BaseController
{
    // 模型名称
    protected Model $model;

    public function index()
    {
        //搜索参数
        $params = $this->request->get('params/a', []);
        //搜索操作符
        $operators = $this->request->get('operators/a', []);
        //排序
        $order = $this->request->get('order/a', []);

        //构建查询
        $query = $this->model->where(function ($query) use ($params, $operators) {
            //遍历搜索参数
            foreach ($params as $field => $value) {
                $op = $operators[$field] ?? '=';
                if ('like' === $op) {
                    $value = "%{$value}%";
                }
                //搜索条件
                $query->where($field, $op, $value);
            }
        });

        //排序
        if ($order) {
            $query->order($order['field'], $order['sort'] ?? 'asc');
        }

        //搜索数据并分页
        $paginate = $query->paginate();
        return json($paginate->toArray());
    }
}

在这个index方法里,我们约定了查询相关参数,params数组表示准备查询的字段名和查询值,operators数组表示字段查询方式(例如:like,in等),order数组表示排序。

Continue reading →

利用 PHP 8 的注解特性来实现依赖注入

在使用 ThinkPHP(以下简称 tp)开发时,我们经常会遇到需要依赖注入的场景。比如在控制器类方法里注入 Request 类:

public function index(Request $request)
{
    // todo
}

但是,当我们需要一个类为整个控制器服务时,上述方法就不适用了。这种方式只适合某一个方法内使用。如果多个方法都要使用这个类,那么就需要将该类作为控制器的成员变量来使用。

class Index {
    private Request $request;
}

既然作为成员变量使用,那么我们需要在系统调用控制器方法前实例化 Request,比如放在构造函数里实例化:

public function __construct()
{
    // tp6/8 里 app 函数可以实例化 Request 等类
    $this->request = app('request');
}

当然,我们也可以利用 tp 的自动注入机制来实例化:

public function __construct(Request $request)
{
    $this->request = $request;
}

这种方式在一个或两个控制器里使用时还是方便的。但是,当项目里有较多控制器时,这种方法显得繁琐。

Continue reading →

PHP8有什么新特征

联合类型(Union Types)

可以声明变量可能的类型。

class Number {
    private int|float $number;
 
    public function setNumber(int|float $number): void {
        $this->number = $number;
    }
 
    public function getNumber(): int|float {
        return $this->number;
    }
}

添加了 WeakMap

允许数组中的 key 放入对象,如:$map[$obj] = 42;

添加了 ValueError 类。

当函数或方法接收到具有正确类型的参数(错误类型应引发 TypeError 但值不合适时,将引发 ValueError 。

类的变更、使用

1、可变参数继承,允许

class A {
    public function method(int $many, string $parameters, $here) {}
}
class B extends A {
    public function method(...$everything) {}
}

2、后期静态绑定(LSB)(有用),对框架级别的封装、一些工厂设计模式有用。

class Test {
    public function create(): static {
          return new static();
     }
}

3、现在可以使用以下方法获取对象的类名称。

$object::class 等价 get_class($object)

4、现在,new 和 instanceof 可以与任意表达式一起使用,使用

new(expression)(... $args)$obj instanceof(expression)

5、现在允许写。

Foo::BAR::$baz

6、添加 Stringable 接口(作用一般,用在视图模板封装)。

只要类实现了__toString,那么这类自动实现了 Stringable 接口。

class Foo{
    public function __toString(): string
    {
        return 'foo';
    }
}
function bar(Stringable $stringable) { 
/* 虽然Foo没有实现Stringable,但是这里正常的。 */
 }
bar(new Foo());
bar('abc');

7、trait 现在可以定义抽象的私有方法。

Continue reading →