php强类型编码下的db操作类设计思考

我对php代码的编写,有一个要求是编写出编辑器可读的代码,换句话说就是编辑器能够点击跳转的代码。
如今的db操作库在执行查询方法后,返回的数据要么是collection集合,要么是mixed类型,编辑器无法提示返回值里面具体有什么。
所以我写了一些伪代码,大伙看看这个设计有没有搞头,或者有没有类似的解决方案。

定义PrimaryKey注解

<?php

namespace App\Models\Test;

use Attribute;

/**
 * 标记主键
 */
#[Attribute] class PrimaryKey
{
}

定义DB

<?php

namespace App\Models\Test;

use ReflectionClass;
use ReflectionProperty;

class DB
{
    protected string $connection = '';
    protected string $table = '';

    /**
     * 查询条件
     * @param string $column 查询列
     * @param string|null $operator 查询操作符
     * @param mixed|null $value 查询值
     * @param string $boolean 查询条件连接符
     * @return $this
     */
    public function where(string $column, string|null $operator = null, mixed $value = null, string $boolean = 'and'): static
    {
        return $this;
    }

    /**
     * 获取所有
     * @return array|static[]
     */
    public function get(): array
    {
        return [
            new static(),
            new static()
        ];
    }

    /**
     * 获取第一个
     * @return $this|null
     */
    public function find(): static|null
    {
        return new static;
    }

    /**
     * 转换为数组
     * @return array
     */
    public function toArray(): array
    {
        //反射子类,并遍历子类的属性
        $reflection = new ReflectionClass($this);
        $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
        $data = [];
        foreach ($properties as $property) {
            $propertyName = $property->getName();
            $data[$propertyName] = $this->$propertyName;
        }
        return $data;
    }

    /**
     * 删除
     * @return int 删除的行数
     */
    public function delete(): int
    {
        return 1;
    }

    /**
     * 创建
     * @return bool 创建成功或失败
     */
    public function create(): bool
    {
        return 1;
    }

    /**
     * 更新
     * @return int 更新的行数
     */
    public function update(): int
    {
        return 1;
    }
}

定义表user的模型

<?php

namespace App\Models\Test;

class  User extends  DB
{
    protected string $connection = 'test_db';
    protected string $table = 'user';
    #[PrimaryKey]
    public int $userId = 0;
    public string $username = '';
    public string $password = '';
    public string $nickname = '';
    public string $avatar = '';
}

定义表order的模型

<?php

namespace App\Models\Test;

class Order extends  DB
{
    protected string $connection = 'test_db';
    protected string $table = 'order';
    #[PrimaryKey]
    public int $orderNo = 0;
    public int $userId = 0;
    public int $amount = 0;
    /**
     * @var int 订单状态【0:待支付 1:支付成功 2:支付失败】
     */
    public int $status = 0;

    const STATUS_WAIT = 0;
    const STATUS_SUCCESS = 1;
    const STATUS_FAIL = 2;
}

增删改查示例

<?php

namespace App\Http\Controllers;

use App\Models\Test\Order;
use App\Models\Test\User;
use Symfony\Component\HttpFoundation\Response;

class TestController extends Controller
{
    /**
     * 查询示例
     * @return Response
     */
    public function index(): Response
    {
        $admin = (new User)->where('username', '=', 'admin')->find();
        $users = (new User)->where('nickname', 'like', "%$admin->nickname%")->get();
        $ret = [];
        foreach ($users as $user) {
            $tmp = [
                'username' => $user->username,
                'nickname' => $user->nickname,
                'avatar' => $user->avatar
            ];
            $order = (new Order)->where('user_id', '=', $user->userId)->find();
            if ($order && $order->status = Order::STATUS_SUCCESS) {
                $tmp['order'] = $order->toArray();
            } else {
                $tmp['order'] = null;
            }
            $ret[] = $tmp;
        }
        return $this->success($ret);
    }

    /**
     * 新增示例
     * @return Response
     */
    public function create(): Response
    {
        $user = new User();
        $user->username = 'test';
        $user->nickname = 'test';
        $user->avatar = 'test';
        return $user->create() ? $this->success() : $this->error('新增失败', 1);
    }

    /**
     * 删除示例
     * @return Response
     */
    public function delete(): Response
    {
        $ok = (new Order)->where('user_id', '=', 1)->delete();
        return $ok ? $this->success() : $this->error('删除失败', 1);
    }

    /**
     * 更新示例
     * @return Response
     */
    public function update(): Response
    {
        $user = new User();
        $user->username = 'test';
        $ok = $user->where('userId', '=', 1)->update();
        return $ok ? $this->success() : $this->error('更新失败', 1);
    }
}

优缺点分析:

  • 优点:getfind返回的数据,编辑器是可是识别的。
  • 缺点:使用返回的数据时,编辑器既提示了表模型本身的属性字段,也提示了表模型父类的方法,这些方法在操作返回值的时候也会提示,属于提示噪音。

“提示”噪音的解决思路

首先是表模型不再继承DB类,然后是改造DB类的findget方法。
find方法返回单条数据时,可以改为传递model对象,并将查询的数据填写到对象身上,类似go中的设计:

order := TestDbEntity.Order{}
TestDbDao.Order.Scan(&order)

但是get方法返回的是多条数据,此时由于php没有强类型的数组,并不能像go一样,实现如下的写法:

var orderList []TestDbEntity.Order
TestDbDao.Order.Scan(&orderList)

折中的思路是让表模型继承一个基础模型,然后基础模型提供public static function makes(): array方法。
整个实现如下:

定义ModelInterface接口

<?php

namespace App\Models\Test;

interface ModelInterface
{
    public static function getConnection(): string;

    public static function getTable(): string;

    public static function makes(): array;

    public static function make(): static;

    public function toArray(): array;
}

实现ModelInterface接口

<?php

namespace App\Models\Test;

use ReflectionClass;
use ReflectionProperty;

class  Model implements ModelInterface
{
    protected static string $connection = '';
    protected static string $table = '';

    /**
     * @return array | static[]
     */
    public static function makes(): array
    {
        return [];
    }

    /**
     * @return static
     */
    public static function make(): static
    {
        return new static();
    }

    public static function getConnection(): string
    {
        return static::$connection;
    }

    public static function getTable(): string
    {
        return static::$table;
    }

    /**
     * 转换为数组
     * @return array
     */
    public function toArray(): array
    {
        //反射子类,并遍历子类的属性
        $reflection = new ReflectionClass($this);
        $properties = $reflection->getProperties(ReflectionProperty::IS_PUBLIC);
        $data = [];
        foreach ($properties as $property) {
            $propertyName = $property->getName();
            $data[$propertyName] = $this->$propertyName;
        }
        return $data;
    }

}

表模型继承基础Model类

<?php

namespace App\Models\Test;

class Order extends Model
{
    protected static string $connection = 'test_db';
    protected static string $table = 'order';
    #[PrimaryKey]
  public int $orderNo = 0;
    public int $userId = 0;
    public int $amount = 0;
    /**
 * @var int 订单状态【0:待支付 1:支付成功 2:支付失败】
  */
  public int $status = 0;

    const STATUS_WAIT = 0;
    const STATUS_SUCCESS = 1;
    const STATUS_FAIL = 2;
}
<?php

namespace App\Models\Test;

class  User extends Model
{
    protected static string $connection = 'test_db';
    protected static string $table = 'user';
    #[PrimaryKey]
    public int $userId = 0;
    public string $username = '';
    public string $password = '';
    public string $nickname = '';
    public string $avatar = '';
}

改写DB

<?php

namespace App\Models\Test;

class DB
{
    protected string $connection = '';
    protected string $table = '';
    /**
     * @var string |ModelInterface
     */
    protected string|ModelInterface $model = '';

    public static function make(string $model): static
    {
        $ret = new static;
        $ret->model = $model;
        return $ret;
    }

    /**
     * 查询条件
     * @param string $column 查询列
     * @param string|null $operator 查询操作符
     * @param mixed|null $value 查询值
     * @param string $boolean 查询条件连接符
     * @return $this
     */
    public function where(string $column, string|null $operator = null, mixed $value = null, string $boolean = 'and'): static
    {
        return $this;
    }

    /**
     * 获取列表
     * @param array|ModelInterface[] $models
     */
    public function get(array &$models): void
    {
        $models[] = $this->model::make();
        $models[] = $this->model::make();
    }

    /**
     * 获取第一个
     * @param ModelInterface $model
     * @return bool
     */
    public function find(ModelInterface $model): bool
    {
        return true;
    }

    /**
     * 删除
     * @return int 删除的行数
     */
    public function delete(): int
    {
        return 1;
    }

    /**
     * 创建
     * @return bool 创建成功或失败
     */
    public function create(ModelInterface $model): bool
    {
        return 1;
    }

    /**
     * 更新
     * @return int 更新的行数
     */
    public function update(ModelInterface $model): int
    {
        return 1;
    }
}

改写增删改查示例

<?php

namespace App\Http\Controllers;

use App\Models\Test\DB;
use App\Models\Test\Order;
use App\Models\Test\User;
use Symfony\Component\HttpFoundation\Response;

class TestController extends Controller
{
    /**
     * 查询示例
     * @return Response
     */
    public function index(): Response
    {
        $admin = User::make();
        DB::make(User::class)->where('username', '=', 'admin')->find($admin);
        $users = User::makes();
        DB::make(User::class)->where('nickname', 'like', "%$admin->nickname%")->get($users);
        $ret = [];
        foreach ($users as $user) {
            $tmp = [
                'username' => $user->username,
                'nickname' => $user->nickname,
                'avatar' => $user->avatar
            ];
            $order = new Order;
            $ok = DB::make(Order::class)->where('user_id', '=', $user->userId)->find($order);
            if ($ok && $order->status = Order::STATUS_SUCCESS) {
                $tmp['order'] = $order->toArray();
            } else {
                $tmp['order'] = null;
            }
            $ret[] = $tmp;
        }
        return $this->success($ret);
    }

    /**
     * 新增示例
     * @return Response
     */
    public function create(): Response
    {
        $user = new User();
        $user->username = 'test';
        $user->nickname = 'test';
        $user->avatar = 'test';
        return DB::make(User::class)->create($user) ? $this->success() : $this->error('新增失败', 1);
    }

    /**
     * 删除示例
     * @return Response
     */
    public function delete(): Response
    {
        $ok = DB::make(Order::class)->where('user_id', '=', 1)->delete();
        return $ok ? $this->success() : $this->error('删除失败', 1);
    }

    /**
     * 更新示例
     * @return Response
     */
    public function update(): Response
    {
        $user = new User();
        $user->username = 'test';
        $ok = DB::make(User::class)->where('userId', '=', 1)->update($user);
        return $ok ? $this->success() : $this->error('更新失败', 1);
    }
}
本作品采用《CC 协议》,转载必须注明作者和本文链接
梦想星辰大海
讨论数量: 1

你说的方案不是很好,你用idea-help+laravel idea 能实现你的编辑器能够点击跳转的代码

/**
 * @method static static updateOrCreate(array $attributes, array $values = [])
 * @method static static|null find(string|int $id, $columns = ['*'])
 * @method static static create(array $attributes = [])
 * @method static Collection<static> all($columns = ['*'])
 * @method static static|Builder query()
 */
class BaseModel extends Model 
{

}


/**
 * @mixin IdeHelperOrder
 */
class BaseModel extends Model
{
    protected static string $connection = 'test_db';
    protected static string $table = 'order';

}
1天前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!