构建自己的PHP框架--实现Model类

席俊达
2023-12-01

在之前的博客中,我们定义了ORM的接口,以及决定了使用PDO去实现。最后我们提到会有一个Model类实现ModelInterface接口。

现在我们来实现这个接口,如下:

<?php
namespace sf\db;

use PDO;

/**
 * Model is the base class for data models.
 * @author Harry Sun <sunguangjun@126.com>
 */
class Model implements ModelInterface
{
    /**
 * Declares the name of the database table associated with this Model class.
 * @return string the table name
 */
    public static function tableName()
    {
        return get_called_class();
    }

    /**
 * Returns the primary key **name(s)** for this Model class.
 * @return string[] the primary key name(s) for this Model class.
 */
    public static function primaryKey()
    {
        return ['id'];
    }

    /**
 * Returns a single model instance by a primary key or an array of column values.
 *
 * ```php
 * // find the first customer whose age is 30 and whose status is 1
 * $customer = Customer::findOne(['age' => 30, 'status' => 1]);
 * ```
 *
 * @param mixed $condition a set of column values
 * @return static|null Model instance matching the condition, or null if nothing matches.
 */
    public static function findOne($condition)
    {
        
    }

    /**
 * Returns a list of models that match the specified primary key value(s) or a set of column values.
 *
 * ```php
 * // find customers whose age is 30 and whose status is 1
 * $customers = Customer::findAll(['age' => 30, 'status' => 1]);
 * ```
 *
 * @param mixed $condition a set of column values
 * @return array an array of Model instance, or an empty array if nothing matches.
 */
    public static function findAll($condition)
    {

    }

    /**
 * Updates models using the provided attribute values and conditions.
 * For example, to change the status to be 2 for all customers whose status is 1:
 *
 * ~~~
 * Customer::updateAll(['status' => 1], ['status' => '2']);
 * ~~~
 *
 * @param array $attributes attribute values (name-value pairs) to be saved for the model.
 * @param array $condition the condition that matches the models that should get updated.
 * An empty condition will match all models.
 * @return integer the number of rows updated
 */
    public static function updateAll($condition, $attributes)
    {

    }

    /**
 * Deletes models using the provided conditions.
 * WARNING: If you do not specify any condition, this method will delete ALL rows in the table.
 *
 * For example, to delete all customers whose status is 3:
 *
 * ~~~
 * Customer::deleteAll([status = 3]);
 * ~~~
 *
 * @param array $condition the condition that matches the models that should get deleted.
 * An empty condition will match all models.
 * @return integer the number of rows deleted
 */
    public static function deleteAll($condition)
    {

    }

    /**
 * Inserts the model into the database using the attribute values of this record.
 *
 * Usage example:
 *
 * ```php
 * $customer = new Customer;
 * $customer->name = $name;
 * $customer->email = $email;
 * $customer->insert();
 * ```
 *
 * @return boolean whether the model is inserted successfully.
 */
    public function insert()
    {

    }

    /**
 * Saves the changes to this model into the database.
 *
 * Usage example:
 *
 * ```php
 * $customer = Customer::findOne(['id' => $id]);
 * $customer->name = $name;
 * $customer->email = $email;
 * $customer->update();
 * ```
 *
 * @return integer|boolean the number of rows affected.
 * Note that it is possible that the number of rows affected is 0, even though the
 * update execution is successful.
 */
    public function update()
    {

    }

    /**
 * Deletes the model from the database.
 *
 * @return integer|boolean the number of rows deleted.
 * Note that it is possible that the number of rows deleted is 0, even though the deletion execution is successful.
 */
    public function delete()
    {

    }
}

当然现在里面还没有写任何的实现,只是继承了ModelInterface接口。

现在我们先来实现一下findOne方法,在开始实现之前我们要想,我们所有的model都要基于PDO,所以我们应该在model中有一个PDO的实例。所以我们需要在Model类中添加如下变量和方法。

    /**
 * @var $pdo PDO instance
 */
    public static $pdo;

    /**
 * Get pdo instance
 * @return PDO
 */
    public static function getDb()
    {
        if (empty(static::$pdo)) {
            $host = 'localhost';
            $database = 'sf';
            $username = 'jun';
            $password = 'jun';
            static::$pdo = new PDO("mysql:host=$host;dbname=$database", $username, $password);
            static::$pdo->exec("set names 'utf8'");
        }

        return static::$pdo;
    }

用static变量可以保证所有继承该Model的类用的都是同一个PDO实例,getDb方法实现了单例模式(其中的配置暂时hard在这里,在之后的博客里会抽出来),保证了一个请求中,使用getDb只会取到一个PDO实例。

下面我们来实现findOne方法,我们现在定义的findOne有很多局限,例如不支持or,不支持select部分字段,不支持表关联等等。我们之后会慢慢完善这一些内容。其实findOne的实现就是拼接sql语句去执行,直接来看下代码:

    public static function findOne($condition)
    {
        //  拼接默认的前半段sql语句
        $sql = 'select * from ' . static::tableName() . ' where ';
        // 取出condition中value作为参数
        $params = array_values($condition);
        $keys = [];
        foreach ($condition as $key => $value) {
            array_push($keys, "$key = ?");
        }
        // 拼接sql完成
        $sql .= implode(' and ', $keys);
        $stmt = static::getDb()->prepare($sql);
        $rs = $stmt->execute($params);

        if ($rs) {
            $row = $stmt->fetch(PDO::FETCH_ASSOC);
            if (!empty($row)) {
                // 创建相应model的实例
                $model = new static();
                foreach ($row as $rowKey => $rowValue) {
                    // 给model的属性赋值
                    $model->$rowKey = $rowValue;
                }
                return $model;
            }
        }
        // 默认返回null
        return null;
    }

我们需要来验证一下我们的代码是正确的,先在MySQL中设置一下用户及权限,mock一下数据。

相应的SQL语句如下:

/*创建新用户*/
CREATE USER jun@localhost IDENTIFIED BY 'jun';

/*用户授权 授权jun用户拥有sf数据库的所有权限*/
GRANT ALL PRIVILEGES ON sf.* TO jun@'%' IDENTIFIED BY 'jun';

/*刷新授权*/
FLUSH PRIVILEGES;

/*创建数据库*/
CREATE DATABASE IF NOT EXISTS `sf`;

/*选择数据库*/
USE `sf`;

/*创建表*/
CREATE TABLE IF NOT EXISTS `user` (
    id INT(20) NOT NULL AUTO_INCREMENT,
    name VARCHAR(50),
    age INT(11),
    PRIMARY KEY(id)
);

/*插入测试数据*/
INSERT INTO `user` (name, age) VALUES('harry', 20), ('tony', 23), ('tom', 24);

然后再在models文件夹中创建一个User.php,代码如下:

<?php
namespace app\models;

use sf\db\Model;

/**
 * User model
 * @property integer $id
 * @property string $name
 * @property integer $age
 */
class User extends Model
{
    public static function tableName()
    {
        return 'user';
    }
}

最后只剩在Controller中使用findOne验证一下了。修改SiteController.php的actionTest方法如下:

    public function actionTest()
    {
        $user = User::findOne(['age' => 20, 'name' => 'harry']);
        $data = [
            'first' => 'awesome-php-zh_CN',
            'second' => 'simple-framework',
            'user' => $user
        ];
        echo $this->toJson($data);
    }

打出的如下结果:

{"first":"awesome-php-zh_CN","second":"simple-framework","user":{"id":"1","name":"harry","age":"20"}}

如果将findOne中的条件改成['age' => 20, 'name' => 'tom'],返回值就变为如下结果:

{"first":"awesome-php-zh_CN","second":"simple-framework","user":null}
 类似资料: