标签归档:模型

PHP框架Phalcon 之 模型 模型元数据

Models Meta-Data 模型元数据
To speed up development Phalcon\Mvc\Model helps you to query fields and constraints from tables related to models. To achieve this, Phalcon\Mvc\Model\MetaData is available to manage and cache table meta-data.
为了加快开发Phalcon\Mvc\Model帮助你从表格关联到模型时查询字段和约束。为了达到这个目标,可用Phalcon\Mvc\Model\MetaData管理和缓存表元数据。

Sometimes it is necessary to get those attributes when working with models. You can get a meta-data instance as follows:
有时当使用模型时有必要去获取那些属性:

<?php

$robot = new Robots();

// Get Phalcon\Mvc\Model\Metadata instance
$metaData = $robot->getModelsMetaData();

// Get robots fields names
$attributes = $metaData->getAttributes($robot);
print_r($attributes);

// Get robots fields data types
$dataTypes = $metaData->getDataTypes($robot);
print_r($dataTypes);

–Caching Meta-Data 缓存元数据
Once the application is in a production stage, it is not necessary to query the meta-data of the table from the database system each time you use the table. This could be done caching the meta-data using any of the following adapters:
缓存元数据到如下介质:

Adapter Description API
Memory This adapter is the default. The meta-data is cached only during the request. When the request is completed, the meta-data are released as part of the normal memory of the request. This adapter is perfect when the application is in development so as to refresh the meta-data in each request containing the new and/or modified fields. Phalcon\Mvc\Model\MetaData\Memory
Session This adapter stores meta-data in the $_SESSION superglobal. This adapter is recommended only when the application is actually using a small number of models. The meta-data are refreshed every time a new session starts. This also requires the use of session_start() to start the session before using any models. Phalcon\Mvc\Model\MetaData\Session
Apc This adapter uses the Alternative PHP Cache (APC) to store the table meta-data. You can specify the lifetime of the meta-data with options. This is the most recommended way to store meta-data when the application is in production stage. Phalcon\Mvc\Model\MetaData\Apc
XCache This adapter uses XCache to store the table meta-data. You can specify the lifetime of the meta-data with options. This is the most recommended way to store meta-data when the application is in production stage. Phalcon\Mvc\Model\MetaData\Xcache
Files This adapter uses plain files to store meta-data. By using this adapter the disk-reading is increased but the database access is reduced Phalcon\Mvc\Model\MetaData\Files

As other ORM’s dependencies, the metadata manager is requested from the services container:

<?php

$di['modelsMetadata'] = function() {

    // Create a meta-data manager with APC
    $metaData = new \Phalcon\Mvc\Model\MetaData\Apc(array(
        "lifetime" => 86400,
        "prefix"   => "my-prefix"
    ));

    return $metaData;
};

——————————
以上内容并没有把这个问题说透彻。modelsMetadata默认不是Phalcon\DI\FactoryDefault会初始化的内容,所以要缓存元数据,就要手动添加如上代码到DI。

先举例:

$u = new Users();
$md = $u->getModelsMetaData();
print_r($md->getAttributes($u));

//输出
Array ( [0] => id [1] => name [2] => email )

//监控到产生了如下SQL
[Tue, 02 Sep 14 02:01:30 +0800][INFO] SELECT IF(COUNT(*)>0, 1 , 0) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_NAME`='users'
[Tue, 02 Sep 14 02:01:30 +0800][INFO] DESCRIBE `users` 

可见,元数据的获取实际是发送DESCRIBE指令,它可以获取表结构,这些所有元数据,基本跟表结构等同。如果设置了缓存元数据,比如插入:

    $di->set('modelsMetadata',function(){
        $metaData = new \Phalcon\Mvc\Model\Metadata\Files(array(
                'metaDataDir' => __DIR__.'/../app/cache/metadata/'
        ));
        return $metaData;
    }); 

代码运行,缓存文件产生:

pwd
/******/app/cache/metadata
ls
map-users.php  meta-users-users.php

第二次运行代码,没有发现再次产生SQL。运行一个查询看看有什么不同:

$u = new Users();
$md = $u->getModelsMetaData();
$us = Users::find(1);

//SQL输出
[Tue, 02 Sep 14 02:16:42 +0800][INFO] SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users` WHERE `users`.`id` = 1

可以看到查询前先确认表是否存在的查询 和 获取表结构的DESCRIBE查询没有产生,因为这些已经被本地缓存了。这个就说明要获取元数据是先到modelsMetadata缓存找,不存储再正常查询然后写入缓存,这个过程自动完成,只要在DI设置了modelsMetadata。
——————————
–Meta-Data Strategies 元数据策略
As mentioned above the default strategy to obtain the model’s meta-data is database introspection反思. In this strategy, the information schema is used to know the fields in a table, its primary key, nullable fields, data types, etc.

You can change the default meta-data introspection in the following way:

<?php

$di['modelsMetadata'] = function() {

    // Instantiate a meta-data adapter
    $metaData = new \Phalcon\Mvc\Model\MetaData\Apc(array(
        "lifetime" => 86400,
        "prefix"   => "my-prefix"
    ));

    //Set a custom meta-data introspection strategy
    $metaData->setStrategy(new MyInstrospectionStrategy());

    return $metaData;
};

(修改元数据策略)
–Database Introspection Strategy
This strategy doesn’t require any customization and is implicitly used by all the meta-data adapters.
默认工作在这个策略下。
–Annotations Strategy 注释策略(很新鲜)
This strategy makes use of annotations to describe the columns in a model:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    /**
     * @Primary
     * @Identity
     * @Column(type="integer", nullable=false)
     */
    public $id;

    /**
     * @Column(type="string", length=70, nullable=false)
     */
    public $name;

    /**
     * @Column(type="string", length=32, nullable=false)
     */
    public $type;

    /**
     * @Column(type="integer", nullable=false)
     */
    public $year;

}

(字段的注释指明了类型 长度 是否可空,这些信息也可以从数据库获取)
Annotations must be placed in properties that are mapped to columns in the mapped source. Properties without the @Column annotation are handled as simple class attributes.
要用@Column开头,否则就是简单类属性。(看起来是利用反射来实现的)

The following annotations are supported:

Name Description
Primary Mark the field as part of the table’s primary key
Identity The field is an auto_increment/serial column
Column This marks an attribute as a mapped column

The annotation @Column supports the following parameters:

Name Description
type The column’s type (string, integer, decimal, boolean)
length The column’s length if any
nullable Set whether the column accepts null values or not

The annotations strategy could be set up this way:
只要如下设置一下,就可以修改元数据策略了

<?php

use Phalcon\Mvc\Model\MetaData\Apc as ApcMetaData,
    Phalcon\Mvc\Model\MetaData\Strategy\Annotations as StrategyAnnotations;

$di['modelsMetadata'] = function() {

    // Instantiate a meta-data adapter
    $metaData = new ApcMetaData(array(
        "lifetime" => 86400,
        "prefix"   => "my-prefix"
    ));

    //Set a custom meta-data database introspection
    $metaData->setStrategy(new StrategyAnnotations());

    return $metaData;
};

–Manual Meta-Data
Phalcon can obtain the metadata for each model automatically without the developer must set them manually using any of the introspection strategies presented above.

The developer also has the option of define the metadata manually. This strategy overrides any strategy set in the meta-data manager. New columns added/modified/removed to/from the mapped table must be added/modified/removed also for everything to work properly.

The following example shows how to define the meta-data manually:

?php

use Phalcon\Mvc\Model,
    Phalcon\Db\Column,
    Phalcon\Mvc\Model\MetaData;

class Robots extends Model
{

    public function metaData()
    {
        return array(

            //Every column in the mapped table
            MetaData::MODELS_ATTRIBUTES => array(
                'id', 'name', 'type', 'year'
            ),

            //Every column part of the primary key
            MetaData::MODELS_PRIMARY_KEY => array(
                'id'
            ),

            //Every column that isn't part of the primary key
            MetaData::MODELS_NON_PRIMARY_KEY => array(
                'name', 'type', 'year'
            ),

            //Every column that doesn't allows null values
            MetaData::MODELS_NOT_NULL => array(
                'id', 'name', 'type', 'year'
            ),

            //Every column and their data types
            MetaData::MODELS_DATA_TYPES => array(
                'id' => Column::TYPE_INTEGER,
                'name' => Column::TYPE_VARCHAR,
                'type' => Column::TYPE_VARCHAR,
                'year' => Column::TYPE_INTEGER
            ),

            //The columns that have numeric data types
            MetaData::MODELS_DATA_TYPES_NUMERIC => array(
                'id' => true,
                'year' => true,
            ),

            //The identity column, use boolean false if the model doesn't have
            //an identity column
            MetaData::MODELS_IDENTITY_COLUMN => 'id',

            //How every column must be bound/casted
            MetaData::MODELS_DATA_TYPES_BIND => array(
                'id' => Column::BIND_PARAM_INT,
                'name' => Column::BIND_PARAM_STR,
                'type' => Column::BIND_PARAM_STR,
                'year' => Column::BIND_PARAM_INT,
            ),

            //Fields that must be ignored from INSERT SQL statements
            MetaData::MODELS_AUTOMATIC_DEFAULT_INSERT => array(
                'year' => true
            ),

            //Fields that must be ignored from UPDATE SQL statements
            MetaData::MODELS_AUTOMATIC_DEFAULT_UPDATE => array(
                'year' => true
            )

        );
    }

}

PHP框架Phalcon 之 模型 事务处理

Transactions 事务
When a process performs multiple database operations, it is often that each step is completed successfully so that data integrity can be maintained. Transactions offer the ability to ensure that all database operations have been executed successfully before the data are committed to the database.

Transactions in Phalcon allow you to commit all operations if they have been executed successfully or rollback all operations if something went wrong.

–Manual Transactions 手动事务
If an application only uses one connection and the transactions aren’t very complex, a transaction can be created by just moving the current connection to transaction mode, doing a rollback or commit if the operation is successfully or not:
如果应用只使用一个链接,事务不会非常复杂,通过移动当前链接的事务模式就可以创建一个事务,接下来就是提交或回滚:

<?php

class RobotsController extends Phalcon\Mvc\Controller
{
    public function saveAction()
    {
        $this->db->begin();

        $robot = new Robots();

        $robot->name = "WALL·E";
        $robot->created_at = date("Y-m-d");
        if ($robot->save() == false) {
            $this->db->rollback();
            return;
        }

        $robotPart = new RobotParts();
        $robotPart->robots_id = $robot->id;
        $robotPart->type = "head";
        if ($robotPart->save() == false) {
            $this->db->rollback();
            return;
        }

        $this->db->commit();
    }
}

–Implicit Transactions 隐式事务
Existing relationships can be used to store records and their related instances, this kind of operation implicitly creates a transaction to ensure that data are correctly stored:

<?php

$robotPart = new RobotParts();
$robotPart->type = "head";

$robot = new Robots();
$robot->name = "WALL·E";
$robot->created_at = date("Y-m-d");
$robot->robotPart = $robotPart;

$robot->save(); //Creates an implicit transaction to store both records

(涉及到多张表,默认悄悄开启事务)

–Isolated Transactions 原子事务
Isolated transactions are executed in a new connection ensuring that all the generated SQL, virtual foreign key checks and business rules are isolated from the main connection. This kind of transaction requires a transaction manager that globally manages each transaction created ensuring that they are correctly rolled back/committed before ending the request:
原子事务在一个新链接中执行用来确保所有产生的SQL,虚拟外键检测和业务逻辑是原子性的。这种类型的事务要求一个事务管理器来全局管理每个事务的创建以确保它们在请求结束(这个请求是只请求一个事务吧?)前正确回滚和提交:

<?php

use Phalcon\Mvc\Model\Transaction\Manager as TxManager,
    Phalcon\Mvc\Model\Transaction\Failed as TxFailed;

try {

    //Create a transaction manager
    $manager = new TxManager();

    // Request a transaction
    $transaction = $manager->get();

    $robot = new Robots();
    $robot->setTransaction($transaction);
    $robot->name = "WALL·E";
    $robot->created_at = date("Y-m-d");
    if ($robot->save() == false) {
        $transaction->rollback("Cannot save robot");
    }

    $robotPart = new RobotParts();
    $robotPart->setTransaction($transaction);
    $robotPart->robots_id = $robot->id;
    $robotPart->type = "head";
    if ($robotPart->save() == false) {
        $transaction->rollback("Cannot save robot part");
    }

    //Everything goes fine, let's commit the transaction
    $transaction->commit();

} catch(TxFailed $e) {
    echo "Failed, reason: ", $e->getMessage();
}

(这里引入了事务管理器,开启事务通过调用事务管理器的get方法,按照之前的说明,它是构建一个新链接,然后在它之上操作)

Transactions can be used to delete many records in a consistent way:

<?php

use Phalcon\Mvc\Model\Transaction\Manager as TxManager,
    Phalcon\Mvc\Model\Transaction\Failed as TxFailed;

try {

    //Create a transaction manager
    $manager = new TxManager();

    //Request a transaction
    $transaction = $manager->get();

    //Get the robots will be deleted
    foreach (Robots::find("type = 'mechanical'") as $robot) {
        $robot->setTransaction($transaction);
        if ($robot->delete() == false) {
            //Something goes wrong, we should to rollback the transaction
            foreach ($robot->getMessages() as $message) {
                $transaction->rollback($message->getMessage());
            }
        }
    }

    //Everything goes fine, let's commit the transaction
    $transaction->commit();

    echo "Robots were deleted successfully!";

} catch(TxFailed $e) {
    echo "Failed, reason: ", $e->getMessage();
}

Transactions are reused no matter where the transaction object is retrieved. A new transaction is generated only when a commit() or rollback() is performed. You can use the service container to create the global transaction manager for the entire application:
事务是重用的。一个新的事务仅在commit() or rollback()被执行时产生。你可以使用服务容器创建一个全局事务管理器。

<?php

$di->setShared('transactions', function(){
    return new \Phalcon\Mvc\Model\Transaction\Manager();
});

Then access it from a controller or view:

<?php

class ProductsController extends \Phalcon\Mvc\Controller
{

    public function saveAction()
    {

        //Obtain the TransactionsManager from the services container
        $manager = $this->di->getTransactions();

        //Or
        $manager = $this->transactions;

        //Request a transaction
        $transaction = $manager->get();

        //...
    }

}

While a transaction is active, the transaction manager will always return the same transaction across the application.
但是一个事务是激活的,事务管理在整个应用中将总是返回相同事务。
(Phalcon\DI\FactoryDefault是Phalcon MVC默认实现的DI,它会初始化一个事务管理器,因为可能产生多个事务,所以才需要管理)

PHP框架Phalcon 之 模型 关系定义

Relationships between Models 模型关系
There are four types of relationships: one-on-one, one-to-many, many-to-one and many-to-many. The relationship may be unidirectional or bidirectional, and each can be simple (a one to one model) or more complex (a combination of models). The model manager manages foreign key constraints for these relationships, the definition of these helps referential integrity as well as easy and fast access of related records to a model. Through the implementation of relations, it is easy to access data in related models from each record in a uniform way.
有四种关系类型:one-on-one, one-to-many, many-to-one and many-to-many。这些关系可以是单向或双向,可以简单或复杂。模型管理器为这些关系管理外键约束,这些定义 引用完整性 更简单和快速访问相关记录到模型。通过这些关系的实现,可以使用统一的方式在相关模型中容易地获取来自每条记录的数据。

Unidirectional relationships 单向关系
Unidirectional relations are those that are generated in relation to one another but not vice versa反过来不行.

Bidirectional relations 双向关系
The bidirectional relations build relationships in both models and each model defines the inverse relationship of the other.

Defining relationships 定义关系
In Phalcon, relationships must be defined in the initialize() method of a model. 必须在模型的 initialize()方法中定义关系。The methods belongsTo(), hasOne(), hasMany() and hasManyToMany() define the relationship between one or more fields from the current model to fields in another model. Each of these methods requires 3 parameters: local fields, referenced model, referenced fields.(三个参数,本地字段,引用模型,引用字段)

Method Description
hasMany Defines a 1-n relationship
hasOne Defines a 1-1 relationship
belongsTo Defines a n-1 relationship
hasManyToMany Defines a n-n relationship

(如果某个模型被其它模型引用,就需要定义hasMany,就是所谓的一对多,同理,如果模型引用到另外模型,那么它就需要定义belongsTo,如果通过一个中间表引用到另外的模型,就需要定义hasManyToMany)

The following schema shows 3 tables whose relations will serve us as an example regarding relationships:

CREATE TABLE `robots` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(70) NOT NULL,
    `type` varchar(32) NOT NULL,
    `year` int(11) NOT NULL,
    PRIMARY KEY (`id`)
);

CREATE TABLE `robots_parts` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `robots_id` int(10) NOT NULL,
    `parts_id` int(10) NOT NULL,
    `created_at` DATE NOT NULL,
    PRIMARY KEY (`id`),
    KEY `robots_id` (`robots_id`),
    KEY `parts_id` (`parts_id`)
);

CREATE TABLE `parts` (
    `id` int(10) unsigned NOT NULL AUTO_INCREMENT,
    `name` varchar(70) NOT NULL,
    PRIMARY KEY (`id`)
);

这三张表就是数据库里面常见的多对多关系,在数据库中只要维护中间表的引用即可。不过在这里就要复杂一些。
The model “Robots” has many “RobotsParts”.
The model “Parts” has many “RobotsParts”.
The model “RobotsParts” belongs to both “Robots” and “Parts” models as a many-to-one relation.
The model “Robots” has a relation many-to-many to “Parts” through “RobotsParts”
(被引用的是老子,它有多个仔,老子自然就是有多个仔,仔自然就属于老子,但是多到多就不好理解了,实际它是指要用中间表建立多对多关系,这些模型管理器才知道这些关系)

The models with their relations could be implemented as follows:

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "robots_id");
    }

}
<?php

class Parts extends \Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "parts_id");
    }

}
<?php

class RobotsParts extends \Phalcon\Mvc\Model
{

    public $id;

    public $robots_id;

    public $parts_id;

    public function initialize()
    {
        $this->belongsTo("robots_id", "Robots", "id");
        $this->belongsTo("parts_id", "Parts", "id");
    }

}

The first parameter indicates the field of the local model used in the relationship; the second indicates the name of the referenced model and the third the field name in the referenced model. You could also use arrays to define multiple fields in the relationship.
Many to many relationships require 3 models and define the attributes involved in the relationship:

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public function initialize()
    {
        $this->hasManyToMany(
            "id",
            "RobotsParts",
            "robots_id", "parts_id",
            "Parts",
            "id"
        );
    }

}
//如果在Parts中定义这个多到多,看起来应该是可以的
<?php

class Parts extends \Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public function initialize()
    {
	$this->hasManyToMany(
            "id",
            "RobotsParts",
            "parts_id", "robots_id ",
            "Robots",
            "id"
        );
	}
}

Taking advantage of relationships
When explicitly defining the relationships between models, it is easy to find related records for a particular record.

<?php

$robot = Robots::findFirst(30);
foreach ($robot->robotsParts as $robotPart) {
    echo $robotPart->parts->name, "\n";
}

优点先不说了,先看看产生的SQL吧

Wed, 03 Sep 14 09:06:05 +0800][INFO] SELECT `robots`.`id`, `robots`.`name`, `robots`.`type`, `robots`.`year` FROM `robots` WHERE `robots`.`id` = 30 LIMIT 1
[Wed, 03 Sep 14 09:06:05 +0800][INFO] SELECT `robots_parts`.`id`, `robots_parts`.`robots_id`, `robots_parts`.`parts_id`, `robots_parts`.`created_at` FROM `robots_parts` WHERE `robots_parts`.`robots_id` = :0
[Wed, 03 Sep 14 09:06:05 +0800][INFO] SELECT `parts`.`id`, `parts`.`name` FROM `parts` WHERE `parts`.`id` = :0 LIMIT 1
[Wed, 03 Sep 14 09:06:05 +0800][INFO] SELECT `parts`.`id`, `parts`.`name` FROM `parts` WHERE `parts`.`id` = :0 LIMIT 1
[Wed, 03 Sep 14 09:06:05 +0800][INFO] SELECT `parts`.`id`, `parts`.`name` FROM `parts` WHERE `parts`.`id` = :0 LIMIT 1

预计会产生联合查询,实际结果让人失望。代码是方便了,实际效率很差。从robot获取robot_part,在从robot_part获取part。

Phalcon uses the magic methods __set/__get/__call to store or retrieve related data using relationships.
By accessing an attribute with the same name as the relationship will retrieve all its related record(s).
Also, you can use a magic getter:

<?php

$robot = Robots::findFirst();
$robotsParts = $robot->getRobotsParts(); // all the related records in RobotsParts
$robotsParts = $robot->getRobotsParts(array('limit' => 5)); // passing parameters

If the called method has a “get” prefix Phalcon\Mvc\Model will return a findFirst()/find() result. The following example compares retrieving related results with using magic methods and without:

<?php

$robot = Robots::findFirst(2);

// Robots model has a 1-n (hasMany)
// relationship to RobotsParts then
$robotsParts = $robot->robotsParts;

// Only parts that match conditions
$robotsParts = $robot->getRobotsParts("created_at = '2012-03-15'");

// Or using bound parameters
$robotsParts = $robot->getRobotsParts(array(
    "created_at = :date:",
    "bind" => array("date" => "2012-03-15")
));

$robotPart = RobotsParts::findFirst(1);

// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = $robotPart->robots;

##########
##Getting related records manually:
<?php

$robot = Robots::findFirst(2);

// Robots model has a 1-n (hasMany)
// relationship to RobotsParts, then
$robotsParts = RobotsParts::find("robots_id = '" . $robot->id . "'");

// Only parts that match conditions
$robotsParts = RobotsParts::find(
    "robots_id = '" . $robot->id . "' AND created_at = '2012-03-15'"
);

$robotPart = RobotsParts::findFirst(1);

// RobotsParts model has a n-1 (belongsTo)
// relationship to RobotsParts then
$robot = Robots::findFirst("id = '" . $robotPart->robots_id . "'");

The prefix “get” is used to find()/findFirst() related records. Depending on the type of relation it will use ‘find’ or ‘findFirst’:

Type Description Implicit Method
Belongs-To Returns a model instance of the related record directly findFirst
Has-One Returns a model instance of the related record directly findFirst
Has-Many Returns a collection of model instances of the referenced model find
Has-Many-to-Many Returns a collection of model instances of the referenced model, it implicitly does ‘inner joins’ with the involved models (complex query)

You can also use “count” prefix to return an integer denoting the count of the related records:

<?php

$robot = Robots::findFirst(2);
echo "The robot has ", $robot->countRobotsParts(), " parts\n";

Aliasing Relationships 关系别名
To explain better how aliases work, let’s check the following example:
Table “robots_similar” has the function to define what robots are similar to others:

mysql> desc robots_similar;
+-------------------+------------------+------+-----+---------+----------------+
| Field             | Type             | Null | Key | Default | Extra          |
+-------------------+------------------+------+-----+---------+----------------+
| id                | int(10) unsigned | NO   | PRI | NULL    | auto_increment |
| robots_id         | int(10) unsigned | NO   | MUL | NULL    |                |
| similar_robots_id | int(10) unsigned | NO   |     | NULL    |                |
+-------------------+------------------+------+-----+---------+----------------+
3 rows in set (0.00 sec)

A model that maps this table and its relationships is the following:

<?php

class RobotsSimilar extends Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->belongsTo('robots_id', 'Robots', 'id');
        $this->belongsTo('similar_robots_id', 'Robots', 'id');
    }

}

Since both relations point to the same model (Robots), obtain the records related to the relationship could not be clear:

<?php

$robotsSimilar = RobotsSimilar::findFirst();

//Returns the related record based on the column (robots_id)
//Also as is a belongsTo it's only returning one record
//but the name 'getRobots' seems to imply that return more than one
$robot = $robotsSimilar->getRobots();

//but, how to get the related record based on the column (similar_robots_id)
//if both relationships have the same name?

The aliases allow us to rename both relationships to solve these problems:

<?php

class RobotsSimilar extends Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->belongsTo('robots_id', 'Robots', 'id', array(
            'alias' => 'Robot'
        ));
        $this->belongsTo('similar_robots_id', 'Robots', 'id', array(
            'alias' => 'SimilarRobot'
        ));
    }

}

With the aliasing we can get the related records easily:

<?php

$robotsSimilar = RobotsSimilar::findFirst();

//Returns the related record based on the column (robots_id)
$robot = $robotsSimilar->getRobot();
$robot = $robotsSimilar->robot;

//Returns the related record based on the column (similar_robots_id)
$similarRobot = $robotsSimilar->getSimilarRobot();
$similarRobot = $robotsSimilar->similarRobot;

Magic Getters vs. Explicit methods
Most IDEs and editors with auto-completion capabilities can not infer提示 the correct types when using magic getters, instead of use the magic getters you can optionally define those methods explicitly明确的 with the corresponding相应的 docblocks(DOC块) helping the IDE to produce a better auto-completion:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "robots_id");
    }

    /**
     * Return the related "robots parts"
     *
     * @return \RobotsParts[]
     */
    public function getRobotsParts($parameters=null)
    {
        return $this->getRelated('RobotsParts', $parameters);
    }

}

Virtual Foreign Keys
By default, relationships do not act like database foreign keys, that is, if you try to insert/update a value without having a valid value in the referenced model, Phalcon will not produce a validation message. You can modify this behavior by adding a fourth parameter when defining a relationship.
默认定义的关系没有扮演类似数据库中的外键,如果插入或更新一个在引用模型中没有验证的值,Phalcon不会产生验证消息。你可以在定义关系时添加第四个参数修改这个行为。
The RobotsPart model can be changed to demonstrate this feature:

<?php

class RobotsParts extends \Phalcon\Mvc\Model
{

    public $id;

    public $robots_id;

    public $parts_id;

    public function initialize()
    {
        $this->belongsTo("robots_id", "Robots", "id", array(
            "foreignKey" => true
        ));

        $this->belongsTo("parts_id", "Parts", "id", array(
            "foreignKey" => array(
                "message" => "The part_id does not exist on the Parts model"
            )
        ));
    }

}

If you alter a belongsTo() relationship to act as foreign key, it will validate that the values inserted/updated on those fields have a valid value on the referenced model. 如果在belongsTo()中定义了外键,那么在插入或更新值时将在引用模型中验证值。 Similarly, if a hasMany()/hasOne() is altered it will validate that the records cannot be deleted if that record is used on a referenced model. 类似,如果hasMany()/hasOne()定义了外键,记录被删除时将检查记录是否被引用。
(belongsTo()/hasMany()/hasOne()之间是逻辑上有外键约束的,但是hasManyToMany()两个实体间被没有这个逻辑关系)

<?php

class Parts extends \Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->hasMany("id", "RobotsParts", "parts_id", array(
            "foreignKey" => array(
                "message" => "The part cannot be deleted because other robots are using it"
            )
        ));
    }

}

(这个被引用的表设置关联外键之后(严格来说对它不是外键约束,是外键关联),当记录被删除时将被阻止)
————————————————————————
为了更好理解这个阻止操作,使用如下代码测试:

class PushController extends \Phalcon\Mvc\Controller{
public function deletePartAction(){
$part = Parts::findFirst(7);
       	$ros = '';
      	foreach($part->robots as $robot){
        	$ros .= $robot->id."($robot->name) ";
      	}       
     	echo "The robots --> ".$ros." has the same part($part->id $part->name)\n\n";

       	if($part->delete() == false){ 
       		foreach($part->getMessages() as $m){ 
          		echo "Message: ", $m->getMessage(), "\n";
               	echo "Field:   ", $m->getField(), "\n";
             	echo "Type:    ", $m->getType(), "\n";
           	}       
      	}       
    	$this->view->disable()
	}
}

#########
##输出
The robots --> 31(new robot) 36(new robot)  has the same part(7 part_6)

Message: The part cannot be deleted because other robots are using it
Field:   id
Type:    ConstraintViolation

首先使用part_6的机器人有31 和 36,当删除part_6时,这个操作被阻止了,因为它被引用。下面看看产生的SQL:

[Wed, 03 Sep 14 14:15:40 +0800][INFO] SELECT `parts`.`id`, `parts`.`name` FROM `parts` WHERE `parts`.`id` = 7 LIMIT 1
[Wed, 03 Sep 14 14:15:40 +0800][INFO] SELECT `robots`.`id`, `robots`.`similar_robots_id`, `robots`.`name`, `robots`.`type`, `robots`.`year` FROM `robots` INNER JOIN `robots_parts` ON `robots_parts`.`robots_id` = `robots`.`id`  WHERE `robots_parts`.`parts_id` = :0
[Wed, 03 Sep 14 14:15:40 +0800][INFO] SELECT COUNT(*) AS `rowcount` FROM `robots_parts` WHERE `robots_parts`.`parts_id` = :0

先定位具体的part,然后通过part找出使用了它的robot(inner join操作),在删除part前到表robots_parts中检查对应的part是否被引用,如果被引用删除就被阻止了。

从这里可以看到,所谓的外键,是程序维护的,不是数据库意义上的外键,所有这里叫虚拟外键。所谓外键引用完整性就是这里的避免被引用的part被删除。

逻辑上,part属于robot一部分,part被引用时它不应该被删除,robot有多个part组成,robot删除时,它的引用信息也应该被删除(不是删除part哦)。
————————————————————————
Cascade/Restrict actions
Relationships that act as virtual foreign keys by default restrict the creation/update/deletion of records to maintain the integrity of data:

<?php

namespace Store\Models;

use Phalcon\Mvc\Model,
    Phalcon\Mvc\Model\Relation;

class Robots extends Model
{

    public $id;

    public $name;

    public function initialize()
    {
        $this->hasMany('id', 'Store\\Models\Parts', 'robots_id', array(
            'foreignKey' => array(
                'action' => Relation::ACTION_CASCADE
            )
        ));
    }

}

The above code set up to delete all the referenced records (parts) if the master record (robot) is deleted.
同样的,这个级联删除也是程序维护的。实际上,外键约束 和 级联删除这些,现代数据库系统都支持这些特征,可以把这些交给数据库系统来做,如果程序来维护开销是很大的,比如插入一条记录,要验证引用ID的有效性,需要针对被引用表来发起一次查询。

————————————————————————
注意到以上代码通过part找到使用了这个part的robots,实际过程产生了一条inner join查询,如果Parts只设置了hasMany()和RobotsParts产生对应,但是它是如果直接可以访问robots($part->robots)的呢?实际是不可以的,需要添加它跟robots的多对多关系才可以:

        $this->hasManyToMany(
            "id",
            "RobotsParts",
            "parts_id", "robots_id",
            "Robots",
            "id"
        );

这个就高速模型管理器,Parts和Robots的多对多关系通过RobotsParts实现,同理,要在Robot中获取Part,也需要在Robots中定义多对多关系,它们是产生JOIN的依据。

另外,我们在控制器代码中通过$part->robots获取使用这个part的robots,这个逻辑应该写入到自己的模型中:

    public function getRobots ($parameters=null)
    {
        return $this->getRelated('Robots', $parameters);
    }

这里的内容,就是Phalcon提供的ORM方案的核心部分。实际上,数据库查询操作还有更加复杂的内容,这些大概就是ORM无法封装的了,这个时候就要使用原生SQL了。

最后,这里提到了一对一的关系,但是没有具体内容。查看hasOne()方法有如下例子:

<?php

class Robots extends \Phalcon\Mvc\Model
{

   public function initialize()
   {
       $this->hasOne('id', 'RobotsDescription', 'robots_id');
   }

}

这里的意思就是Robots有一个对应的RobotsDescription。正如文档开头描述那样,hasOne应该是一个双向关系,hasManyToMany也是双向关系,而hasMany和belongsTo是单向的,所以在RobotsDescription中也应该定义hasOne。

PHP框架Phalcon 之 模型 基本使用

这部分是Phalcon文档中版面最多的章节(约占八分之一),描述了模型的方方面面,向我们展示了一个完整的ORM方案。它是框架中最主要和内容最多也是最复杂的部分。

为了更好理解内容,我这里把官方文档拆分为几块,便于阅读和笔记,另外,正确理解依赖注入 和 事件管理器,是理解Phalcon的基础,所以建议先阅读:
PHP框架Phalcon 之 依赖注入
PHP框架Phalcon 之 事件管理器

关于模型使用拆分如下做阅读和笔记:
PHP框架Phalcon 之 模型 基本使用
PHP框架Phalcon 之 模型 关系定义
PHP框架Phalcon 之 模型 事务处理
PHP框架Phalcon 之 模型 模型元数据


Working with Models

A model represents the information (data) of the application and the rules to manipulate that data. Models are primarily used for managing the rules of interaction with a corresponding database table. In most cases, each table in your database will correspond to one model in your application. The bulk of your application’s business logic will be concentrated in the models.
大部分情况,一个模型和一个数据表相关。

Phalcon\Mvc\Model is the base for all models in a Phalcon application. It provides database independence, basic CRUD functionality, advanced finding capabilities, and the ability to relate models to one another, among other services. Phalcon\Mvc\Model avoids the need of having to use SQL statements because it translates methods dynamically to the respective database engine operations.
Phalcon\Mvc\Model是Phalcon应用所有模型的基准。

Models are intended to work on a database high layer of abstraction. If you need to work with databases at a lower level check out the Phalcon\Db component documentation.

Creating Models 创建模型

A model is a class that extends from Phalcon\Mvc\Model. It must be placed in the models directory. A model file must contain a single class; its class name should be in camel case notation:
模型是一个从Phalcon\Mvc\Model继承的类。它必须放到模型目录中。模型文件必须包含单个类;类名必须使用camel方式:

<?php
class Robots extends \Phalcon\Mvc\Model
{
}

The above example shows the implementation of the “Robots” model. Note that the class Robots inherits from Phalcon\Mvc\Model. This component provides a great deal of functionality to models that inherit it, including basic database CRUD (Create, Read, Update, Delete) operations, data validation, as well as sophisticated复杂的 search support and the ability to relate multiple models with each other.

If you’re using PHP 5.4/5.5 it is recommended you declare each column that makes part of the model in order to save memory and reduce the memory allocation.
如果使用PHP5.4/5.5推荐在每个模型中定义数据表的每个列。

By default, the model “Robots” will refer to the table “robots”. If you want to manually specify another name for the mapping table, you can use the getSource() method:
默认模型名自动映射到对应的表名,模型获取表名时调用getSource,所以重写这个方法可以修改模型管理的表名:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function getSource()
    {
        return "the_robots";
    }

}

The model Robots now maps to “the_robots” table. The initialize() method aids in setting up the model with a custom behavior i.e. a different table. The initialize() method is only called once during the request.

<?php
class Robots extends \Phalcon\Mvc\Model
{
	public function initialize()
	{
		$this->setSource("the_robots");
	}
}

The initialize() method is only called once during the request, it’s intended to perform initializations that apply for all instances of the model created within the application. If you want to perform initialization tasks for every instance created you can ‘onConstruct’:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function onConstruct()
    {
        //...
    }

}

–Public properties vs. Setters/Getters
Models can be implemented with properties of public scope, meaning that each property can be read/updated from any part of the code that has instantiated that model class without any restrictions:
可用公共属性 或 Setters/Getters,没有限制。

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public $price;
}

By using getters and setters you can control which properties are visible publicly perform various transformations to the data (which would be impossible otherwise) and also add validation rules to the data stored in the object:
使用Setters/Getters可以控制属性可见性并添加验证规则(原则上应该使用这个)

<?php

class Robots extends \Phalcon\Mvc\Model
{
    protected $id;

    protected $name;

    protected $price;

    public function getId()
    {
        return $this->id;
    }

    public function setName($name)
    {
        //The name is too short?
        if (strlen($name) < 10) {
            throw new \InvalidArgumentException('The name is too short');
        }
        $this->name = $name;
    }

    public function getName()
    {
        return $this->name;
    }

    public function setPrice($price)
    {
        //Negative prices aren't allowed
        if ($price < 0) {
            throw new \InvalidArgumentException('Price can\'t be negative');
        }
        $this->price = $price;
    }

    public function getPrice()
    {
        //Convert the value to double before be used
        return (double) $this->price;
    }
}

Public properties provide less complexity in development. However getters/setters can heavily increase the testability, extensibility and maintainability of applications. Developers can decide which strategy is more appropriate for the application they are creating. The ORM is compatible with both schemes of defining properties.
ORM兼容这个两种设置方法。

–Models in Namespaces 在命名空间中的模型
Namespaces can be used to avoid class name collision. The mapped table is taken from the class name, in this case ‘Robots’:
模型类可以使用命名空间避免类名冲突,但是映射到的数据表是跟类名一样的(不考虑命名空间),在这里是‘Robots’:

<?php

namespace Store\Toys;

class Robots extends \Phalcon\Mvc\Model
{

}

Understanding Records To Objects 理解记录到对象
Every instance of a model represents a row in the table. You can easily access record data by reading object properties. For example, for a table “robots” with the records:
每个模型实例代表表中的一行。你可以很容易通过读取对象属性来访问数据记录。

mysql> select * from robots;
+----+------------+------------+------+
| id | name       | type       | year |
+----+------------+------------+------+
|  1 | Robotina   | mechanical | 1972 |
|  2 | Astro Boy  | mechanical | 1952 |
|  3 | Terminator | cyborg     | 2029 |
+----+------------+------------+------+
3 rows in set (0.00 sec)

You could find a certain record by its primary key and then print its name:通过主键查找特定行,然后打印它的名字:

<?php

// Find record with id = 3
$robot = Robots::findFirst(3);

// Prints "Terminator"
echo $robot->name;

Once the record is in memory, you can make modifications to its data and then save changes:
记录一旦载入内存,你可以修改它的数据然后保持修改:

<?php

$robot = Robots::findFirst(3);
$robot->name = "RoboCop";
$robot->save();

As you can see, there is no need to use raw SQL statements. Phalcon\Mvc\Model provides high database abstraction for web applications.
Phalcon\Mvc\Model提供高层数据库抽象。

Finding Records 查找记录
Phalcon\Mvc\Model also offers several methods for querying records. The following examples will show you how to query one or more records from a model:
Phalcon\Mvc\Model提供多种方法查询记录:

<?php

// How many robots are there?
$robots = Robots::find();
echo "There are ", count($robots), "\n";

// How many mechanical robots are there?
$robots = Robots::find("type = 'mechanical'");
echo "There are ", count($robots), "\n";

// Get and print virtual robots ordered by name
$robots = Robots::find(array(
    "type = 'virtual'",
    "order" => "name"
));
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// Get first 100 virtual robots ordered by name
$robots = Robots::find(array(
    "type = 'virtual'",
    "order" => "name",
    "limit" => 100
));
foreach ($robots as $robot) {
   echo $robot->name, "\n";
}

(以上count一个结果的操作是不推荐的,因为它是逐行返回累加操作,对大结果集,这个操作就很槽糕,应该使用统计函数)

You could also use the findFirst() method to get only the first record matching the given criteria:
返回查询的第一条记录,可以使用findFirst(),实际对应MySQL就是limit 1。

<?php

// What's the first robot in robots table?
$robot = Robots::findFirst();
echo "The robot name is ", $robot->name, "\n";

// What's the first mechanical robot in robots table?
$robot = Robots::findFirst("type = 'mechanical'");
echo "The first mechanical robot name is ", $robot->name, "\n";

// Get first virtual robot ordered by name
$robot = Robots::findFirst(array("type = 'virtual'", "order" => "name"));
echo "The first virtual robot name is ", $robot->name, "\n";

Both find() and findFirst() methods accept an associative array specifying the search criteria:
可以使用关联数组:

<?php

$robot = Robots::findFirst(array(
    "type = 'virtual'",
    "order" => "name DESC",
    "limit" => 30
));

$robots = Robots::find(array(
    "conditions" => "type = ?1",
    "bind"       => array(1 => "virtual")
));

The available query options are:
可用查询选项:

Parameter Description Example
conditions Search conditions for the find operation. Is used to extract only those records that fulfill a specified criterion. By default Phalcon\Mvc\Model assumes the first parameter are the conditions. “conditions” => “name LIKE ‘steve%’”
columns Return specific columns instead of the full columns in the model. When using this option an incomplete object is returned “columns” => “id, name”
bind Bind is used together with options, by replacing placeholders and escaping values thus increasing security “bind” => array(“status” => “A”, “type” => “some-time”)
bindTypes When binding parameters, you can use this parameter to define additional casting to the bound parameters increasing even more the security “bindTypes” => array(Column::BIND_PARAM_STR, Column::BIND_PARAM_INT)
order Is used to sort the resultset. Use one or more fields separated by commas. “order” => “name DESC, status”
limit Limit the results of the query to results to certain range “limit” => 10 / “limit” => array(“number” => 10, “offset” => 5)
group Allows to collect data across multiple records and group the results by one or more columns “group” => “name, status”
for_update With this option, Phalcon\Mvc\Model reads the latest available data, setting exclusive locks on each row it reads “for_update” => true
shared_lock With this option, Phalcon\Mvc\Model reads the latest available data, setting shared locks on each row it reads “shared_lock” => true
cache Cache the resultset, reducing the continuous access to the relational system “cache” => array(“lifetime” => 3600, “key” => “my-find-key”)
hydration Sets the hydration strategy to represent each returned record in the result “hydration” => Resultset::HYDRATE_OBJECTS

(从这个表格来看,虽然不用写SQL,但是如果对SQL一无所知,也不行)

If you prefer, there is also available a way to create queries in an object-oriented way, instead of using an array of parameters:

<?php

$robots = Robots::query()
    ->where("type = :type:")
    ->andWhere("year < 2000")
    ->bind(array("type" => "mechanical"))
    ->order("name")
    ->execute();

(这个方式看起来更加直观,这是一种面向对象的构建查询的方式,execute()执行后返回ResultSet)

注意:find和findFirst方法是只能针对当模型操作,没有join操作,涉及多表操作时,一、可以依赖模型间的关系定义,自动完成;二、使用query()构建查询,三、使用Phalcon\Mvc\Query执行PQHL查询 四、使用Phalcon\Mvc\Query\Builder来构建PHQL查询语句然后执行;一般如果涉及多表查询,如果模型间的关系定义无法满足时(比如left join等),可以使用模型的query()方法构建查询并把逻辑封装在模型内部。

The static method query() returns a Phalcon\Mvc\Model\Criteria object that is friendly with IDE autocompleters.

All the queries are internally handled as PHQL queries. PHQL is a high-level, object-oriented and SQL-like language. This language provide you more features to perform queries like joining other models, define groupings, add aggregations etc.
所有查询先构建成PHQL查询,然后再翻译成具体数据库的SQL。

Lastly, there is the findFirstBy() method. This method expands on the “findFirst()” method mentioned earlier. It allows you to quickly perform a retrieval from a table by using the property name in the method itself and passing it a parameter that contains the data you want to search for in that column. An example is in order, so taking our Robots model mentioned earlier :

<?php

class Robots extends \Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public $price;
}

We have three properties to work with here. $id, $name and $price. So, let’s say you want to retrieve the first record in the table with the name ‘Terminator’. This could be written like so :

<?php

$name = "Terminator";
$robot = Robots::findFirstByName($name);

if($robot){
    $this->flash->success("The first robot with the name " . $name . " cost " . $robot->price ".");
}else{
    $this->flash->error("There were no robots found in our table with the name " . $name ".");
}

Notice that we used ‘Name’ in the method call and passed the variable $name to it, which contains the name we are looking for in our table. Notice also that when we find a match with our query, all the other properties are available to us as well.默认查询所有属性(虽然只查询一个字段,但是所有属性都被填充了)。

–Model Resultsets 模型结果集
While findFirst() returns directly an instance of the called class (when there is data to be returned), the find() method returns a Phalcon\Mvc\Model\Resultset\Simple. This is an object that encapsulates all the functionality a resultset has like traversing, seeking specific records, counting, etc.
findFirst()直接返回一个实例。find()返回Phalcon\Mvc\Model\Resultset\Simple实例。它封装了结果具备的遍历,定位指定记录,计总等。

These objects are more powerful than standard arrays. One of the greatest features of the Phalcon\Mvc\Model\Resultset is that at any time there is only one record in memory. This greatly helps in memory management especially when working with large amounts of data.
比数组强大,结果集在任何时间只有一条记录在内存(这个扯蛋了)

<?php

// Get all robots
$robots = Robots::find();

// Traversing with a foreach
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// Traversing with a while
$robots->rewind();
while ($robots->valid()) {
    $robot = $robots->current();
    echo $robot->name, "\n";
    $robots->next();
}

// Count the resultset
echo count($robots);

// Alternative way to count the resultset
echo $robots->count();

// Move the internal cursor to the third robot
$robots->seek(2);
$robot = $robots->current();

// Access a robot by its position in the resultset
$robot = $robots[5];

// Check if there is a record in certain position
if (isset($robots[3])) {
   $robot = $robots[3];
}

// Get the first record in the resultset
$robot = $robots->getFirst();

// Get the last record
$robot = $robots->getLast();

Phalcon’s resultsets emulate scrollable cursors, you can get any row just by accessing its position, or seeking the internal pointer to a specific position. Note that some database systems don’t support scrollable cursors, this forces to re-execute the query in order to rewind the cursor to the beginning and obtain the record at the requested position. Similarly, if a resultset is traversed several times, the query must be executed the same number of times.
Phalcon的结果集模拟游标。有些数据库系统不支持游标(不是说模拟么?要支持干啥?),这会强制重新执行查询以做每次定位操作(比如定位到第五行,查询然后定位,再次获取第10行,重新发起查询定位,这个不是很操蛋,不过这里看起来所谓的模拟游标就是类似MySQL中Limit,操蛋,如果支持Limit,查询还是要查的,不过可以限制具体的记录返回)。

Storing large query results in memory could consume many resources, because of this, resultsets are obtained from the database in chunks of 32 rows reducing the need for re-execute the request in several cases also saving memory.
这里说结果集每次从数据库获取32行作为一个块,如果定位在这里面,那么不需要再次查询(这里这个说法颠覆了上面说的结果每时只有一条记录在内存)

Note that resultsets can be serialized and stored in a cache backend. Phalcon\Cache can help with that task. However, serializing data causes Phalcon\Mvc\Model to retrieve all the data from the database in an array, thus consuming more memory while this process takes place.
可以把结果集序列化缓存,如果这样操作,会让模型去获取所有数据置入一个数组(如果是大集合,序列化将非常危险,因为从缓存获取数据,总是先全部读出,然后反序列化,然后获取一组数据,所有要把结果集进行缓存,你必须十分清楚你在干什么)。

<?php

// Query all records from model parts
$parts = Parts::find();

// Store the resultset into a file
file_put_contents("cache.txt", serialize($parts));

// Get parts from file
$parts = unserialize(file_get_contents("cache.txt"));

// Traverse the parts
foreach ($parts as $part) {
   echo $part->id;
}

–Filtering Resultsets 过滤结果集
The most efficient way to filter data is setting some search criteria, databases will use indexes set on tables to return data faster. Phalcon additionally allows you to filter the data using PHP using any resource that is not available in the database:

<?php

$customers = Customers::find()->filter(function($customer) {

    //Return only customers with a valid e-mail
    if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
        return $customer;
    }

});

(每条返回的记录都经过匿名函数的检测,这样的搞法如果合理使用,将提升效率,否则就是性能杀手。)

–Binding Parameters 绑定参数
Bound parameters are also supported in Phalcon\Mvc\Model. Although there is a minimal performance impact by using bound parameters, you are encouraged to use this methodology so as to eliminate the possibility of your code being subject to SQL injection attacks. Both string and integer placeholders are supported. Binding parameters can simply be achieved as follows:字符串和数字占位符都支持:

<?php

// Query robots binding parameters with string placeholders
$conditions = "name = :name: AND type = :type:";

//Parameters whose keys are the same as placeholders
$parameters = array(
    "name" => "Robotina",
    "type" => "maid"
);

//Perform the query
$robots = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

// Query robots binding parameters with integer placeholders
$conditions = "name = ?1 AND type = ?2";
$parameters = array(1 => "Robotina", 2 => "maid");
$robots     = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

// Query robots binding parameters with both string and integer placeholders
$conditions = "name = :name: AND type = ?1";

//Parameters whose keys are the same as placeholders
$parameters = array(
    "name" => "Robotina",
    1 => "maid"
);

//Perform the query
$robots = Robots::find(array(
    $conditions,
    "bind" => $parameters
));

When using numeric placeholders, you will need to define them as integers i.e. 1 or 2. In this case “1” or “2” are considered strings and not numbers, so the placeholder could not be successfully replaced.

Strings are automatically escaped using PDO. This function takes into account the connection charset, so its recommended to define the correct charset in the connection parameters or in the database configuration, as a wrong charset will produce undesired effects when storing or retrieving data.

Additionally you can set the parameter “bindTypes”, this allows defining how the parameters should be bound according to its data type:

<?php

use \Phalcon\Db\Column;

//Bind parameters
$parameters = array(
    "name" => "Robotina",
    "year" => 2008
);

//Casting Types
$types = array(
    "name" => Column::BIND_PARAM_STR,
    "year" => Column::BIND_PARAM_INT
);

// Query robots binding parameters with string placeholders
$robots = Robots::find(array(
    "name = :name: AND year = :year:",
    "bind" => $parameters,
    "bindTypes" => $types
));

Since the default bind-type is \Phalcon\Db\Column::BIND_PARAM_STR, there is no need to specify the “bindTypes” parameter if all of the columns are of that type.

Bound parameters are available for all query methods such as find() and findFirst() but also the calculation methods like count(), sum(), average() etc.

Initializing/Preparing fetched records
May be the case that after obtaining a record from the database is necessary to initialise the data before being used by the rest of the application. You can implement the method ‘afterFetch’ in a model, this event will be executed just after create the instance and assign the data to it:

<?php

class Robots extends Phalcon\Mvc\Model
{

    public $id;

    public $name;

    public $status;

    public function beforeSave()
    {
        //Convert the array into a string
        $this->status = join(',', $this->status);
    }

    public function afterFetch()
    {
        //Convert the string to an array
        $this->status = explode(',', $this->status);
    }
}

If you use getters/setters instead of/or together with public properties, you can initialize the field once it is accessed:

<?php

class Robots extends Phalcon\Mvc\Model
{
    public $id;

    public $name;

    public $status;

    public function getStatus()
    {
        return explode(',', $this->status);
    }

}

Relationships between Models 模型之间的关系
Virtual Foreign Keys 虚拟外键
这两个内容是ORM的核心,单独拆分到http://blog.ifeeline.com/1246.html

Generating Calculations 产生计算
Calculations (or aggregations) are helpers for commonly used functions of database systems such as COUNT, SUM, MAX, MIN or AVG. Phalcon\Mvc\Model allows to use these functions directly from the exposed methods.
(注意这里的count是模型的方法,不是计总结果时直接使用的count)

<?php

// How many employees are?
$rowcount = Employees::count();

// How many different areas are assigned to employees?
$rowcount = Employees::count(array("distinct" => "area"));

// How many employees are in the Testing area?
$rowcount = Employees::count("area = 'Testing'");

// Count employees grouping results by their area
$group = Employees::count(array("group" => "area"));
foreach ($group as $row) {
   echo "There are ", $row->rowcount, " in ", $row->area;
}

// Count employees grouping by their area and ordering the result by count
$group = Employees::count(array(
    "group" => "area",
    "order" => "rowcount"
));

// Avoid SQL injections using bound parameters
$group = Employees::count(array(
    "type > ?0",
    "bind" => array($type)
));

<?php

// How much are the salaries of all employees?
$total = Employees::sum(array("column" => "salary"));

// How much are the salaries of all employees in the Sales area?
$total = Employees::sum(array(
    "column"     => "salary",
    "conditions" => "area = 'Sales'"
));

// Generate a grouping of the salaries of each area
$group = Employees::sum(array(
    "column" => "salary",
    "group"  => "area"
));
foreach ($group as $row) {
   echo "The sum of salaries of the ", $row->area, " is ", $row->sumatory;
}

// Generate a grouping of the salaries of each area ordering
// salaries from higher to lower
$group = Employees::sum(array(
    "column" => "salary",
    "group"  => "area",
    "order"  => "sumatory DESC"
));

// Avoid SQL injections using bound parameters
$group = Employees::sum(array(
    "conditions" => "area > ?0",
    "bind" => array($area)
));

<?php

// What is the average salary for all employees?
$average = Employees::average(array("column" => "salary"));

// What is the average salary for the Sales's area employees?
$average = Employees::average(array(
    "column" => "salary",
    "conditions" => "area = 'Sales'"
));

// Avoid SQL injections using bound parameters
$average = Employees::average(array(
    "column" => "age",
    "conditions" => "area > ?0",
    "bind" => array($area)
));

<?php

// What is the oldest age of all employees?
$age = Employees::maximum(array("column" => "age"));

// What is the oldest of employees from the Sales area?
$age = Employees::maximum(array(
    "column" => "age",
    "conditions" => "area = 'Sales'"
));

// What is the lowest salary of all employees?
$salary = Employees::minimum(array("column" => "salary"));

Hydration Modes
As mentioned above, resultsets are collections of complete objects, this means that every returned result is an object representing a row in the database. These objects can be modified and saved again to persistence:

<?php

// Manipulating a resultset of complete objects
foreach (Robots::find() as $robot) {
    $robot->year = 2000;
    $robot->save();
}

Sometimes records are obtained only to be presented to a user in read-only mode, in these cases it may be useful to change the way in which records are represented to facilitate their handling. The strategy used to represent objects returned in a resultset is called ‘hydration mode’:

<?php

use Phalcon\Mvc\Model\Resultset;

$robots = Robots::find();

//Return every robot as an array
$robots->setHydrateMode(Resultset::HYDRATE_ARRAYS);

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

//Return every robot as an stdClass
$robots->setHydrateMode(Resultset::HYDRATE_OBJECTS);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

//Return every robot as a Robots instance
$robots->setHydrateMode(Resultset::HYDRATE_RECORDS);

foreach ($robots as $robot) {
    echo $robot->year, PHP_EOL;
}

Hydration mode can also be passed as a parameter of ‘find’:

<?php

use Phalcon\Mvc\Model\Resultset;

$robots = Robots::find(array(
    'hydration' => Resultset::HYDRATE_ARRAYS
));

foreach ($robots as $robot) {
    echo $robot['year'], PHP_EOL;
}

Creating Updating/Records 创建更新/记录
The method Phalcon\Mvc\Model::save() allows you to create/update records according to whether they already exist in the table associated with a model. The save method is called internally by the create and update methods of Phalcon\Mvc\Model. For this to work as expected it is necessary to have properly defined a primary key in the entity to determine whether a record should be updated or created.
save方法内部实际调用create和更新方法。需要正确定义主键。(如果指定了主键值,就是update操作,否则就是create操作)

Also the method executes associated validators, virtual foreign keys and events that are defined in the model:

<?php

$robot       = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;
if ($robot->save() == false) {
    echo "Umh, We can't store robots right now: \n";
    foreach ($robot->getMessages() as $message) {
        echo $message, "\n";
    }
} else {
    echo "Great, a new robot was saved successfully!";
}

An array could be passed to “save” to avoid assign every column manually. Phalcon\Mvc\Model will check if there are setters implemented for the columns passed in the array giving priority to them instead of assign directly the values of the attributes:

<?php

$robot = new Robots();
$robot->save(array(
    "type" => "mechanical",
    "name" => "Astro Boy",
    "year" => 1952
));

Values assigned directly or via the array of attributes are escaped/sanitized according to the related attribute data type. So you can pass an insecure array without worrying about possible SQL injections:

<?php

$robot = new Robots();
$robot->save($_POST);

Without precautions mass assignment could allow attackers to set any database column’s value. Only use this feature if you want to permit a user to insert/update every column in the model, even if those fields are not in the submitted form.

You can set an additional parameter in ‘save’ to set a whitelist of fields that only must taken into account when doing the mass assignment:

<?php

$robot = new Robots();
$robot->save($_POST, array('name', 'type'));

——————————————————————
在保存或查询操作前都会先获取元数据(可能来自缓存),默认来自数据库,首先产生:

DESCRIBE `users`

在做更新和插入操作时,会判断哪些字段是不能空的,如果不分配值操作就不能成功。
——————————————————————
–Create/Update with Confidence 根据条件创建或更新
When an application has a lot of competition, we could be expecting create a record but it is actually updated. This could happen if we use Phalcon\Mvc\Model::save() to persist the records in the database. If we want to be absolutely sure that a record is created or updated, we can change the save() call with create() or update():
(这里描述的这个情况很难想象到发生的场景,因为更新操作肯定是上锁的)

<?php

$robot       = new Robots();
$robot->type = "mechanical";
$robot->name = "Astro Boy";
$robot->year = 1952;

//This record only must be created
if ($robot->create() == false) {
    echo "Umh, We can't store robots right now: \n";
    foreach ($robot->getMessages() as $message) {
        echo $message, "\n";
    }
} else {
    echo "Great, a new robot was created successfully!";
}

These methods “create” and “update” also accept an array of values as parameter.

–Auto-generated identity columns
Some models may have identity columns. These columns usually are the primary key of the mapped table. Phalcon\Mvc\Model can recognize the identity column omitting it in the generated SQL INSERT, so the database system can generate an auto-generated value for it. Always after creating a record, the identity field will be registered with the value generated in the database system for it:

<?php

$robot->save();

echo "The generated id is: ", $robot->id;

Phalcon\Mvc\Model is able to recognize the identity column. Depending on the database system, those columns may be serial columns like in PostgreSQL or auto_increment columns in the case of MySQL.

PostgreSQL uses sequences to generate auto-numeric values, by default, Phalcon tries to obtain the generated value from the sequence “table_field_seq”, for example: robots_id_seq, if that sequence has a different name, the method “getSequenceName” needs to be implemented:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function getSequenceName()
    {
        return "robots_sequence_name";
    }

}

–Storing related records 存储记录排序
Magic properties can be used to store a records and its related properties:

<?php

// Create an artist
$artist = new Artists();
$artist->name = 'Shinichi Osawa';
$artist->country = 'Japan';

// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist; //Assign the artist
$album->year = 2008;

//Save both records
$album->save();

Saving a record and its related records in a has-many relation:

<?php

// Get an existing artist
$artist = Artists::findFirst('name = "Shinichi Osawa"');

// Create an album
$album = new Albums();
$album->name = 'The One';
$album->artist = $artist;

$songs = array();

// Create a first song
$songs[0] = new Songs();
$songs[0]->name = 'Star Guitar';
$songs[0]->duration = '5:54';

// Create a second song
$songs[1] = new Songs();
$songs[1]->name = 'Last Days';
$songs[1]->duration = '4:29';

// Assign the songs array
$album->songs = $songs;

// Save the album + its songs
$album->save();

Saving the album and the artist at the same time implicitly makes use of a transaction so if anything goes wrong with saving the related records, the parent will not be saved either. Messages are passed back to the user for information regarding any errors.

Note: Adding related entities by overloading the following methods is not possible:

        PhalconMvcModel::beforeSave()
        PhalconMvcModel::beforeCreate()
        PhalconMvcModel::beforeUpdate()

You need to overload PhalconMvcModel::save() for this to work from within a model.

–Validation Messages 验证消息
Phalcon\Mvc\Model has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the insert/update processes.
Phalcon\Mvc\Model有一个子系统,它提供一种灵活的方式输出和保存在插入和更新过程中产生的验证消息。

Each message consists of an instance of the class Phalcon\Mvc\Model\Message. The set of messages generated can be retrieved with the method getMessages(). Each message provides extended information like the field name that generated the message or the message type:
每个消息由Phalcon\Mvc\Model\Message类实例组成。产生的消息集可以通过getMessages()方法获取。

<?php

if ($robot->save() == false) {
    foreach ($robot->getMessages() as $message) {
        echo "Message: ", $message->getMessage();
        echo "Field: ", $message->getField();
        echo "Type: ", $message->getType();
    }
}

Phalcon\Mvc\Model can generate the following types of validation messages:
支持的消息类型:

Type Description
PresenceOf Generated when a field with a non-null attribute on the database is trying to insert/update a null value
ConstraintViolation Generated when a field part of a virtual foreign key is trying to insert/update a value that doesn’t exist in the referenced model
InvalidValue Generated when a validator failed because of an invalid value
InvalidCreateAttempt Produced when a record is attempted to be created but it already exists
InvalidUpdateAttempt Produced when a record is attempted to be updated but it doesn’t exist

The method getMessages() can be overridden in a model to replace/translate the default messages generated automatically by the ORM:
在模型中getMessages()方法可以重写以替换或翻译默认由ORM自动产生的消息:

<?php

class Robots extends Phalcon\Mvc\Model
{
    public function getMessages()
    {
        $messages = array();
        foreach (parent::getMessages() as $message) {
            switch ($message->getType()) {
                case 'InvalidCreateAttempt':
                    $messages[] = 'The record cannot be created because it already exists';
                    break;
                case 'InvalidUpdateAttempt':
                    $messages[] = 'The record cannot be updated because it already exists';
                    break;
                case 'PresenceOf':
                    $messages[] = 'The field ' . $message->getField() . ' is mandatory';
                    break;
            }
        }
        return $messages;
    }
}

–Events and Events Manager 事件与事件管理器
Models allow you to implement events that will be thrown when performing an insert/update/delete. They help define business rules for a certain model. The following are the events supported by Phalcon\Mvc\Model and their order of execution:
模型允许你去实现当执行insert/update/delete时被抛出的事件。一下是Phalcon\Mvc\Model支持的事件和它们执行的顺序:

Operation Name Can stop operation? Explanation
Inserting/Updating beforeValidation YES Is executed before the fields are validated for not nulls/empty strings or foreign keys
Inserting beforeValidationOnCreate YES Is executed before the fields are validated for not nulls/empty strings or foreign keys when an insertion operation is being made
Updating beforeValidationOnUpdate YES Is executed before the fields are validated for not nulls/empty strings or foreign keys when an updating operation is being made
Inserting/Updating onValidationFails YES (already stopped) Is executed after an integrity validator fails
Inserting afterValidationOnCreate YES Is executed after the fields are validated for not nulls/empty strings or foreign keys when an insertion operation is being made
Updating afterValidationOnUpdate YES Is executed after the fields are validated for not nulls/empty strings or foreign keys when an updating operation is being made
Inserting/Updating afterValidation YES Is executed after the fields are validated for not nulls/empty strings or foreign keys
Inserting/Updating beforeSave YES Runs before the required operation over the database system
Updating beforeUpdate YES Runs before the required operation over the database system only when an updating operation is being made
Inserting beforeCreate YES Runs before the required operation over the database system only when an inserting operation is being made
Updating afterUpdate NO Runs after the required operation over the database system only when an updating operation is being made
Inserting afterCreate NO Runs after the required operation over the database system only when an inserting operation is being made
Inserting/Updating afterSave NO Runs after the required operation over the database system

–Implementing Events in the Model’s class 在模型类中实现事件
The easier way to make a model react to events is implement a method with the same name of the event in the model’s class:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function beforeValidationOnCreate()
    {
        echo "This is executed before creating a Robot!";
    }

}

Events can be useful to assign values before performing an operation, for example:
在执行一个操作前去分配值,事件很有用。实际上,在操作是也可以即时分配,但是有些字段确实不需要每次都做相同操作。

<?php

class Products extends \Phalcon\Mvc\Model
{

    public function beforeCreate()
    {
        //Set the creation date
        $this->created_at = date('Y-m-d H:i:s');
    }

    public function beforeUpdate()
    {
        //Set the modification date
        $this->modified_in = date('Y-m-d H:i:s');
    }

}

–Using a custom Events Manager 使用自定义事件管理器
Additionally, this component is integrated with Phalcon\Events\Manager, this means we can create listeners that run when an event is triggered.

<?php

use Phalcon\Mvc\Model,
    Phalcon\Events\Manager as EventsManager;

class Robots extends Model
{

    public function initialize()
    {

        $eventsManager = new EventsManager();

        //Attach an anonymous function as a listener for "model" events
        $eventsManager->attach('model', function($event, $robot) {
            if ($event->getType() == 'beforeSave') {
                if ($robot->name == 'Scooby Doo') {
                    echo "Scooby Doo isn't a robot!";
                    return false;
                }
            }
            return true;
        });

        //Attach the events manager to the event
        $this->setEventsManager($eventsManager);
    }

}

In the example given above, EventsManager only acts as a bridge between an object and a listener (the anonymous function). Events will be fired to the listener when ‘robots’ are saved:(触发事件)

<?php

$robot = new Robots();
$robot->name = 'Scooby Doo';
$robot->year = 1969;
$robot->save();

(为模型指定一个事件管理器是完全可以的,但是默认事件触发是向model前缀fire的,所以使用自定义的事件管理器要适得默认事件可以触发,那么也必须在attach遵守一致的约定,不过,如果实在觉得过意不去,可以去重写对应方法,比如在save方法,可以向自定义前缀开火,那么在事件管理器中attach时就可以指定自定义前缀了)

If we want all objects created in our application use the same EventsManager, then we need to assign it to the Models Manager:
如果我们想让在应用中创建的所有对象使用相同的事件管理器,我们需要把它分配到模型管理器(换个说法就是分配到模型处理器的事件管理器,那么所有模型都使用这个事件管理器):

<?php

//Registering the modelsManager service
$di->setShared('modelsManager', function() {

    $eventsManager = new \Phalcon\Events\Manager();

    //Attach an anonymous function as a listener for "model" events
    $eventsManager->attach('model', function($event, $model){

        //Catch events produced by the Robots model
        if (get_class($model) == 'Robots') {

            if ($event->getType() == 'beforeSave') {
                if ($model->name == 'Scooby Doo') {
                    echo "Scooby Doo isn't a robot!";
                    return false;
                }
            }

        }
        return true;
    });

    //Setting a default EventsManager
    $modelsManager = new ModelsManager();
    $modelsManager->setEventsManager($eventsManager);
    return $modelsManager;
});

注意,Phalcon MVC默认就会初始化一个模型管理器,并实现类似上面的代码(把全局事件管理器作为模型事件管理器)。上面代码是为所有模型指定一个全新的事件管理器。

–Implementing a Business Rule 业务逻辑实现
When an insert, update or delete is executed, the model verifies if there are any methods with the names of the events listed in the table above.

We recommend that validation methods are declared protected to prevent that business logic implementation from being exposed publicly.
建议定义验证方法定位为protected(一般指事件方法)

The following example implements an event that validates the year cannot be smaller than 0 on update or insert:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function beforeSave()
    {
        if ($this->year < 0) {
            echo "Year cannot be smaller than zero!";
            return false;
        }
    }

}

Some events return false as an indication to stop the current operation. If an event doesn’t return anything, Phalcon\Mvc\Model will assume a true value.
有些事件返回false表示停止当前操作。如果事件不返回任何东西,Phalcon\Mvc\Model假设放回true。

–Validating Data Integrity 验证数据完整性
Phalcon\Mvc\Model provides several events to validate data and implement business rules. The special “validation” event allows us to call built-in validators over the record. Phalcon exposes a few built-in validators that can be used at this stage of validation.
Phalcon\Mvc\Model提供了多个事件去验证数据和实现业务逻辑。这个特定的“validation”事件允许我们在记录上调用内置的验证器。Phalcon暴露了一些内置的验证器。

The following example shows how to use it:

<?php

use Phalcon\Mvc\Model\Validator\InclusionIn,
    Phalcon\Mvc\Model\Validator\Uniqueness;

class Robots extends \Phalcon\Mvc\Model
{

    public function validation()
    {

        $this->validate(new InclusionIn(
            array(
                "field"  => "type",
                "domain" => array("Mechanical", "Virtual")
            )
        ));

        $this->validate(new Uniqueness(
            array(
                "field"   => "name",
                "message" => "The robot name must be unique"
            )
        ));

        return $this->validationHasFailed() != true;
    }

}

The above example performs a validation using the built-in validator “InclusionIn”. It checks the value of the field “type” in a domain list. If the value is not included in the method then the validator will fail and return false. The following built-in validators are available:
以上例子使用内置的InclusionIn验证器来执行验证。它检测字段值是否在domain列表中。如果不包含这个值,验证器验证失败并返回false.还有如下内置的验证器可用:

Name Explanation Example
PresenceOf Validates that a field’s value isn’t null or empty string. This validator is automatically added based on the attributes marked as not null on the mapped table Example
Email Validates that field contains a valid email format Example
ExclusionIn Validates that a value is not within a list of possible values Example
InclusionIn Validates that a value is within a list of possible values Example
Numericality Validates that a field has a numeric format Example
Regex Validates that the value of a field matches a regular expression Example
Uniqueness Validates that a field or a combination of a set of fields are not present more than once in the existing records of the related table Example
StringLength Validates the length of a string Example
Url Validates that a value has a valid URL format Example

(以下是自定义验证器,这里先省略…)

–Avoiding SQL injections 避免SQL注入
Every value assigned to a model attribute is escaped depending of its data type. A developer doesn’t need to escape manually each value before storing it on the database. Phalcon uses internally the bound parameters capability provided by PDO to automatically escape every value to be stored in the database.
每个分配到模型属性的值会根据它的数据类型escape。开发者不需要在插入到数据库前手动escape每个值。Phalcon内部自动使用PDO提供的参数绑定来escape每个将插入到数据库的值。(实际情况是先换成PHQL,而这些PHQL会采用参数绑定的方式插入值)

(省略例子…)

Skipping Columns 忽略某些列
To tell Phalcon\Mvc\Model that always omits some fields in the creation and/or update of records in order to delegate the database system the assignation of the values by a trigger or a default:
为了告诉Phalcon\Mvc\Model在创建或更新记录是总是省略一些字段

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function initialize()
    {
        //Skips fields/columns on both INSERT/UPDATE operations
        $this->skipAttributes(array('year', 'price'));

        //Skips only when inserting
        $this->skipAttributesOnCreate(array('created_at'));

        //Skips only when updating
        $this->skipAttributesOnUpdate(array('modified_in'));
    }

}

This will ignore globally these fields on each INSERT/UPDATE operation on the whole application. If you want to ignore different attributes on different INSERT/UPDATE operations, you can specify the second parameter (boolean) – true for replacement. Forcing a default value can be done in the following way:
第二参数指定为true,强制要求一个默认值:

<?php

$robot = new Robots();
$robot->name = 'Bender';
$robot->year = 1999;
$robot->created_at = new \Phalcon\Db\RawValue('default');
$robot->create();

A callback also can be used to create a conditional assignment of automatic default values:

<?php

use Phalcon\Mvc\Model,
    Phalcon\Db\RawValue;

class Robots extends Model
{
    public function beforeCreate()
    {
        if ($this->price > 10000) {
            $this->type = new RawValue('default');
        }
    }
}

Never use a \Phalcon\Db\RawValue to assign external data (such as user input) or variable data. The value of these fields is ignored when binding parameters to the query. So it could be used to attack the application injecting SQL.

–Dynamic Update 动态更新
SQL UPDATE statements are by default created with every column defined in the model (full all-field SQL update). You can change specific models to make dynamic updates, in this case, just the fields that had changed are used to create the final SQL statement.

In some cases this could improve the performance by reducing the traffic between the application and the database server, this specially helps when the table has blob/text fields:

<?php

class Robots extends Phalcon\Mvc\Model
{
    public function initialize()
    {
        $this->useDynamicUpdate(true);
    }
}

所谓动态更新就是当更新的数据没有任何更改时,更新操作被忽略,说是这个特征有些情况能够减少传输提供性能(减少传输是肯定的,提高性能就未必了)
——————————

//控制器代码
$user = Users::findFirst();
$user->save()

以下是启用与不启用动态更新的SQL输出

启用动态更新
[Mon, 01 Sep 14 23:48:51 +0800][INFO] SELECT IF(COUNT(*)>0, 1 , 0) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_NAME`='users'
[Mon, 01 Sep 14 23:48:51 +0800][INFO] DESCRIBE `users`
[Mon, 01 Sep 14 23:48:51 +0800][INFO] SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users` LIMIT 1
不启用动态更新
[Mon, 01 Sep 14 23:49:29 +0800][INFO] SELECT IF(COUNT(*)>0, 1 , 0) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_NAME`='users'
[Mon, 01 Sep 14 23:49:29 +0800][INFO] DESCRIBE `users`
[Mon, 01 Sep 14 23:49:29 +0800][INFO] SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users` LIMIT 1
[Mon, 01 Sep 14 23:49:29 +0800][INFO] UPDATE `users` SET `name` = ?, `email` = ? WHERE `id` = ?

可以看到,在不启用动态更新时,UPDATE语句发出了。
——————————

Deleting Records 删除记录
The method Phalcon\Mvc\Model::delete() allows to delete a record. You can use it as follows:

<?php

$robot = Robots::findFirst(11);
if ($robot != false) {
    if ($robot->delete() == false) {
        echo "Sorry, we can't delete the robot right now: \n";
        foreach ($robot->getMessages() as $message) {
            echo $message, "\n";
        }
    } else {
        echo "The robot was deleted successfully!";
    }
}

You can also delete many records by traversing a resultset with a foreach:

<?php

foreach (Robots::find("type='mechanical'") as $robot) {
    if ($robot->delete() == false) {
        echo "Sorry, we can't delete the robot right now: \n";
        foreach ($robot->getMessages() as $message) {
            echo $message, "\n";
        }
    } else {
        echo "The robot was deleted successfully!";
    }
}

——————————–
以上这段程序看起来非常低效,比如:

$users = Users::find();
foreach($users as $u){
	$u->delete();
}

对应的SQL输出:

[Tue, 02 Sep 14 00:04:18 +0800][INFO] SELECT IF(COUNT(*)>0, 1 , 0) FROM `INFORMATION_SCHEMA`.`TABLES` WHERE `TABLE_NAME`='users'
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DESCRIBE `users`
[Tue, 02 Sep 14 00:04:18 +0800][INFO] SELECT `users`.`id`, `users`.`name`, `users`.`email` FROM `users`
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DELETE FROM `users` WHERE `id` = ?
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DELETE FROM `users` WHERE `id` = ?
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DELETE FROM `users` WHERE `id` = ?
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DELETE FROM `users` WHERE `id` = ?
[Tue, 02 Sep 14 00:04:18 +0800][INFO] DELETE FROM `users` WHERE `id` = ?

一条条删除。除非写SQL,暂时找不到类似SQL批量删除的办法。

Users::find()返回的是一个Phalcon\Mvc\Model\ResultSet对象,它有一个delete方法,可以:

$users = Users::find();
$users->delete();

不过这种搞法,跟来一个foreach本质是一样的。
——————————–
The following events are available to define custom business rules that can be executed when a delete operation is performed:

Operation Name Can stop operation? Explanation
Deleting beforeDelete YES Runs before the delete operation is made
Deleting afterDelete NO Runs after the delete operation was made

With the above events can also define business rules in the models:

<?php

class Robots extends Phalcon\Mvc\Model
{

    public function beforeDelete()
    {
        if ($this->status == 'A') {
            echo "The robot is active, it can't be deleted";
            return false;
        }
        return true;
    }

}

(可以在这里事件中添加业务逻辑)

Validation Failed Events 验证失败事件
Another type of events are available when the data validation process finds any inconsistency:

Operation Name Explanation
Insert or Update notSave Triggered when the INSERT or UPDATE operation fails for any reason
Insert, Delete or Update onValidationFails Triggered when any data manipulation operation fails


Behaviors (这部分先省略)

Independent Column Mapping 独立的列映射
The ORM supports an independent column map, which allows the developer to use different column names in the model to the ones in the table. Phalcon will recognize the new column names and will rename them accordingly to match the respective columns in the database. This is a great feature when one needs to rename fields in the database without having to worry about all the queries in the code. A change in the column map in the model will take care of the rest. For example:
模型中使用的字段和数据表不一样,可以通过映射来实现。这个映射的做法,可以避免当数据字段改名时而波及到代码中的所有查询,看起来,所有字段都应该做映射。。。。

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function columnMap()
    {
        //Keys are the real names in the table and
        //the values their names in the application
        return array(
            'id' => 'code',
            'the_name' => 'theName',
            'the_type' => 'theType',
            'the_year' => 'theYear'
        );
    }

}

Then you can use the new names naturally in your code:

<?php

//Find a robot by its name
$robot = Robots::findFirst("theName = 'Voltron'");
echo $robot->theName, "\n";

//Get robots ordered by type
$robot = Robots::find(array('order' => 'theType DESC'));
foreach ($robots as $robot) {
    echo 'Code: ', $robot->code, "\n";
}

//Create a robot
$robot = new Robots();
$robot->code = '10101';
$robot->theName = 'Bender';
$robot->theType = 'Industrial';
$robot->theYear = 2999;
$robot->save();

Take into consideration the following the next when renaming your columns:
References to attributes in relationships/validators must use the new names(在关系或验证器中引用到属性必须使用新名)
Refer the real column names will result in an exception by the ORM
(引用到真实列名将产生异常)

The independent column map allow you to:
Write applications using your own conventions 建立自已的约定
Eliminate vendor prefixes/suffixes in your code 添加前后缀
Change column names without change your application code 改变列名不改动代码

Operations over Resultsets 在结果集上操作

If a resultset is composed of complete objects, the resultset is in the ability to perform operations on the records obtained in a simple manner:

–Updating related records 更新相关记录

<?php

foreach ($robots->getParts() as $part) {
    $part->stock = 100;
    $part->updated_at = time();
    if ($part->update() == false) {
        foreach ($part->getMessages() as $message) {
            echo $message;
        }
        break;
    }
}

<?php

$robots->getParts()->update(array(
    'stock' => 100,
    'updated_at' => time()
));

//‘update’ also accepts an anonymous function to filter what records must be updated:
<?php

$data = array(
    'stock' => 100,
    'updated_at' => time()
);

//Update all the parts except these whose type is basic
$robots->getParts()->update($data, function($part) {
    if ($part->type == Part::TYPE_BASIC) {
        return false;
    }
    return true;
});

–Deleting related records 删除相关记录

<?php

foreach ($robots->getParts() as $part) {
    if ($part->delete() == false) {
        foreach ($part->getMessages() as $message) {
            echo $message;
        }
        break;
    }
}

<?php

$robots->getParts()->delete();

‘delete’ also accepts an anonymous function to filter what records must be deleted:

Record Snapshots 记录快照
Specific models could be set to maintain a record snapshot when they’re queried. You can use this feature to implement auditing or just to know what fields are changed according to the data queried from the persistence:
特殊模型可以设置维护记录快照:

<?php

class Robots extends Phalcon\Mvc\Model
{
    public function initialize()
    {
        $this->keepSnapshots(true);
    }
}

When activating this feature the application consumes a bit more of memory to keep track of the original values obtained from the persistence. In models that have this feature activated you can check what fields changed:
可以检查哪些字段改变了:

<?php

//Get a record from the database
$robot = Robots::findFirst();

//Change a column
$robot->name = 'Other name';

var_dump($robot->getChangedFields()); // ['name']
var_dump($robot->hasChanged('name')); // true
var_dump($robot->hasChanged('type')); // false

Pointing to a different schema
If a model is mapped to a table that is in a different schemas/databases than the default. You can use the getSchema method to define that:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function getSchema()
    {
        return "toys";
    }

}

Setting multiple databases 设置多数据库
In Phalcon, all models can belong to the same database connection or have an individual one. Actually, when Phalcon\Mvc\Model needs to connect to the database it requests the “db” service in the application’s services container. You can overwrite this service setting it in the initialize method:
可以属于相同数据库链接也可以属于私有的。Phalcon\Mvc\Model需要数据库链接时,它向容器获取db服务。所以可以重新这个服务的初始化:

<?php

//This service returns a MySQL database
$di->set('dbMysql', function() {
     return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "invo"
    ));
});

//This service returns a PostgreSQL database
$di->set('dbPostgres', function() {
     return new \Phalcon\Db\Adapter\Pdo\PostgreSQL(array(
        "host" => "localhost",
        "username" => "postgres",
        "password" => "",
        "dbname" => "invo"
    ));
});

//具体模型中
<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->setConnectionService('dbPostgres');
    }

}

But Phalcon offers you more flexibility, you can define the connection that must be used to ‘read’ and for ‘write’. This is specially useful to balance the load to your databases implementing a master-slave architecture:
可以定义读写链接,在主从复制结构中非常有用:

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function initialize()
    {
        $this->setReadConnectionService('dbSlave');
        $this->setWriteConnectionService('dbMaster');
    }

}

The ORM also provides Horizontal Sharding facilities, by allowing you to implement a ‘shard’ selection according to the current query conditions:

<?php

class Robots extends Phalcon\Mvc\Model
{
    /**
     * Dynamically selects a shard
     *
     * @param array $intermediate
     * @param array $bindParams
     * @param array $bindTypes
     */
    public function selectReadConnection($intermediate, $bindParams, $bindTypes)
    {
        //Check if there is a 'where' clause in the select
        if (isset($intermediate['where'])) {

            $conditions = $intermediate['where'];

            //Choose the possible shard according to the conditions
            if ($conditions['left']['name'] == 'id') {
                $id = $conditions['right']['value'];
                if ($id > 0 && $id < 10000) {
                    return $this->getDI()->get('dbShard1');
                }
                if ($id > 10000) {
                    return $this->getDI()->get('dbShard2');
                }
            }
        }

        //Use a default shard
        return $this->getDI()->get('dbShard0');
    }

}

The method ‘selectReadConnection’ is called to choose the right connection, this method intercepts any new query executed:

<?php

$robot = Robots::findFirst('id = 101');

Logging Low-Level SQL Statements 记录底层SQL语句
When using high-level abstraction components such as Phalcon\Mvc\Model to access a database, it is difficult to understand which statements are finally sent to the database system. Phalcon\Mvc\Model is supported internally by Phalcon\Db. Phalcon\Logger interacts with Phalcon\Db, providing logging capabilities on the database abstraction layer, thus allowing us to log SQL statements as they happen.

<?php

use Phalcon\Logger,
    Phalcon\Db\Adapter\Pdo\Mysql as Connection,
    Phalcon\Events\Manager,
    Phalcon\Logger\Adapter\File as FileLogger;

$di->set('db', function() {

    $eventsManager = new EventsManager();

    $logger = new FileLogger("app/logs/debug.log");

    //Listen all the database events
    $eventsManager->attach('db', function($event, $connection) use ($logger) {
        if ($event->getType() == 'beforeQuery') {
            $logger->log($connection->getSQLStatement(), Logger::INFO);
        }
    });

    $connection = new Connection(array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "invo"
    ));

    //Assign the eventsManager to the db adapter instance
    $connection->setEventsManager($eventsManager);

    return $connection;
});

As models access the default database connection, all SQL statements that are sent to the database system will be logged in the file:

<?php

$robot = new Robots();
$robot->name = "Robby the Robot";
$robot->created_at = "1956-07-21";
if ($robot->save() == false) {
    echo "Cannot save robot";
}

As above, the file app/logs/db.log will contain something like this:

[Mon, 30 Apr 12 13:47:18 -0500][DEBUG][Resource Id #77] INSERT INTO robots
(name, created_at) VALUES ('Robby the Robot', '1956-07-21')

Profiling SQL Statements
Thanks to Phalcon\Db, the underlying component of Phalcon\Mvc\Model, it’s possible to profile the SQL statements generated by the ORM in order to analyze the performance of database operations. With this you can diagnose performance problems and to discover bottlenecks.

<?php

$di->set('profiler', function(){
    return new \Phalcon\Db\Profiler();
}, true);

$di->set('db', function() use ($di) {

    $eventsManager = new \Phalcon\Events\Manager();

    //Get a shared instance of the DbProfiler
    $profiler = $di->getProfiler();

    //Listen all the database events
    $eventsManager->attach('db', function($event, $connection) use ($profiler) {
        if ($event->getType() == 'beforeQuery') {
            $profiler->startProfile($connection->getSQLStatement());
        }
        if ($event->getType() == 'afterQuery') {
            $profiler->stopProfile();
        }
    });

    $connection = new \Phalcon\Db\Adapter\Pdo\Mysql(array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "invo"
    ));

    //Assign the eventsManager to the db adapter instance
    $connection->setEventsManager($eventsManager);

    return $connection;
});

Profiling some queries:

<?php

// Send some SQL statements to the database
Robots::find();
Robots::find(array("order" => "name"));
Robots::find(array("limit" => 30));

//Get the generated profiles from the profiler
$profiles = $di->get('profiler')->getProfiles();

foreach ($profiles as $profile) {
   echo "SQL Statement: ", $profile->getSQLStatement(), "\n";
   echo "Start Time: ", $profile->getInitialTime(), "\n";
   echo "Final Time: ", $profile->getFinalTime(), "\n";
   echo "Total Elapsed Time: ", $profile->getTotalElapsedSeconds(), "\n";
}

Each generated profile contains the duration in milliseconds that each instruction takes to complete as well as the generated SQL statement.

Injecting services into Models 向模型注入服务

<?php

class Robots extends \Phalcon\Mvc\Model
{

    public function notSave()
    {
        //Obtain the flash service from the DI container
        $flash = $this->getDI()->getFlash();

        //Show validation messages
        foreach ($this->getMessages() as $message) {
            $flash->error($message);
        }
    }

}

The “notSave” event is triggered every time that a “create” or “update” action fails. So we’re flashing the validation messages obtaining the “flash” service from the DI container. By doing this, we don’t have to print messages after each save.

Disabling/Enabling Features
In the ORM we have implemented a mechanism that allow you to enable/disable specific features or options globally on the fly. According to how you use the ORM you can disable that you aren’t using. These options can also be temporarily disabled if required:
禁用模型提供的功能,比如禁止事件,列重命名等:

<?php

\Phalcon\Mvc\Model::setup(array(
    'events' => false,
    'columnRenaming' => false
));
Option Description Default
events Enables/Disables callbacks, hooks and event notifications from all the models true
columnRenaming Enables/Disables the column renaming true
notNullValidations The ORM automatically validate the not null columns present in the mapped table true
virtualForeignKeys Enables/Disables the virtual foreign keys true
phqlLiterals Enables/Disables literals in the PHQL parser true

Stand-Alone component 独立组件
Using Phalcon\Mvc\Model in a stand-alone mode can be demonstrated展示 below:
Phalcon\Mvc\Model可以在独立模式下工作:

<?php

use Phalcon\DI,
    Phalcon\Db\Adapter\Pdo\Sqlite as Connection,
    Phalcon\Mvc\Model\Manager as ModelsManager,
    Phalcon\Mvc\Model\Metadata\Memory as MetaData,
    Phalcon\Mvc\Model;

$di = new DI();

//Setup a connection
$di->set('db', new Connection(array(
    "dbname" => "sample.db"
)));

//Set a models manager
$di->set('modelsManager', new ModelsManager());

//Use the memory meta-data adapter or other
$di->set('modelsMetadata', new MetaData());

//Create a model
class Robots extends Model
{

}

//Use the model
echo Robots::count();

这样这个模型就可以作为一个独立组件来使用了。