2017
05-25

Phalcon入门教程之模型

Phalcon 提供了四种方式操作Mysql数据库:模型、PHQL、数据库抽象层以及原生SQL。不论何种方式,首先都需要在DI中注册 db 服务才能正常使用:

DI注册db服务

//  文件路径:app/core/services.php
$di -> setShared('db', function () use($config) {
    $dbconfig = $config -> database -> db;
    $dbconfig = $dbconfig -> toArray();
    if (!is_array($dbconfig) || count($dbconfig)==0) {
        throw new \Exception("the database config is error");
    }
    $connection = new \Phalcon\Db\Adapter\Pdo\Mysql(array(
        "host" => $dbconfig['host'], "port" => $dbconfig['port'],
        "username" => $dbconfig['username'],
        "password" => $dbconfig['password'],
        "dbname" => $dbconfig['dbname'],
        "charset" => $dbconfig['charset'])
    );
    return $connection;
});

数据库连接信息配置如下:

// 文件路径:app/config/system.php
return array(
    //数据库表配置
    'database' => array(
        //数据库连接信息
        'db' => array(
            'host' => '127.0.0.1',
            'port' => 3306,
            'username' => 'admin',
            'password' => 'admin',
            'dbname' => 'test',
            'charset' => 'utf8',
        ),

        //表前缀
        'prefix' => 'test_',
    ),
);

记录底层SQL语句

在我们开发过程中,有时候需要通过SQL语句来分析定位问题。那么,我们需要将ORM生成的底层SQL记录到日志中。修改DI中注册的 db 服务如下:

//文件路径:app/core/services.php
$di -> setShared('db', function () use($config) {
    $dbconfig = $config -> database -> db;
    $dbconfig = $dbconfig -> toArray();
    if (!is_array($dbconfig) || count($dbconfig)==0) {
        throw new \Exception("the database config is error");
    }
    $eventsManager = new \Phalcon\Events\Manager();
    // 分析底层sql性能,并记录日志
    $profiler = new Phalcon\Db\Profiler();
    $eventsManager -> attach('db', function ($event, $connection) use ($profiler) {
        if($event -> getType() == 'beforeQuery'){
            //在sql发送到数据库前启动分析
            $profiler -> startProfile($connection -> getSQLStatement());
        }
        if($event -> getType() == 'afterQuery'){
            //在sql执行完毕后停止分析
            $profiler -> stopProfile();
            //获取分析结果
            $profile = $profiler -> getLastProfile();
            $sql = $profile->getSQLStatement();
            $params = $connection->getSqlVariables();
            (is_array($params) && count($params)) && $params = json_encode($params);
            $executeTime = $profile->getTotalElapsedSeconds();
            //日志记录
            $currentDay = date('Ymd');
            $logger = new \Phalcon\Logger\Adapter\File(ROOT_PATH . "/app/cache/logs/{$currentDay}.log");
            $logger -> debug("{$sql} {$params} {$executeTime}");
        }
    });

    $connection = new \Phalcon\Db\Adapter\Pdo\Mysql(array(
        "host" => $dbconfig['host'], "port" => $dbconfig['port'],
        "username" => $dbconfig['username'],
        "password" => $dbconfig['password'],
        "dbname" => $dbconfig['dbname'],
        "charset" => $dbconfig['charset'])
    );

    /* 注册监听事件 */
    $connection->setEventsManager($eventsManager);

    return $connection;
});

通过代码可以看到,不仅是底层SQL,还将SQL绑定的参数(PDO预处理)和SQL执行时间也记录到日志中了。日志记录demo如下:

SELECT `users`.`uid` AS `uid`, `users`.`mobile` AS `mobile` FROM `users` WHERE `users`.`uid` = :uid LIMIT :APL0 {"uid":1,"APL0":1} 0.034402132034302

花括号({})中的就是SQL预处理时绑定的参数,最后的浮点数就是SQL执行时间(单位为秒)。

创建模型

模型类的命名必须符合驼峰命名法,而且须继承自 Phalcon\Mvc\Model 类:

// 文件路径:app/frontend/models/ArticlesModel.php
class Articles extends \Marser\App\Frontend\Models\BaseModel {
    // \Marser\App\Frontend\Models\BaseModel继承自 \Phalcon\Mvc\Model 类。
    // 此处是再次封装一个基础模型类,  以方便后续的通用方法封装
    //...
}

数据库表映射

默认情况下,Articles 模型类对应的数据表名是 articles ;若是 ArticlesTags 模型类,则对应的数据库表名是 articles_tags , 即类名对应着表名。如果想映射到其他数据库表,可以使用 setSource() 方法设置:

// 文件路径:app/frontend/models/ArticlesModel.php
class Articles extends \Marser\App\Frontend\Models\BaseModel {
  public function initialize()
    {
        $this->setSource("articles_tags");
    }
}

在项目开发中,建议一个数据表对应着一个模型类。即使是关联表,也强烈建议创建其对应的模型类,因为 Phalcon 中提供的连表操作,都是基于模型类的(后续的教程会分享)。

设置表前缀

在进行数据库表设计的时候,有时会在表名前加上一段前缀,如 test_articles 。我们依然可以通过 setSource() 映射数据表:

// 文件路径:app/frontend/models/ArticlesModel.php
class Articles extends \Marser\App\Frontend\Models\BaseModel {
  public function initialize()
    {
        $this->setSource("test_articles");
    }
}

假设,我们的项目中有100张数据表,那么就意味着有100个模型类。此时我们在每个模型类中都必须调用 setSource() 来映射完整的表名。如果某天我们需要修改这100张表的前缀,那么将要修改这100个模型类,不仅耗时耗力还麻烦。我们尝试着将此处理过程提取出来进行封装:

// 文件路径: app/frontend/models/ArticlesModel.php
class ArticlesModel extends \Marser\App\Frontend\Models\BaseModel {

    /**
     * 表名
     */
    const TABLE_NAME = 'articles';

    public function initialize(){
        parent::initialize();
        //映射数据表(补上表前缀)
        $this->set_table_source(self::TABLE_NAME);
    }
}

BaseModel 模型基类中的 set_table_source() 方法定义如下:

// 文件路径: app/frontend/models/BaseModel.php
class BaseModel extends \Phalcon\Mvc\Model {

    public function initialize(){

    }

    /**
     * 映射数据表(补上表前缀)
     * @param string $tableName
     * @param null $prefix
     */
    protected function set_table_source($tableName, $prefix = null){
        //默认从配置中读取表前缀配置
        empty($prefix) && $prefix = $this->getDI()->get('config')->database->prefix;
        //拼接成完整表名之后,再通过setSource()映射数据表
        $this->setSource($prefix . $tableName);
    }
}

我们在每个模型类中定义一个 类常量 来存储无前缀的表名,再通过 set_table_source() 成员方法来拼接表前缀并映射。眼尖的读者,应该在上面的数据库连接信息配置中有看到 prefix 的表前缀配置。
还是以上面为例,此时我们就不需要修改100个模型类的代码,而只需修改配置文件中的 prefix 配置即可。

以上代码已托管在github:https://github.com/KevinJay/marser-phalcon-demo

最后,欢迎大家加入QQ群交流讨论:

  • 广州PHP高端交流群:158587573
  • Phalcon玩家群:150237524
「真诚赞赏,手留余香」
您的支持将鼓励我继续创作 :)