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

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

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

<?php
namespace sfdb;

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 appmodels;

use sfdbModel;

/**
 * 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}

好了,今天就先到这里。项目内容和博客内容也都会放到Github上,欢迎大家提建议。

code:https://github.com/CraryPrimitiveMan/simple-framework/tree/0.5

blog project:https://github.com/CraryPrimitiveMan/create-your-own-php-framework

原文地址:https://www.cnblogs.com/CraryPrimitiveMan/p/5225686.html