構建自己的PHP框架--實現Model類(1)


在之前的博客中,我們定義了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}

好了,今天就先到這里。項目內容和博客內容也都會放到Github上,歡迎大家提建議。

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM