月度归档:2013年11月

PHP框架Phalcon 之 PHQL

Phalcon PHQL
PHQL是一个抽象,大概流程:PHQL经过解析器装换成IR并缓存,然后再转换成针对具体的数据库系统的SQL。下次如果要执行相同的PHQL,首先查看缓存,如果缓存存在则直接返回,然后转换成针对具体的数据库系统的SQL。这里的PHQL抽象就是Phalcon提供的统一操作接口。而Phalcon中的模型操作,首先是先转换成PHQL,然后再进一步操作,模型的操作是PHQL之上的抽象,PHQL是具体SQL的抽象,这里的内容是构建PHQL。

涉及的类:
Phalcon Query组件
红色部分是实际可用的类。

首先是Phalcon\Mvc\Model\Query,它实际是具体实现Phalcon\Mvc\Model\QueryInterface接口,由于它内容在解析PHQL时需要访问具体模型,所有它也实现了Phalcon\DI\InjectionAwareInterface接口,这样它依赖的modelsManager就可以被注入了,正因为如此,一般都不会直接new一个Phalcon\Mvc\Model\Query,而是通过modelsManager的createQuery获取实例:

<?php

//Executing a simple query
$query = $this->modelsManager->createQuery("SELECT * FROM Cars");
$cars = $query->execute();

//With bound parameters
$query = $this->modelsManager->createQuery("SELECT * FROM Cars WHERE name = :name:");
$cars = $query->execute(array(
    'name' => 'Audi'
));

//或者干脆直接创建执行
<?php

//Executing a simple query
$cars = $this->modelsManager->executeQuery("SELECT * FROM Cars");

//Executing with bound parameters
$cars = $this->modelsManager->executeQuery("SELECT * FROM Cars WHERE name = :name:", array(
    'name' => 'Audi'
));

Phalcon\Mvc\Model\Query\Builder提供了一种面向对象的构建PHQL的方法,它也实现了Phalcon\DI\InjectionAwareInterface接口,所以一般也不直接new,而是通过modelsManager的createBuilder获取一个实例,然后以面向对象的方式构建PHQL,然后调用getQuery获取Phalcon\Mvc\Model\Query实例,调用它的excute方法执行PHQL:

<?php

//Getting a whole set
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->join('RobotsParts')
    ->orderBy('Robots.name')
    ->getQuery()
    ->execute();

//Getting the first row
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->join('RobotsParts')
    ->orderBy('Robots.name')
    ->getQuery()
    ->getSingleResult();

//类似如下代码(构建PHQL的区别,一个是直接写,一个是面向对象方式写)
$phql = "SELECT Robots.*
    FROM Robots JOIN RobotsParts p
    ORDER BY Robots.name LIMIT 20";
$result = $manager->executeQuery($phql);

很明显,面向对象方式的代码看起来更加直观一点,但是多封装了一次。另外要注意的是,不要和模型的query()方法混淆,它返回\Phalcon\Mvc\Model\Criteria对象可以使用面向对象方法构建查询。

Phalcon\Mvc\Model\Query\Status是对Phalcon\Mvc\Model\Query的执行结果的封装:

<?php

$phql = "UPDATE Robots SET name = :name:, type = :type:, year = :year: WHERE id = :id:";
$status = $app->modelsManager->executeQuery($phql, array(
   'id' => 100,
   'name' => 'Astroy Boy',
   'type' => 'mechanical',
   'year' => 1959
));

 //Check if the update was successful
 if ($status->success() == true) {
   echo 'OK';
 }

Phalcon\Mvc\Model\Query\Status中有一个getMessages()方法,它实际是调用模型中的getMessages()方法,这个方法可以获取PHQL执行异常时的具体信息。

模型中的封装已经提供了足够多的内容,尽管如此,完全基于模型的操作还是有限的,或者有些操作它无法完成,所有SQL(或者这里说的PHQL)还是需要认真掌握的。
—————————————————————
以下是官方文档,添加部分中文,是为学习笔记和备忘。

Phalcon Query Language (PHQL)
Phalcon Query Language, PhalconQL or simply PHQL is a high-level, object-oriented SQL dialect方言 that allows to write queries using a standardized SQL-like language. PHQL is implemented as a parser (written in C) that translates syntax in that of the target RDBMS.
PHQL是SQL抽象,这些抽象SQL通过这个解析器,它可以产生针对具体RDBMS的SQL,看起来是个好东西。

To achieve the highest performance possible, Phalcon provides a parser that uses the same technology as SQLite. This technology provides a small in-memory parser with a very low memory footprint that is also thread-safe.
Phalcon提供了一个和SQLite使用相同的技术的解析器。这个技术提供了一个小型的在内存中的并且占用非常低的内存同时也是线程安全的解析器。

The parser first checks the syntax of the pass PHQL statement, then builds an intermediate representation of the statement and finally it converts it to the respective SQL dialect of the target RDBMS.
这个解析器首先检查传递到PHQL语句的语法,然后构建一个用在内部表现的语句,最后把它转换成针对具体的RDBMS的SQL。

In PHQL, we’ve implemented a set of features to make your access to databases more secure:
实现了一些特征使得可以更加安全访问数据库:
Bound parameters are part of the PHQL language helping you to secure your code
绑定参数是PHQL语言的一部分用来帮助你使代码安全
PHQL only allows one SQL statement to be executed per call preventing injections
PHQL每次调用只允许一个SQL语句执行以阻止注入
PHQL ignores all SQL comments which are often used in SQL injections
PHQL忽略经常用在SQL注入中的所有SQL注释
PHQL only allows data manipulation statements, avoiding altering or dropping tables/databases by mistake or externally without authorization
PHQL只允许数据维护语句
PHQL implements a high-level abstraction allowing you to handle tables as models and fields as class attributes
PHQL实现了一个高层抽象,允许你处理表作为模型,字段作为类属性。

Usage Example 用法
To better explain how PHQL works consider the following example. We have two models “Cars” and “Brands”:

?php

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

    public $name;

    public $brand_id;

    public $price;

    public $year;

    public $style;

    /**
     * This model is mapped to the table sample_cars
     */
    public function getSource()
    {
        return 'sample_cars';
    }

    /**
     * A car only has a Brand, but a Brand have many Cars
     */
    public function initialize()
    {
        $this->belongsTo('brand_id', 'Brands', 'id');
    }
}

And every Car has a Brand, so a Brand has many Cars:

<?php

class Brands extends Phalcon\Mvc\Model
{

    public $id;

    public $name;

    /**
     * The model Brands is mapped to the "sample_brands" table
     */
    public function getSource()
    {
        return 'sample_brands';
    }

    /**
     * A Brand can have many Cars
     */
    public function initialize()
    {
        $this->hasMany('id', 'Cars', 'brand_id');
    }
}

Creating PHQL Queries 创建PHQL查询
PHQL queries can be created just by instantiating the class Phalcon\Mvc\Model\Query:
PHQL查询只需要实例化一个Phalcon\Mvc\Model\Query类:

<?php

// Instantiate the Query
$query = new Phalcon\Mvc\Model\Query("SELECT * FROM Cars", $this->getDI());

// Execute the query returning a result if any
$cars = $query->execute();

(以上直接new,第二参数是DI,资源需要注入)

From a controller or a view, it’s easy to create/execute them using an injected models manager:

<?php

//Executing a simple query
$query = $this->modelsManager->createQuery("SELECT * FROM Cars");
$cars = $query->execute();

//With bound parameters
$query = $this->modelsManager->createQuery("SELECT * FROM Cars WHERE name = :name:");
$cars = $query->execute(array(
    'name' => 'Audi'
));

(这里通过modelsManager获取查询实例,它实现了Phalcon\DI\InjectionAwareInterface接口)

Or simply execute it:

<?php

//Executing a simple query
$cars = $this->modelsManager->executeQuery("SELECT * FROM Cars");

//Executing with bound parameters
$cars = $this->modelsManager->executeQuery("SELECT * FROM Cars WHERE name = :name:", array(
    'name' => 'Audi'
));

Selecting Records 查询
As the familiar SQL, PHQL allows querying of records using the SELECT statement we know, except that instead of specifying tables, we use the models classes:
用模型类构建查询:

<?php

$query = $manager->createQuery("SELECT * FROM Cars ORDER BY Cars.name");
$query = $manager->createQuery("SELECT Cars.name FROM Cars ORDER BY Cars.name");

Classes in namespaces are also allowed:

<?php

$phql = "SELECT * FROM Formula\Cars ORDER BY Formula\Cars.name";
$query = $manager->createQuery($phql);

$phql = "SELECT Formula\Cars.name FROM Formula\Cars ORDER BY Formula\Cars.name";
$query = $manager->createQuery($phql);

$phql = "SELECT c.name FROM Formula\Cars c ORDER BY c.name";
$query = $manager->createQuery($phql);

(使用命名空间时,类名必须是全名)

Most of the SQL standard is supported by PHQL, even nonstandard directives such as LIMIT:
大部分标准SQL在PHQL中都支持,哪怕是非标准指令比如limit:

<?php

$phql   = "SELECT c.name FROM Cars AS c "
   . "WHERE c.brand_id = 21 ORDER BY c.name LIMIT 100";
$query = $manager->createQuery($phql);

–Result Types
Depending on the type of columns we query, the result type will vary. If you retrieve a single whole object, then the object returned is a Phalcon\Mvc\Model\Resultset\Simple. This kind of resultset is a set of complete model objects:
依赖查询列类型,结果类型也会不同。如果检索单个对象,返回Phalcon\Mvc\Model\Resultset\Simple,它是模型对象的集:

<?php

$phql = "SELECT c.* FROM Cars AS c ORDER BY c.name";
$cars = $manager->executeQuery($phql);
foreach ($cars as $car) {
    echo "Name: ", $car->name, "\n";
}

This is exactly the same as:

<?php

$cars = Cars::find(array("order" => "name"));
foreach ($cars as $car) {
    echo "Name: ", $car->name, "\n";
}

(先转换成PHQL,然后转换成具体的SQL)

Complete objects can be modified and re-saved in the database because they represent a complete record of the associated table. There are other types of queries that do not return complete objects, for example:
在数据库中完全对象可以修改然后重写保存因为它代表相关表的完整记录。也有其它查询类型不返回完全对象的:(相关表的记录叫完整对象,如果部分属性没有获取,那就不是所谓的完全对象)

<?php

$phql = "SELECT c.id, c.name FROM Cars AS c ORDER BY c.name";
$cars = $manager->executeQuery($phql);
foreach ($cars as $car) {
    echo "Name: ", $car->name, "\n";
}

We are only requesting some fields in the table, therefore those cannot be considered an entire object, so the returned object is still a resulset of type Phalcon\Mvc\Model\Resultset\Simple. However, each element is a standard object that only contain the two columns that were requested.
只要求了一些表格中的字段,尽管不能认为是整个对象,但返回的对象还是Phalcon\Mvc\Model\Resultset\Simple这种类型的结果集。每个元素是一个标准的只包含两列信息的标准对象。

These values that don’t represent complete objects are what we call scalars. PHQL allows you to query all types of scalars: fields, functions, literals, expressions, etc..:

<?php

$phql = "SELECT CONCAT(c.id, ' ', c.name) AS id_name FROM Cars AS c ORDER BY c.name";
$cars = $manager->executeQuery($phql);
foreach ($cars as $car) {
    echo $car->id_name, "\n";
}

As we can query complete objects or scalars, we can also query both at once:

<?php

$phql   = "SELECT c.price*0.16 AS taxes, c.* FROM Cars AS c ORDER BY c.name";
$result = $manager->executeQuery($phql);

The result in this case is an object Phalcon\Mvc\Model\Resultset\Complex. This allows access to both complete objects and scalars at once:

<?php

foreach ($result as $row) {
    echo "Name: ", $row->cars->name, "\n";
    echo "Price: ", $row->cars->price, "\n";
    echo "Taxes: ", $row->taxes, "\n";
}

Scalars are mapped as properties of each “row”, while complete objects are mapped as properties with the name of its related model.

–Joins 联接
It’s easy to request records from multiple models using PHQL. Most kinds of Joins are supported. As we defined relationships in the models, PHQL adds these conditions automatically:
大部分Joins都支持。当我们在模型中定义关系时,PHQL就会自动添加这些条件:

<?php

$phql  = "SELECT Cars.name AS car_name, Brands.name AS brand_name FROM Cars JOIN Brands";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo $row->car_name, "\n";
    echo $row->brand_name, "\n";
}

(PHQL中并不需要写联接的条件,条件是在每个模型中定义的,那意思是说这里写的PHQL执行前还有被处理一番,另外操蛋的时,如果模型没有定义关系,PHQL写上是否OK呢?)

By default, an INNER JOIN is assumed. You can specify the type of JOIN in the query:
默认假设使用INNER JOIN。你可以在查询中指定JOIN的类型:

<?php

$phql = "SELECT Cars.*, Brands.* FROM Cars INNER JOIN Brands";
$rows = $manager->executeQuery($phql);

$phql = "SELECT Cars.*, Brands.* FROM Cars LEFT JOIN Brands";
$rows = $manager->executeQuery($phql);

$phql = "SELECT Cars.*, Brands.* FROM Cars LEFT OUTER JOIN Brands";
$rows = $manager->executeQuery($phql);

$phql = "SELECT Cars.*, Brands.* FROM Cars CROSS JOIN Brands";
$rows = $manager->executeQuery($phql);

Also is possible set manually the conditions of the JOIN:
也可以手动设置JOIN的条件(那么模型中还需要定义条件吗?)

<?php

$phql = "SELECT c.*, b.* FROM Cars c, Brands b WHERE b.id = c.brands_id";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo "Car: ", $row->c->name, "\n";
    echo "Brand: ", $row->b->name, "\n";
}

When the joined model has a many-to-many relation to the ‘from’ model, the intermediate model is implicitly added to the generated query:
当联接模型有多对多联系到from模型,在产生的查询中中间模型隐式被添加:

<?php

$phql = 'SELECT Brands.name, Songs.name FROM Artists ' .
        'JOIN Songs WHERE Artists.genre = "Trip-Hop"';
$result = $this->modelsManager->query($phql);

This code produces the following SQL in MySQL:

SELECT `brands`.`name`, `songs`.`name` FROM `artists`
INNER JOIN `albums` ON `albums`.`artists_id` = `artists`.`id`
INNER JOIN `songs` ON `albums`.`songs_id` = `songs`.`id`
WHERE `artists`.`genre` = 'Trip-Hop'

–Aggregations
The following examples show how to use aggregations in PHQL:

<?php

// How much are the prices of all the cars?
$phql = "SELECT SUM(price) AS summatory FROM Cars";
$row  = $manager->executeQuery($phql)->getFirst();
echo $row['summatory'];

// How many cars are by each brand?
$phql = "SELECT Cars.brand_id, COUNT(*) FROM Cars GROUP BY Cars.brand_id";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo $row->brand_id, ' ', $row["1"], "\n";
}

// How many cars are by each brand?
$phql = "SELECT Brands.name, COUNT(*) FROM Cars JOIN Brands GROUP BY 1";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo $row->name, ' ', $row["1"], "\n";
}

$phql = "SELECT MAX(price) AS maximum, MIN(price) AS minimum FROM Cars";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo $row["maximum"], ' ', $row["minimum"], "\n";
}

// Count distinct used brands
$phql = "SELECT COUNT(DISTINCT brand_id) AS brandId FROM Cars";
$rows = $manager->executeQuery($phql);
foreach ($rows as $row) {
    echo $row->brandId, "\n";
}

–Conditions
Conditions allow us to filter the set of records we want to query. The WHERE clause allows to do that:

<?php

// Simple conditions
$phql = "SELECT * FROM Cars WHERE Cars.name = 'Lamborghini Espada'";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.price > 10000";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE TRIM(Cars.name) = 'Audi R8'";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.name LIKE 'Ferrari%'";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.name NOT LIKE 'Ferrari%'";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.price IS NULL";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.id IN (120, 121, 122)";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.id NOT IN (430, 431)";
$cars = $manager->executeQuery($phql);

$phql = "SELECT * FROM Cars WHERE Cars.id BETWEEN 1 AND 100";
$cars = $manager->executeQuery($phql);

Also, as part of PHQL, prepared parameters automatically escape the input data, introducing more security:

<?php

$phql = "SELECT * FROM Cars WHERE Cars.name = :name:";
$cars = $manager->executeQuery($phql, array("name" => 'Lamborghini Espada'));

$phql = "SELECT * FROM Cars WHERE Cars.name = ?0";
$cars = $manager->executeQuery($phql, array(0 => 'Lamborghini Espada'));

Inserting Data
With PHQL it’s possible to insert data using the familiar INSERT statement:

<?php

// Inserting without columns
$phql = "INSERT INTO Cars VALUES (NULL, 'Lamborghini Espada', "
      . "7, 10000.00, 1969, 'Grand Tourer')";
$manager->executeQuery($phql);

// Specifying columns to insert
$phql = "INSERT INTO Cars (name, brand_id, year, style) "
      . "VALUES ('Lamborghini Espada', 7, 1969, 'Grand Tourer')";
$manager->executeQuery($phql);

// Inserting using placeholders
$phql = "INSERT INTO Cars (name, brand_id, year, style) "
      . "VALUES (:name:, :brand_id:, :year:, :style)";
$manager->executeQuery($sql,
    array(
        'name'     => 'Lamborghini Espada',
        'brand_id' => 7,
        'year'     => 1969,
        'style'    => 'Grand Tourer',
    )
);

Phalcon doesn’t only transform the PHQL statements into SQL. All events and business rules defined in the model are executed as if we created individual objects manually. Let’s add a business rule on the model cars. A car cannot cost less than $ 10,000:

<?php

use Phalcon\Mvc\Model\Message;

class Cars extends Phalcon\Mvc\Model
{

    public function beforeCreate()
    {
        if ($this->price < 10000)
        {
            $this->appendMessage(new Message("A car cannot cost less than $ 10,000"));
            return false;
        }
    }

}

If we made the following INSERT in the models Cars, the operation will not be successful because the price does not meet the business rule that we implemented:

<?php

$phql   = "INSERT INTO Cars VALUES (NULL, 'Nissan Versa', 7, 9999.00, 2012, 'Sedan')";
$result = $manager->executeQuery($phql);
if ($result->success() == false)
{
    foreach ($result->getMessages() as $message)
    {
        echo $message->getMessage();
    }
}

Updating Data
Updating rows is very similar than inserting rows. As you may know, the instruction to update records is UPDATE. When a record is updated the events related to the update operation will be executed for each row.

<?php

// Updating a single column
$phql = "UPDATE Cars SET price = 15000.00 WHERE id = 101";
$manager->executeQuery($phql);

// Updating multiples columns
$phql = "UPDATE Cars SET price = 15000.00, type = 'Sedan' WHERE id = 101";
$manager->executeQuery($phql);

// Updating multiples rows
$phql = "UPDATE Cars SET price = 7000.00, type = 'Sedan' WHERE brands_id > 5";
$manager->executeQuery($phql);

// Using placeholders
$phql = "UPDATE Cars SET price = ?0, type = ?1 WHERE brands_id > ?2";
$manager->executeQuery($phql, array(
    0 => 7000.00,
    1 => 'Sedan',
    2 => 5
));

An UPDATE statement performs the update in two phases:

First, if the UPDATE has a WHERE clause it retrieves all the objects that match these criteria,如果有WHERE子句,先把所有符合条件的结果返回
Second, based on the queried objects it updates/changes the requested attributes storing them to the relational database然后更新

This way of operation allows that events, virtual foreign keys and validations take part of the updating process. In summary, the following code:
这种方式允许事件、虚拟外键和验证作为更新过程的一部分:

<?php

$phql = "UPDATE Cars SET price = 15000.00 WHERE id > 101";
$success = $manager->executeQuery($phql);

//等价如下代码
<?php

$messages = null;

$process = function() use (&$messages) {
    foreach (Cars::find("id > 101") as $car) {
        $car->price = 15000;
        if ($car->save() == false) {
            $messages = $car->getMessages();
            return false;
        }
    }
    return true;
};

$success = $process();

Deleting Data
When a record is deleted the events related to the delete operation will be executed for each row:

<?php

// Deleting a single row
$phql = "DELETE FROM Cars WHERE id = 101";
$manager->executeQuery($phql);

// Deleting multiple rows
$phql = "DELETE FROM Cars WHERE id > 100";
$manager->executeQuery($phql);

// Using placeholders
$phql = "DELETE FROM Cars WHERE id BETWEEN :initial: AND :final:";
$manager->executeQuery(
    $phql,
    array(
        'initial' => 1,
        'final' => 100
    )
);

DELETE operations are also executed in two phases like UPDATEs.
删除操作和更新操作一样也执行两不解析。

Creating queries using the Query Builder
A builder is available to create PHQL queries without the need to write PHQL statements, also providing IDE facilities:

<?php

//Getting a whole set
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->join('RobotsParts')
    ->orderBy('Robots.name')
    ->getQuery()
    ->execute();

//Getting the first row
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->join('RobotsParts')
    ->orderBy('Robots.name')
    ->getQuery()
    ->getSingleResult();

//类似
<?php

$phql = "SELECT Robots.*
    FROM Robots JOIN RobotsParts p
    ORDER BY Robots.name LIMIT 20";
$result = $manager->executeQuery($phql);

More examples of the builder:

<?php

// 'SELECT Robots.* FROM Robots';
$builder->from('Robots');

// 'SELECT Robots.*, RobotsParts.* FROM Robots, RobotsParts';
$builder->from(array('Robots', 'RobotsParts'));

// 'SELECT * FROM Robots';
$phql = $builder->columns('*')
                ->from('Robots');

// 'SELECT id FROM Robots';
$builder->columns('id')
        ->from('Robots');

// 'SELECT id, name FROM Robots';
$builder->columns(array('id', 'name'))
        ->from('Robots');

// 'SELECT Robots.* FROM Robots WHERE Robots.name = "Voltron"';
$builder->from('Robots')
        ->where('Robots.name = "Voltron"');

// 'SELECT Robots.* FROM Robots WHERE Robots.id = 100';
$builder->from('Robots')
        ->where(100);

// 'SELECT Robots.* FROM Robots WHERE Robots.type = "virtual" AND Robots.id > 50';
$builder->from('Robots')
        ->where('type = "virtual"')
        ->andWhere('id > 50');

// 'SELECT Robots.* FROM Robots WHERE Robots.type = "virtual" OR Robots.id > 50';
$builder->from('Robots')
        ->where('type = "virtual"')
        ->orWhere('id > 50');

// 'SELECT Robots.* FROM Robots GROUP BY Robots.name';
$builder->from('Robots')
        ->groupBy('Robots.name');

// 'SELECT Robots.* FROM Robots GROUP BY Robots.name, Robots.id';
$builder->from('Robots')
        ->groupBy(array('Robots.name', 'Robots.id'));

// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name';
$builder->columns(array('Robots.name', 'SUM(Robots.price)'))
    ->from('Robots')
    ->groupBy('Robots.name');

// 'SELECT Robots.name, SUM(Robots.price) FROM Robots GROUP BY Robots.name HAVING SUM(Robots.price) > 1000';
$builder->columns(array('Robots.name', 'SUM(Robots.price)'))
    ->from('Robots')
    ->groupBy('Robots.name')
    ->having('SUM(Robots.price) > 1000');

// 'SELECT Robots.* FROM Robots JOIN RobotsParts';
$builder->from('Robots')
    ->join('RobotsParts');

// 'SELECT Robots.* FROM Robots JOIN RobotsParts AS p';
$builder->from('Robots')
    ->join('RobotsParts', null, 'p');

// 'SELECT Robots.* FROM Robots JOIN RobotsParts ON Robots.id = RobotsParts.robots_id AS p';
$builder->from('Robots')
    ->join('RobotsParts', 'Robots.id = RobotsParts.robots_id', 'p');

// 'SELECT Robots.* FROM Robots ;
// JOIN RobotsParts ON Robots.id = RobotsParts.robots_id AS p ;
// JOIN Parts ON Parts.id = RobotsParts.parts_id AS t';
$builder->from('Robots')
    ->join('RobotsParts', 'Robots.id = RobotsParts.robots_id', 'p')
    ->join('Parts', 'Parts.id = RobotsParts.parts_id', 't');

// 'SELECT r.* FROM Robots AS r';
$builder->addFrom('Robots', 'r');

// 'SELECT Robots.*, p.* FROM Robots, Parts AS p';
$builder->from('Robots')
    ->addFrom('Parts', 'p');

// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p';
$builder->from(array('r' => 'Robots'))
        ->addFrom('Parts', 'p');

// 'SELECT r.*, p.* FROM Robots AS r, Parts AS p';
$builder->from(array('r' => 'Robots', 'p' => 'Parts'));

// 'SELECT Robots.* FROM Robots LIMIT 10';
$builder->from('Robots')
    ->limit(10);

// 'SELECT Robots.* FROM Robots LIMIT 10 OFFSET 5';
$builder->from('Robots')
        ->limit(10, 5);

// 'SELECT Robots.* FROM Robots WHERE id BETWEEN 1 AND 100';
$builder->from('Robots')
        ->betweenWhere('id', 1, 100);

// 'SELECT Robots.* FROM Robots WHERE id IN (1, 2, 3)';
$builder->from('Robots')
        ->inWhere('id', array(1, 2, 3));

// 'SELECT Robots.* FROM Robots WHERE id NOT IN (1, 2, 3)';
$builder->from('Robots')
        ->notInWhere('id', array(1, 2, 3));

// 'SELECT Robots.* FROM Robots WHERE name LIKE '%Art%';
$builder->from('Robots')
        ->where('name LIKE :name:', array('name' => '%' . $name . '%'));

// 'SELECT r.* FROM Store\Robots WHERE r.name LIKE '%Art%';
$builder->from(['r' => 'Store\Robots'])
        ->where('r.name LIKE :name:', array('name' => '%' . $name . '%'));

–Bound Parameters
Bound parameters in the query builder can be set as the query is constructed or past all at once when executing:

<?php

//Passing parameters in the query construction
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->where('name = :name:', array('name' => $name))
    ->andWhere('type = :type:', array('type' => $type))
    ->getQuery()
    ->execute();

//Passing parameters in query execution
$robots = $this->modelsManager->createBuilder()
    ->from('Robots')
    ->where('name = :name:')
    ->andWhere('type = :type:')
    ->getQuery()
    ->execute(array('name' => $name, 'type' => $type));

Disallow literals in PHQL
Literals can be disabled in PHQL, this means that directly using strings, numbers and boolean values in PHQL strings will be disallowed. If PHQL statements are created embedding external data on them, this could open the application to potential SQL injections:

<?php

$login = 'voltron';
$phql = "SELECT * FROM Models\Users WHERE login = '$login'";
$result = $manager->executeQuery($phql);

//If $login is changed to ‘ OR ‘’ = ‘, the produced PHQL is:
<?php

"SELECT * FROM Models\Users WHERE login = '' OR '' = ''"

Which is always true no matter what the login stored in the database is.

If literals are disallowed strings can be used as part of a PHQL statement, thus an exception will be thrown forcing the developer to use bound parameters. The same query can be written in a secure way like this:
literals禁用后强制开发组使用绑定参数:

<?php

$phql = "SELECT Robots.* FROM Robots WHERE Robots.name = :name:";
$result = $manager->executeQuery($phql, array('name' => $name));

You can disallow literals in the following way:

<?php

Phalcon\Mvc\Model::setup(array('phqlLiterals' => false));

Bound parameters can be used even if literals are allowed or not. Disallowing them is just another security decision a developer could take in web applications.

Escaping Reserved Words 保留字
PHQL has a few reserved words, if you want to use any of them as attributes or models names, you need to escape those words using the cross-database escaping delimiters ‘[‘ and ‘]’:

<?php

$phql = "SELECT * FROM [Update]";
$result = $manager->executeQuery($phql);

$phql = "SELECT id, [Like] FROM Posts";
$result = $manager->executeQuery($phql);

The delimiters are dynamically translated to valid delimiters depending on the database system where the application is currently running on.

PHQL Lifecycle PHQL生存周期
Being a high-level language, PHQL gives developers the ability to personalize and customize different aspects in order to suit their needs. The following is the life cycle of each PHQL statement executed:

The PHQL is parsed and converted into an Intermediate Representation (IR) which is independent of the SQL implemented by database system
PHQL被解析转换中间表现,它独立于数据库实现的SQL
The IR is converted to valid SQL according to the database system associated to the model
IR转换到有效的SQL
PHQL statements are parsed once and cached in memory. Further executions of the same statement result in a slightly faster execution
PHQL语句一旦被解析就会在内存中缓存。

Using Raw SQL 使用原生SQL
A database system could offer specific SQL extensions that aren’t supported by PHQL, in this case, a raw SQL can be appropriate:
PHQL不支持的数据库系统提供特殊的SQL扩展,这种情况,原生SQL是比较合适的:

<?php

use Phalcon\Mvc\Model\Resultset\Simple as Resultset;

class Robots extends Phalcon\Mvc\Model
{
    public static function findByCreateInterval()
    {
        // A raw SQL statement
        $sql = "SELECT * FROM robots WHERE id > 0";

        // Base model
        $robot = new Robots();

        // Execute the query
        return new Resultset(null, $robot, $robot->getReadConnection()->query($sql));
    }
}

(直接获取适配器,调用它的query执行原生SQL)
If Raw SQL queries are common in your application a generic method could be added to your model:

<?php

use Phalcon\Mvc\Model\Resultset\Simple as Resultset;

class Robots extends Phalcon\Mvc\Model
{
    public static function findByRawSql($conditions, $params=null)
    {
        // A raw SQL statement
        $sql = "SELECT * FROM robots WHERE $conditions";

        // Base model
        $robot = new Robots();

        // Execute the query
        return new Resultset(null, $robot, $robot->getReadConnection()->query($sql, $params));
    }
}

The above findByRawSql could be used as follows:

<?php

$robots = Robots::findByRawSql('id > ?', array(10));

Troubleshooting
Some things to keep in mind when using PHQL:
在使用PHQL时,请记住:
Classes are case-sensitive, if a class is not defined with the same name as it was created this could lead to an unexpected behavior in operating systems with case-sensitive file systems such as Linux.
类是大小写敏感的,否则抛出异常。
Correct charset must be defined in the connection to bind parameters with success
正确设置连接的字符集确保参数绑定成功
Aliased classes aren’t replaced by full namespaced classes since this only occurs in PHP code and not inside strings
类别名不能被类全名替代?
If column renaming is enabled avoid using column aliases with the same name as columns to be renamed, this may confuse the query resolver

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();

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

PHP框架Phalcon 之 使用控制器

Phalcon控制器
所有从Phalcon\DI\Injectable继承的类,都有一个魔术方法__get,通过它把注入的资源映射为对象的属性,比如可以在控制器中直接如下访问:

$this->dispatcher->getControllerName();
##
$this->getDI()->get(“dispatcher”)->getControllerName();

这里的直接对dispatcher的获取实际就是__get方法实现的,否则就要通过$this->getDI()->get(“dispatcher”)获取,显然,这个封装会必要直观。

从以上对象接口图来看,在Phalcon中的控制器的实现是非常简单的,如果不是要DI注入依赖和使用事件管理器,它就跟一般的类没有什么区别了。

为了方便DI中的资源依赖注入控制器,我们自定义的控制器都应该继承这个Phalcon\Mvc\Controller抽象类。

Phalcon\Mvc\ControllerInterface中未见有定义任何接口,但是文档说明中提到如要要在构建控制器时进行初始化,不建议在重写构造函数,推荐的方法是实现onContruct方法,这个方法没有发现在控制器的层次中:

<?php

class IndexController extends \Phalcon\Mvc\Controller
{
    public function testAction(){
        $c = new ReflectionClass($this);
        print_r($c->getMethods());
    }
}


//输出
Array
(
    [0] => ReflectionMethod Object
        (
            [name] => testAction
            [class] => IndexController
        )

    [1] => ReflectionMethod Object
        (
            [name] => __construct
            [class] => Phalcon\Mvc\Controller
        )

    [2] => ReflectionMethod Object
        (
            [name] => setDI
            [class] => Phalcon\DI\Injectable
        )

    [3] => ReflectionMethod Object
        (
            [name] => getDI
            [class] => Phalcon\DI\Injectable
        )

    [4] => ReflectionMethod Object
        (
            [name] => setEventsManager
            [class] => Phalcon\DI\Injectable
        )

    [5] => ReflectionMethod Object
        (
            [name] => getEventsManager
            [class] => Phalcon\DI\Injectable
        )

    [6] => ReflectionMethod Object
        (
            [name] => __get
            [class] => Phalcon\DI\Injectable
        )

从输出情况来看,并未发现onContruct方法,按理论上来说,如果要在对象创建时执行某个方法,那么只需要在构造函数中调用一下,那么子类只要实现这个方法就可以实现方法调用,不过这里从结果来看,看起来不是怎么干的。大概是在控制器对象构建之后,马上就调用它的onContruce方法(当然要判断方法是否存在)。

文档中,还提到了要在Action方法前进行初始化操作,可以实现initialize方法,这个是一个事件触发的方法,在它之前控制器对象已经构建了。它跟onContruct不一样的地方首先是执行的时间点不一样,然后是Action方法执行前触发。

————————————————————
以下是关于使用控制器的官方文档,添加部分中文和注释,是为学习笔记与备忘。

Using Controllers
使用控制器

The controllers provide a number of methods that are called actions. Actions are methods on a controller that handle requests. By default all public methods on a controller map to actions and are accessible by an URL. Actions are responsible for interpreting the request and creating the response. Usually responses are in the form of a rendered view, but there are other ways to create responses as well. For instance, when you access an URL like this: http://localhost/blog/posts/show/2012/the-post-title Phalcon by default will decompose each part like this:
控制器提供的方法叫Action。控制器中所有公共方法可以通过URL访问。Action负责翻译请求和创建响应。

In this case, the PostsController will handle this request. There is no a special location to put controllers in an application, they could be loaded using autoloaders, so you’re free to organize your controllers as you need. Controllers must have the suffix “Controller” while actions the suffix “Action”. A sample of a controller is as follows:
在应用中控制器存放的目录没有限制(只要在autoloaders中注册目录),控制器必须以Controller结尾,行为必须以Action结尾:

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function showAction($year, $postTitle)
	{
	}
}

Additional URI parameters are defined as action parameters, so that they can be easily accessed using local variables.A controller can optionally extend Phalcon\Mvc\Controller. By doing this, the controller can have easy access to the application services.Parameters without a default value are handled as required. Setting optional values for parameters is done as usual in PHP:
URL的附加参数被定义为action的参数。控制器继承Phalcon\Mvc\Controller是可选的。如果继承它,控制器可以访问应用的服务(继承了Phalcon\DI\Injectable)。注意:同时控制器也是分发器的事件监听器(有三个事件的触发可以被控制器监听,分别是beforExecuteRoute initialize afterExecuteRoute)。Action参数如果没有默认值表示需要提供值(在请求的URL中体现)

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function showAction($year=2012, $postTitle=’some default title’)
	{
	}
}

///////////////////////////
//测试例子
class IndexController extends \Phalcon\Mvc\Controller{
	//访问http://xxx/index 没有指定了默认值
	//输出 警告信息 方法参数缺失 
	public function testAction($id,$name){
		echo "the param \$id is {$id} and \$name is {$name}\n";
              	return false;
   	}
	//访问http://xxx/index 指定了默认值 默认值被使用
	//输出the param $id is 1000 and $name is vfeelit 
	public function testAction($id=1000,$name="vfeelit"){
		echo "the param \$id is {$id} and \$name is {$name}\n";
              	return false;
   	}
}

Parameters are assigned in the same order as they were passed in the route. You can get an arbitrary parameter from its name in the following way:
参数顺序跟路由中的一样,在Action中也可以通过其它方式获取:

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function showAction()
	{
		$year = $this->dispatcher->getParam('year’);
		$postTitle = $this->dispatcher->getParam('postTitle’);
	}
}

(注意,以上的程序让人困惑,showAction()中没有指定参数,而getParam中通过名称索引获取值,而这里没有说明如何通过设置路由来使得可以通过名称索引获取值,URL类似xxx/year/2015/posttitle/thinking-in-phalcon,当前默认是xxx/2015/thinking-in-phalcon,所以应该使用getParam(0)来获取2015,使用getParam(1)来获取thinking-in-phalcon)

Dispatch Loop 分发循环
The dispatch loop will be executed within the Dispatcher until there are no actions left to be executed. In the above example only one action was executed. Now we’ll see how “forward” can provide a more complex flow of operation in the dispatch loop, by forwarding execution to a different controller/action.
当没有action需要执行时,分发循环执行结束。可用forward把请求带入到另一个控制器。

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function showAction($year, $postTitle)
	{
		$this->flash->error("You don’t have permission to access this area");
		// Forward flow to another action
		$this->dispatcher->forward(array(
			"controller" => "users",
			"action" => "signin"
		));
	}
}

(注意,$this->dispatcher可写成$this->getDI()->get(“dispatcher”))

If users don’t have permissions to access a certain action then will be forwarded to the Users controller, signin action.
当用没有权限访问特定的Action时,把它forward到用户登录。这个和重定向的区别是不用再次发起请求。

<?php
class UsersController extends \Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function signinAction()
	{
	}
}

There is no limit on the “forwards” you can have in your application, so long as they do not result in circular references, at which point your application will halt. If there are no other actions to be dispatched by the dispatch loop, the dispatcher will automatically invoke the view layer of the MVC that is managed by Phalcon\Mvc\View.
只要没有造成循环引用就没有forwards次数限制。如果在分发循环中没有其它Action,分发器自动调用视图层。

Initializing Controllers 初始化控制器
Phalcon\Mvc\Controller offers the initialize method, which is executed first, before any action is executed on a controller.The use of the “__construct” method is not recommended.
在控制器的所有Action被执行之前,它的inititial方法先被执行,不推荐使用构造函数。

Method ‘initialize’ is only called if the event ‘beforeExecuteRoute’ is executed with success. This avoid that application logic in the initializer cannot be executed without authorization. If you want to execute some initialization logic just after build the controller object you can implement the method‘onConstruct’:
initialize方法只有beforeExecuteRoute事件成功执行后才执行(需要知道的细节:beforeExecuteRoute事件触发这个点,分发器已经生成了具体的控制器并且已经定位了Action[方法存在],然后执行beforeExecuteRoute事件绑定的事件,它执行完毕后[成功,返回FALSE表示失败],接着分发器触发initialize事件导致控制器的initialize方法被执行,然后才执行具体的Action方法,所以,onConstruct在构造控制器时就被执行了,所以先于initialize方法

———————
用以下方法验证:

class IndexController extends \Phalcon\Mvc\Controller
{

        public function initialize(){
                echo "i am in initialize.\n";
        }
        public function onConstruct(){
                echo "i am in onConstruct()\n";
        }
        public function testAction(){
                return false;
        }
	//监听方法特殊,只有一个参数,一般第一参数是事件对象
        public function beforeExecuteRoute($dp){
                echo "i am in beforeExecuteRoute\n";
                //return false;
        }
}

———————

Injecting Services 注入服务(参考DI部分)
Request and Response 请求和响应
Assuming that the framework provides a set of pre-registered services. We explain how to interact with the HTTP environment. The “request” service contains an instance of Phalcon\Http\Request and the “response” contains a Phalcon\Http\Response representing what is going to be sent back to the client.

<?php
class PostsController extends Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function saveAction()
	{
		// Check if request has made with POST
		if ($this->request->isPost() == true) {
			// Access POST data
			$customerName = $this->request->getPost("name");
			$customerBorn = $this->request->getPost("born");
		}
	}
}

The response object is not usually used directly, but is built up before the execution of the action, sometimes – like in an afterDispatch event – it can be useful to access the response directly:
响应对象通常不直接使用,但是它在执行一个方法前会被构建起来,比如在afterDispatch事件中,直接访问响应对象比较有用(afterDispatch事件触发后Action肯定被调用,所以reponse肯定建立了)

<?php
class PostsController extends Phalcon\Mvc\Controller
{
	public function indexAction()
	{
	}
	public function notFoundAction()
	{
		// Send a HTTP 404 response header
		$this->response->setStatusCode(404, "Not Found");
	}
}

Session Data 会话数据
Sessions help us maintain persistent data between requests. You could access a Phalcon\Session\Bag from any controller to encapsulate data that need to be persistent.

<?php
class UserController extends Phalcon\Mvc\Controller
{
	public function indexAction()
	{
		$this->persistent->name = "Michael";
	}
	public function welcomeAction()
	{
		echo "Welcome, ", $this->persistent->name;
	}
}

Phalcon\Session\Bag用来把$_SESSION数据进行分组。这里的persistent应该是一个Phalcon\Session\Bag实例(这部分内容在关于会话的章节详术了)。

Using Services as Controllers

Creating a Base Controller

Events in Controllers 控制器中的事件
Controllers automatically act as listeners for dispatcher events, implementing methods with those event names allow you to implement hook points before/after the actions are executed:
控制器自动作为dispatcher事件监听器(先理解事件管理器)。注意:实际只能针对beforExecuteRoute和和initialize和afterExecuteRoute事件绑定方法。一般监听方法是两个参数,第一个是事件对象,第二个是事件触发对象,但是这三个方法看起来比较特殊,只有触发对象,initialize可以没有参数。

<?php
class PostsController extends \Phalcon\Mvc\Controller
{
	public function beforeExecuteRoute($dispatcher)
	{
		// This is executed before every found action
		if ($dispatcher->getActionName() == ’save’) {
			$this->flash->error("You don’t have permission to save posts");
			$this->dispatcher->forward(array(
				’controller’ => ’home’,
				’action’ => ’index’
			));
			return false;
		}
	}
	public function afterExecuteRoute($dispatcher)
	{
		// Executed after every found action
	}
}

PHP框架Phalcon 之 事件管理器

以下内容来自PHP框架Phalcon官方文档关于事件管理器的章节,添加部分中文注释和个人的理解,是为备忘。

Events Manager
事件管理器

The purpose of this component is to intercept the execution of most of the components of the framework by creating “hooks point”. These hook points allow the developer to obtain status information, manipulate data or change the flow of execution during the process of a component.
这个组件的目的是通过创建挂钩去拦截框架的大部分的组件的执行。挂钩允许开发者在组件的处理过程中获取状态信息,维护数据或改变执行流程。

Usage Example
用法实例

In the following example, we use the EventsManager to listen for events produced in a MySQL connection managed by Phalcon\Db. First, we need a listener object to do this. We created a class whose methods are the events we want to listen:
在以下例子中,我们使用EventsManager监听由Phalcon\Db管理的MySQL链接中产生的事件。首先我们需要一个监听者对象。我们创建一个类,它的方法就是我们想要监听的的事件(注:就是方法名和事件名一样,这里的实现跟一般看到的事件模式实现有点不一样,当触发这个事件时,如果绑定了一个对象,就取对象中跟事件同名的方法去执行)

<?php

class MyDbListener
{

    public function afterConnect()
    {

    }

    public function beforeQuery()
    {

    }

    public function afterQuery()
    {

    }

}

This new class can be as verbose as we need it to. The EventsManager will interface between the component and our listener class, offering hook points based on the methods we defined in our listener class:
EventsManager将是组件和我们的监听者类之间的接口,提供的挂钩点是基于我们的监听类中定义的方法(这里实际是组件中触发的事件,要和监听器中的方法对应,除非直接绑定函数):

<?php

use Phalcon\Events\Manager as EventsManager,
    Phalcon\Db\Adapter\Pdo\Mysql as DbAdapter;

$eventsManager = new EventsManager();

//Create a database listener
$dbListener = new MyDbListener();

//Listen all the database events
$eventsManager->attach('db', $dbListener);

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

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

//Send a SQL command to the database server
$connection->query("SELECT * FROM products p WHERE p.status = 1");

(到这里让人产生一个困惑,把$eventsManager传递到了具体的对象,$eventsManager也捆绑了一个叫db的监听器,但是事件是如何触发的呢?实际上,Phalcon\Db\Adapter\Pdo\Mysql类的query方法内部是先$eventsManager->fire(“db:beforeQuery”,$this),然后再执行查询,之后是$eventsManager->fire(“db:afterQuery”,$this),那么绑定到db的监听器自然要对应有beforeQuery方法和afterQuery方法,也可以绑定到一个匿名函数,这个时候需要通过$event->getType()来获取事件名,然后执行对应方法)

In order to log all the SQL statements executed by our application, we need to use the event “afterQuery”. The first parameter passed to the event listener contains contextual information about the event that is running, the second is the connection itself.
为了记录SQL语句的执行,我们需要使用afterQuery事件。传递到事件监听器的第一个参数包含正在运行的事件的上下文信息(事件对象,包含了相关信息),第二个是链接对象自己。

<?php

use Phalcon\Logger\Adapter\File as Logger;

class MyDbListener
{

    protected $_logger;

    public function __construct()
    {
        $this->_logger = new Logger("../apps/logs/db.log");
    }

    public function afterQuery($event, $connection)
    {
        $this->_logger->log($connection->getSQLStatement(), \Phalcon\Logger::INFO);
    }

}

As part of this example, we will also implement the Phalcon\Db\Profiler to detect the SQL statements that are taking longer to execute than expected:
作为例子的一部分,我们也实现Phalcon\Db\Profiler去检测SQL语句是否比预期执行更长。

<?php

use Phalcon\Db\Profiler,
    Phalcon\Logger,
    Phalcon\Logger\Adapter\File;

class MyDbListener
{

    protected $_profiler;

    protected $_logger;

    /**
     * Creates the profiler and starts the logging
     */
    public function __construct()
    {
        $this->_profiler = new Profiler();
        $this->_logger = new Logger("../apps/logs/db.log");
    }

    /**
     * This is executed if the event triggered is 'beforeQuery'
     */
    public function beforeQuery($event, $connection)
    {
        $this->_profiler->startProfile($connection->getSQLStatement());
    }

    /**
     * This is executed if the event triggered is 'afterQuery'
     */
    public function afterQuery($event, $connection)
    {
        $this->_logger->log($connection->getSQLStatement(), Logger::INFO);
        $this->_profiler->stopProfile();
    }

    public function getProfiler()
    {
        return $this->_profiler;
    }

}

The resulting profile data can be obtained from the listener:

<?php

//Send a SQL command to the database server
$connection->execute("SELECT * FROM products p WHERE p.status = 1");

foreach ($dbListener->getProfiler()->getProfiles() 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";
}

In a similar manner we can register an lambda function to perform the task instead of a separate listener class (as seen above):

<?php

//Listen all the database events
$eventManager->attach('db', function($event, $connection) {
    if ($event->getType() == 'afterQuery') {
        echo $connection->getSQLStatement();
    }
});

(这个搞法导致所有事件触发时都执行这个匿名函数,一般如果绑定的是一个对象,则去执行和事件同名的方法)

—-整理以上的例子,写成完成程序,修改错误:

<?php
class MyDbListener{
    protected $_profiler;
    protected $_logger;

    /**
     * Creates the profiler and starts the logging
     */
    public function __construct(){
        echo "In MyDbListener Construct.\n";
        $this->_profiler = new Phalcon\Db\Profiler();
        $this->_logger = new Phalcon\Logger\Adapter\File("../app/logs/db.log");
    }

    /**
     * This is executed if the event triggered is 'beforeQuery'
     */
    public function beforeQuery($event, $connection){
        echo "In MyDbListener beforeQuery meoth. {$event->getType()}\n";
        $this->_profiler->startProfile($connection->getSQLStatement());
    }
    
    /**
     * This is executed if the event triggered is 'afterQuery'
     */
    public function afterQuery($event, $connection){
        echo "In MyDbListener afterQuery meoth. {$event->getType()}\n";
        $this->_logger->log($connection->getSQLStatement(), Phalcon\Logger::INFO);
        $this->_profiler->stopProfile();
    }
    public function getProfiler(){
        return $this->_profiler;
    }   
    public function afterQuery($event, $connection){
        echo "In MyDbListener afterQuery meoth. {$event->getType()}\n";
        $this->_logger->log($connection->getSQLStatement(), Phalcon\Logger::INFO);
        $this->_profiler->stopProfile();
    }
    public function getProfiler(){
        return $this->_profiler;
    }
}

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

//Create a database listener
$dbListener = new \MyDbListener();

//Listen all the database events
$eventsManager->attach('db', $dbListener);

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

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

//Send a SQL command to the database server
$connection->query("SELECT * FROM ai_order WHERE id = 1000");

可以想象一下,在query内部肯定是先fire事件db:beforeQuery然后是db:afterQuery。

Creating components that trigger Events
创建触发事件的组件
You can create components in your application that trigger events to an EventsManager. As a consequence, there may exist listeners that react to these events when generated. In the following example we’re creating a component called “MyComponent”. This component is EventsManager aware; when its method “someTask” is executed it triggers two events to any listener in the EventsManager:
你可以在你的应用中创建组件触发事件到EventsManager。因此,可能存在监听器响应这些事件。以下例子创建一个叫MyComponent的组件。这个组件能意识到EventsManager;当它的方法someTask被执行时,它触发了两个事件到EventsManager中的所有监听器:

<?php

use Phalcon\Events\EventsAwareInterface;

class MyComponent implements EventsAwareInterface
{

    protected $_eventsManager;

    public function setEventsManager($eventsManager)
    {
        $this->_eventsManager = $eventsManager;
    }

    public function getEventsManager()
    {
        return $this->_eventsManager;
    }

    public function someTask()
    {
        $this->_eventsManager->fire("my-component:beforeSomeTask", $this);

        // do some task

        $this->_eventsManager->fire("my-component:afterSomeTask", $this);
    }

}

(EventsAwareInterface接口包含setEventsManager()和getEventsManager()两个方法,实现这两个方法实际是内部可以使用事件管理器,这样就可以调用它的fire方法,然后只要能获取到事件管理器实例,在什么地方都是可以fire的)

Note that events produced by this component are prefixed with “my-component”. This is a unique word that helps us identify events that are generated from certain component. You can even generate events outside the component with the same name. Now let’s create a listener to this component:
注意这个组件产生的事件以my-component为前缀。这是一个根据特定组件产生唯一单词可以帮助我们辨认事件 。你甚至可以在组件外使用相同的名字产生事件。现在让我们创建一个监听器到这个组件:

<?php

class SomeListener
{

    public function beforeSomeTask($event, $myComponent)
    {
        echo "Here, beforeSomeTask\n";
    }

    public function afterSomeTask($event, $myComponent)
    {
        echo "Here, afterSomeTask\n";
    }

}

A listener is simply a class that implements any of all the events triggered by the component. Now let’s make everything work together:
一个监听器是一个简单的类,它是实现了通过组件触发的所有事件:

<?php

//Create an Events Manager
$eventsManager = new Phalcon\Events\Manager();

//Create the MyComponent instance
$myComponent = new MyComponent();

//Bind the eventsManager to the instance
$myComponent->setEventsManager($eventsManager);

//Attach the listener to the EventsManager
$eventsManager->attach('my-component', new SomeListener());

//Execute methods in the component
$myComponent->someTask();

As “someTask” is executed, the two methods in the listener will be executed, producing the following output:

Here, beforeSomeTask
Here, afterSomeTask

Additional data may also passed when triggering an event using the third parameter of “fire”:
附加数据可以使用fire函数的第三参数传递:

<?php
//$this是触发事件的组件,来源
$eventsManager->fire("my-component:afterSomeTask", $this, $extraData);

In a listener the third parameter also receives this data:

<?php

//Receiving the data in the third parameter
$eventManager->attach('my-component', function($event, $component, $data) {
    print_r($data);
});

//Receiving the data from the event context
$eventManager->attach('my-component', function($event, $component) {
    print_r($event->getData());
});

If a listener it is only interested in listening a specific type of event you can attach a listener directly:

<?php

//The handler will only be executed if the event triggered is "beforeSomeTask"
$eventManager->attach('my-component:beforeSomeTask', function($event, $component) {
    //...
});

Event Propagation/Cancellation
事件传播/取消
Many listeners may be added to the same event manager, this means that for the same type of event many listeners can be notified. The listeners are notified in the order they were registered in the EventsManager. Some events are cancelable, indicating that these may be stopped preventing other listeners are notified about the event:
同一个事件管理器可以添加很多监听器,这意味着相同的类型的事件可以得到通知。监听器以在EventsManager中注册的顺序来得到通知。一些事件可以被取消,标志着将阻止其他监听被通知:

<?php

$eventsManager->attach('db', function($event, $connection){

    //We stop the event if it is cancelable
    if ($event->isCancelable()) {
        //Stop the event, so other listeners will not be notified about this
        $event->stop();
    }

    //...

});

By default events are cancelable, even most of events produced by the framework are cancelables. You can fire a not-cancelable event by passing “false” in the fourth parameter of fire:

<?php

$eventsManager->fire("my-component:afterSomeTask", $this, $extraData, false);

Listener Priorities
监听器优先级
When attaching listeners you can set a specific priority. With this feature you can attach listeners indicating the order in which they must be called:

<?php

$evManager->enablePriorities(true);

$evManager->attach('db', new DbListener(), 150); //More priority
$evManager->attach('db', new DbListener(), 100); //Normal priority
$evManager->attach('db', new DbListener(), 50); //Less priority

Collecting Responses
收集响应
The events manager can collect every response returned by every notified listener, this example explains how it works:
事件管理器可以收集每个被通知的监听的响应:

<?php

use Phalcon\Events\Manager as EventsManager;

$evManager = new EventsManager();

//Set up the events manager to collect responses
$evManager->collectResponses(true);

//Attach a listener
$evManager->attach('custom:custom', function() {
    return 'first response';
});

//Attach a listener
$evManager->attach('custom:custom', function() {
    return 'second response';
});

//Fire the event
$evManager->fire('custom:custom', null);

//Get all the collected responses
print_r($evManager->getResponses());

//输出
Array ( [0] => first response [1] => second response )

最后总结一下:
1)Phalcon中跟事件管理器实现有关的一共有如下类:
Phalcon\Events\Event
代表事件,当事件触发时,一个具体的事件对象就会传递到监听器
Phalcon\Events\Exception
Phalcon\Events\Manager
实现Phalcon\Events\ManagerInterface接口,核心方法就是attach方法和fire方法,分别对应为事件绑定监听器 和 触发事件。

2)接口:
Phalcon\Events\EventsAwareInterface
组件如果要触发事件,需要实现这个接口
Phalcon\Events\ManagerInterface
实现事件管理器需要具备的方法

关于Phalcon提供的一些事件,可以参考官方文档:
http://docs.phalconphp.com/en/latest/reference/applications.html#application-events(application事件)
http://docs.phalconphp.com/en/latest/reference/dispatching.html#dispatch-loop-events(分发事件)
http://docs.phalconphp.com/en/latest/reference/views.html#view-events(视图事件)
http://docs.phalconphp.com/en/latest/reference/controllers.html#events-in-controllers(控制器默认作为dispatcher的监听器)

其它方式实现的事件管理的方法:
http://blog.ifeeline.com/206.html
http://blog.ifeeline.com/465.html

PHP框架Phalcon 之 依赖注入

Phalcon DI组件
Phalcon\DiInterface是DI组件要实现的基本方法,比如get、set、getShared、setService、setService等,Phalcon\DI就是一个通用的DI,Phalcon\DI\FactoryDefault继承通用的DI,但是它自动注册了全部框架提供的服务,Phalcon的MVC一般使用它。Phalcon\DI\FactoryDefault注册的服务可以参考以下内容中的Factory Default DI一节。

Phalcon DI组件
如果实现了Phalcon\DI\InjectionAwareInterface,那么就是可以通过setDI和getDI设置和获取DI。实现Phalcon\Events\EventsAwareInterface说明可以触发事件。Phalcon\DI\Injectable接口实现了以上两个接口,那么任何组件,如果要使用DI中注册的实例(意思就是可注入的),都需要实现这个接口。Phalcon中很多组件都实现了这个接口,这是降低组件耦合的有效办法。注意:Phalcon\DI\Injectable还实现__get()魔术方法,通过它可以实现在当前对象以访问属性的方式获取DI的服务,比如:

$this->db     #原本要$this->getDI()->get("db")

Phalcon中很多逐渐都继承了Phalcon\DI\Injectable了,至少要知道的是控制器和模型和视图都继承这个类,这样就可以在其内部以直接访问属性的方式访问DI资源。

Phalcon DI组件
在DI中可以设置服务,但是服务的规范由ServiceInterface制定,注册到DI中的服务,都应该是Phalcon\DI\Service的实例。

Phalcon\DI\Service\Builder
这个类用来构建复杂服务。

以下是Phalcon\DI\FactoryDefault的输出

Phalcon\DI\FactoryDefault Object
(
    [_services] => Array
        (
            [router] => Phalcon\DI\Service Object
            [dispatcher] => Phalcon\DI\Service Object
            [url] => Phalcon\DI\Service Object
            [modelsManager] => Phalcon\DI\Service Object
            [modelsMetadata] => Phalcon\DI\Service Object
            [response] => Phalcon\DI\Service Object
            [cookies] => Phalcon\DI\Service Object
            [request] => Phalcon\DI\Service Object
            [filter] => Phalcon\DI\Service Object
            [escaper] => Phalcon\DI\Service Object
            [security] => Phalcon\DI\Service Object
            [crypt] => Phalcon\DI\Service Object
            [annotations] => Phalcon\DI\Service Object
            [flash] => Phalcon\DI\Service Object
            [flashSession] => Phalcon\DI\Service Object
            [tag] => Phalcon\DI\Service Object
            [session] => Phalcon\DI\Service Object
            [sessionBag] => Phalcon\DI\Service Object
            [eventsManager] => Phalcon\DI\Service Object
            [transactionManager] => Phalcon\DI\Service Object
            [assets] => Phalcon\DI\Service Object
            [db] => Phalcon\DI\Service Object
            [view] => Phalcon\DI\Service Object
        )

    [_sharedInstances] => Array
        (
            [router] => Phalcon\Mvc\Router Object
            [view] => Phalcon\Mvc\View Object
            [dispatcher] => Phalcon\Mvc\Dispatcher Object
            [IndexController] => IndexController Object
            [assets] => Phalcon\Assets\Manager Object
            [url] => Phalcon\Mvc\Url Object
            [escaper] => Phalcon\Escaper Object
            [response] => Phalcon\Http\Response Object
        )

    [_freshInstance] => 1
)

_services保存了服务定义,这些服务在实际使用的才会实例化,同时根据实例化时的情况如果为共享实例就写入_sharedInstances中,实例化后就可以从共享实例中获取实例,除非明确要使用一份新实例。比如,db虽然已经设置了服务,但是当前并没有用到,所以它没有实例化。这个是DI依赖注入的其中一个优点。

Phalcon\DI\FactoryDefault会默认注册一批框架需要的服务,大部分都设置为是共享的,当要改变这个已经注册的服务时,只要重新以相同的名字注册一次即可,但是如果没有明确指定服务是否是共享的,那就是非共享的(set方法第三参数是布尔值,默认是false,设置为true时服务为共享服务),但是在覆盖默认的服务视乎是例外的,对于默认服务,如果原来是共享的,覆盖时就算设置为非共享(调用set方法把第三参数设置为false,对应服务标记_shared为空),但它一旦实例化后也会被放入到_sharedInstances数组,这个就说明它总是共享的。
————————————————————————————–

前言:
在Zend Framework 1.x中或Yaf,或其它的框架中,经常有如下类似代码(Yaf为例子):

#application/Bootstrap.php
class Bootstrap extends Yaf_Bootstrap_Abstract{
        public function _initDatabaseAdapter(){
                $dbConfig = Yaf_Application::app()->getConfig()->get("db");
                $dbAdapter = $dbConfig->get("adapter");
                $dbParams = $dbConfig->get("params")->toArray();

                $db = Zend_Db::factory($dbAdapter, $dbParams);
                Zend_Db_Table::setDefaultAdapter($db);

                Yaf_Registry::set('db', $db);
        }
}

在具体的控制器类中通过Yaf_Registry::get(‘db’)获取数据库的引用。这里通过全局注册表的方式,让具体的类中没有具体依赖数据库实例,也算解决了组件之间的耦合问题。但是缺点是每次请求都会实例化一个数据库链接,哪怕某些访问可能根本不用到数据库,或者可以在具体用到的数据库链接的控制器方法中再实例化一个数据库链接,但是只要稍加思考,就要觉得不靠谱。

Phalcon中的DI是依赖注入,有两个容器,_services和_sharedInstances,_services用来存放注册的服务(非实例),_sharedInstances是根据注册服务时如果是共享服务,一旦服务实例化就放入这个容器,后面如果再次调用,则会直接返回这个实例。_services中注册的服务,是用到的时候才会实例化并决定是否放入_sharedInstances容器。这个特征就解决了我以上说的问题,用不到就不会实例化,用到时才实例化之。
————————————————————————————–
以下是Phalcon框架文档关于DI的内容,非常详细,值得一读,我添加了部分中文,是为学习笔记与备忘。
http://docs.phalconphp.com/en/latest/reference/di.html

Dependency Injection/Service Location
依赖注入/服务定位

The following example is a bit lengthy, but explains why use service location and dependency injection. First, let’s pretend we are developing a component called SomeComponent. This performs a task that is not important now. Our component has some dependency that is a connection to a database.
以下的例子有点长,但是解释了为何使用服务定位和依赖注入。首先,让我们假设我们正在开发一个叫SomeComponent的组件。这个组件执行的任务在当前是不重要的。我们的组件对链接到数据库有依赖。

In this first example, the connection is created inside the component. This approach is impractical; due to the fact we cannot change the connection parameters or the type of database system because the component only works as created.
在第一个例子,链接在组件内部被创建。这个方法是不符合实践的;因为我们不能修改链接参数或者数据库的类型。

<?php

class SomeComponent
{

    /**
     * The instantiation of the connection is hardcoded inside
     * the component, therefore it's difficult replace it externally
     * or change its behavior
     */
    public function someDbTask()
    {
        $connection = new Connection(array(
            "host" => "localhost",
            "username" => "root",
            "password" => "secret",
            "dbname" => "invo"
        ));

        // ...
    }

}

$some = new SomeComponent();
$some->someDbTask();

To solve this, we have created a setter that injects the dependency externally before using it. For now, this seems to be a good solution:
为了解决这个问题,我们创建了setter使得在使用它之前可以从外部注入依赖。现在,这好像是一个好的解决方案:

<?php

class SomeComponent
{

    protected $_connection;

    /**
     * Sets the connection externally
     */
    public function setConnection($connection)
    {
        $this->_connection = $connection;
    }

    public function someDbTask()
    {
        $connection = $this->_connection;

        // ...
    }

}

$some = new SomeComponent();

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

//Inject the connection in the component
$some->setConnection($connection);

$some->someDbTask();

Now consider that we use this component in different parts of the application and then we will need to create the connection several times before passing it to the component. Using some kind of global registry where we obtain the connection instance and not have to create it again and again could solve this:
现在考虑我们在应用程序的不同部分中使用这个组件然后在传递到组件之前我们将需要创建链接多次。从某类型的全局注册表获取链接实例而不用多次创建它可以解决这个问题:

<?php

class Registry
{

    /**
     * Returns the connection
     */
    public static function getConnection()
    {
       return new Connection(array(
            "host" => "localhost",
            "username" => "root",
            "password" => "secret",
            "dbname" => "invo"
        ));
    }

}

class SomeComponent
{

    protected $_connection;

    /**
     * Sets the connection externally
     */
    public function setConnection($connection)
    {
        $this->_connection = $connection;
    }

    public function someDbTask()
    {
        $connection = $this->_connection;

        // ...
    }

}

$some = new SomeComponent();

//Pass the connection defined in the registry
$some->setConnection(Registry::getConnection());

$some->someDbTask();

Now, let’s imagine that we must implement two methods in the component, the first always need to create a new connection and the second always need to use a shared connection:
现在,让我们想象我们必须在我们的组件中实现两个方法,第一个总是需要创建一个新的链接而第二个总是使用共享的链接:

<?php

class Registry
{

    protected static $_connection;

    /**
     * Creates a connection
     */
    protected static function _createConnection()
    {
        return new Connection(array(
            "host" => "localhost",
            "username" => "root",
            "password" => "secret",
            "dbname" => "invo"
        ));
    }

    /**
     * Creates a connection only once and returns it
     */
    public static function getSharedConnection()
    {
        if (self::$_connection===null){
            $connection = self::_createConnection();
            self::$_connection = $connection;
        }
        return self::$_connection;
    }

    /**
     * Always returns a new connection
     */
    public static function getNewConnection()
    {
        return self::_createConnection();
    }

}

class SomeComponent
{

    protected $_connection;

    /**
     * Sets the connection externally
     */
    public function setConnection($connection)
    {
        $this->_connection = $connection;
    }

    /**
     * This method always needs the shared connection
     */
    public function someDbTask()
    {
        $connection = $this->_connection;

        // ...
    }

    /**
     * This method always needs a new connection
     */
    public function someOtherDbTask($connection)
    {

    }

}

$some = new SomeComponent();

//This injects the shared connection
$some->setConnection(Registry::getSharedConnection());

$some->someDbTask();

//Here, we always pass a new connection as parameter
$some->someOtherDbTask(Registry::getNewConnection());

So far we have seen how dependency injection solved our problems. Passing dependencies as arguments instead of creating them internally in the code makes our application more maintainable and decoupled. However, in the long-term, this form of dependency injection have some disadvantages.
现在为止我们已经看到依赖注入如何解决我们的问题。以参数传递依赖来代替在代码中创建它们使得我们的应用程序更加可维护和低耦合。尽管,在这个很长的术语汇总,这种形式的依赖注入有一些缺点。

For instance, if the component has many dependencies, we will need to create multiple setter arguments to pass the dependencies or create a constructor that pass them with many arguments, additionally creating dependencies before using the component, every time, makes our code not as maintainable as we would like:
比如,如果组件有很多依赖,我们将需要创建多个setter参数来传递依赖或创建用来传递它们的多参数的构造函数,在使用组件前要创建依赖,每一次,使得我们的代码不是我们喜欢的那样好维护:

<?php

//Create the dependencies or retrieve them from the registry
$connection = new Connection();
$session = new Session();
$fileSystem = new FileSystem();
$filter = new Filter();
$selector = new Selector();

//Pass them as constructor parameters
$some = new SomeComponent($connection, $session, $fileSystem, $filter, $selector);

// ... or using setters

$some->setConnection($connection);
$some->setSession($session);
$some->setFileSystem($fileSystem);
$some->setFilter($filter);
$some->setSelector($selector);

Think we had to create this object in many parts of our application. If you ever do not require any of the dependencies, we need to go everywhere to remove the parameter in the constructor or the setter where we injected the code. To solve this, we return again to a global registry to create the component. However, it adds a new layer of abstraction before creating the object:
思考一下在我们应用中很多部分创建了这个对象。如果你不需要任何依赖,我们需要到每一处去移除我们注入到代码中的构造函数中的参数或移除setter。为了解决这个问题,我们回到全局注册表去创建组件。但是,在创建对象之前添加了一个抽象层:

<?php

class SomeComponent
{

    // ...

    /**
     * Define a factory method to create SomeComponent instances injecting its dependencies
     */
    public static function factory()
    {

        $connection = new Connection();
        $session = new Session();
        $fileSystem = new FileSystem();
        $filter = new Filter();
        $selector = new Selector();

        return new self($connection, $session, $fileSystem, $filter, $selector);
    }

}

One moment, we returned to the beginning, we are again building the dependencies inside of the component! We can move on and find out a way to solve this problem every time. But it seems that time and again we fall back into bad practices.
稍等,我们回到了原点,我们在组件内部再次建立了依赖…

A practical and elegant way to solve these problems is using a container for dependencies. The containers act as the global registry that we saw earlier. Using the container for dependencies as a bridge to obtain the dependencies allows us to reduce the complexity of our component:
解决这一问题的实用和优雅的方式是为依赖使用一个容器。这个容器扮演全局注册表。使用这个容器作为桥梁去获取依赖可以让我们降低组件的复杂度。

<?php

class SomeComponent
{

    protected $_di;

    public function __construct($di)
    {
        $this->_di = $di;
    }

    public function someDbTask()
    {

        // Get the connection service
        // Always returns a new connection
        $connection = $this->_di->get('db');

    }

    public function someOtherDbTask()
    {

        // Get a shared connection service,
        // this will return the same connection everytime
        $connection = $this->_di->getShared('db');

        //This method also requires an input filtering service
        $filter = $this->_di->get('filter');

    }

}

$di = new Phalcon\DI();

//Register a "db" service in the container
$di->set('db', function() {
    return new Connection(array(
        "host" => "localhost",
        "username" => "root",
        "password" => "secret",
        "dbname" => "invo"
    ));
});

//Register a "filter" service in the container
$di->set('filter', function() {
    return new Filter();
});

//Register a "session" service in the container
$di->set('session', function() {
    return new Session();
});

//Pass the service container as unique parameter
$some = new SomeComponent($di);

$some->someTask();

The component now simply access the service it requires when it needs it, if it does not require a service that is not even initialized saving resources. The component is now highly decoupled. For example, we can replace the manner in which connections are created, their behavior or any other aspect of them and that would not affect the component.
组件在需要服务时可以很简单地获取,如果不需要服务甚至都不需要初始化它。组件现在是高度解耦的了。例如,我们可以替换创建的链接,它们的行为不会影响到组件。

Our approach
我们的方法

Phalcon\DI is a component implementing Dependency Injection and Location of services and it’s itself a container for them.
Phalcon\DI是一个实现依赖注入和服务定位器的组件,而它本身就是一个容器。

Since Phalcon is highly decoupled, Phalcon\DI is essential to integrate the different components of the framework. The developer can also use this component to inject dependencies and manage global instances of the different classes used in the application.
Phalcon是高度解耦合的,Phalcon\DI在框架的不同组件中整合是必不可少的。开发者也可以使用这个组件去注入依赖和管理不同在应用中使用的类的全局实例。

Basically, this component implements the Inversion of Control pattern. Applying this, the objects do not receive their dependencies using setters or constructors, but requesting a service dependency injector. This reduces the overall complexity since there is only one way to get the required dependencies within a component.
基本上,这个组件实现了IoC设计模式(控制反转)。应该这个模式,对象不接收它们的通过setter或构造函数传递的依赖,而是要求一个服务依赖注入器(Di)。这减少了整体负责度,在组件中唯一一个获取依赖的方法。

Additionally, this pattern increases testability in the code, thus making it less prone to errors.

Registering services in the Container
在容器中注册服务

The framework itself or the developer can register services. When a component A requires component B (or an instance of its class) to operate, it can request component B from the container, rather than creating a new instance component B.
框架本身或开发者可以注册服务。当组件A需要组件B(或者一个类的实例)去操作,它可以要求组件B来自容器,而不用创建一个新的组件B实例。

This way of working gives us many advantages:
这个方法有很多优点:
We can easily replace a component with one created by ourselves or a third party.容易替换组件(指被依赖的那个组件)。
We have full control of the object initialization, allowing us to set these objects, as needed before delivering them to components.完全控制对象初始化,在传递到组件之前可以设置它。
We can get global instances of components in a structured and unified way可以以结构化和统一的方式获取全局的组件实例

Services can be registered using several types of definitions:
服务可以使用多种定义的类型注册:

<?php

//Create the Dependency Injector Container
$di = new Phalcon\DI();

//By its class name
$di->set("request", 'Phalcon\Http\Request');

//Using an anonymous function, the instance will be lazy loaded
$di->set("request", function() {
    return new Phalcon\Http\Request();
});

//Registering an instance directly
$di->set("request", new Phalcon\Http\Request());

//Using an array definition
$di->set("request", array(
    "className" => 'Phalcon\Http\Request'
));

The array syntax is also allowed to register services:
也可以使用数组语法:

<?php

//Create the Dependency Injector Container
$di = new Phalcon\DI();

//By its class name
$di["request"] = 'Phalcon\Http\Request';

//Using an anonymous function, the instance will be lazy loaded
$di["request"] = function() {
    return new Phalcon\Http\Request();
};

//Registering an instance directly
$di["request"] = new Phalcon\Http\Request();

//Using an array definition
$di["request"] = array(
    "className" => 'Phalcon\Http\Request'
);

In the examples above, when the framework needs to access the request data, it will ask for the service identified as ‘request’ in the container. The container in turn will return an instance of the required service. A developer might eventually replace a component when he/she needs.
以上例子,当框架需要放入request数据时,它将向容器中要求认证为request的服务。容器将返回一个要求的实例。开发者可能要最终替换一个组件。

Each of the methods (demonstrated in the examples above) used to set/register a service has advantages and disadvantages. It is up to the developer and the particular requirements that will designate which one is used.
以上每个方法有有点和缺点…

Setting a service by a string is simple, but lacks flexibility. Setting services using an array offers a lot more flexibility, but makes the code more complicated. The lambda function is a good balance between the two, but could lead to more maintenance than one would expect.
通过一个字符串设置一个服务是简单的,但是缺乏灵活性。通过数组设置提供了更多灵活性,但使得代码复杂。lambda函数在它们之间取得平衡,但是可能会导致要比我们预期更多的维护。

Phalcon\DI offers lazy loading for every service it stores. Unless the developer chooses to instantiate an object directly and store it in the container, any object stored in it (via array, string, etc.) will be lazy loaded i.e. instantiated only when requested.
Phalcon\DI为在它之中存储的每个服务提供了延迟加载。除非开发者直接实例化一个对象并在它之中存储。只有在要求时才实例化。

Simple Registration
简单注册
As seen before, there are several ways to register services. These we call simple:之前的多种服务的注册都称为简单的:

String 字符串

This type expects the name of a valid class, returning an object of the specified class, if the class is not loaded it will be instantiated using an auto-loader. This type of definition does not allow to specify arguments for the class constructor or parameters:
这个类型期望是一个验证的类名,返回一个指定类的实例,如果类没有加载则将使用auto-loader初始化。这种定义类型不运行为构造函数指定参数:

<?php

// return new Phalcon\Http\Request();
$di->set('request', 'Phalcon\Http\Request');

Object 对象

This type expects an object. Due to the fact that object does not need to be resolved as it is already an object, one could say that it is not really a dependency injection, however it is useful if you want to force the returned dependency to always be the same object/value:

<?php

// return new Phalcon\Http\Request();
$di->set('request', new Phalcon\Http\Request());

Closures/Anonymous functions闭包或匿名函数

This method offers greater freedom to build the dependency as desired, however, it is difficult to change some of the parameters externally without having to completely change the definition of dependency:

<?php

$di->set("db", function() {
    return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
         "host" => "localhost",
         "username" => "root",
         "password" => "secret",
         "dbname" => "blog"
    ));
});

Some of the limitations can be overcome by passing additional variables to the closure’s environment:
一些限制可以通过传递额外的变量到闭包环境来克服:

<?php

//Using the $config variable in the current scope
$di->set("db", function() use ($config) {
    return new \Phalcon\Db\Adapter\Pdo\Mysql(array(
         "host" => $config->host,
         "username" => $config->username,
         "password" => $config->password,
         "dbname" => $config->name
    ));
});

Complex Registration
复杂注册

If it is required to change the definition of a service without instantiating/resolving the service(应该是说要改变服务定义,比如改变类名), then, we need to define the services using the array syntax. Define a service using an array definition can be a little more verbose:

<?php

//Register a service 'logger' with a class name and its parameters
$di->set('logger', array(
    'className' => 'Phalcon\Logger\Adapter\File',
    'arguments' => array(
        array(
            'type' => 'parameter',
            'value' => '../apps/logs/error.log'
        )
    )
));

//Using an anonymous function
$di->set('logger', function() {
    return new \Phalcon\Logger\Adapter\File('../apps/logs/error.log');
});

Both service registrations above produce the same result. The array definition however, allows for alteration of the service parameters if needed:

<?php

//Change the service class name
$di->getService('logger')->setClassName('MyCustomLogger');

//Change the first parameter without instantiating the logger
$di->getService('logger')->setParameter(0, array(
    'type' => 'parameter',
    'value' => '../apps/logs/error.log'
));

(这个搞法很前卫)。

In addition by using the array syntax you can use three types of dependency injection:
使用数组语法可以使用三种依赖注入类型:

Constructor Injection
构造函数注入
This injection type passes the dependencies/arguments to the class constructor. Let’s pretend we have the following component:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{

    protected $_response;

    protected $_someFlag;

    public function __construct(Response $response, $someFlag)
    {
        $this->_response = $response;
        $this->_someFlag = $someFlag;
    }

}

$di->set('response', array(
    'className' => 'Phalcon\Http\Response'
));

$di->set('someComponent', array(
    'className' => 'SomeApp\SomeComponent',
    'arguments' => array(
        array('type' => 'service', 'name' => 'response'),
        array('type' => 'parameter', 'value' => true)
    )
));

Setter Injection
Setter注入
Classes may have setters to inject optional dependencies, our previous class can be changed to accept the dependencies with setters:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{

    protected $_response;

    protected $_someFlag;

    public function setResponse(Response $response)
    {
        $this->_response = $response;
    }

    public function setFlag($someFlag)
    {
        $this->_someFlag = $someFlag;
    }

}

$di->set('response', array(
    'className' => 'Phalcon\Http\Response'
));

$di->set('someComponent', array(
    'className' => 'SomeApp\SomeComponent',
    'calls' => array(
        array(
            'method' => 'setResponse',
            'arguments' => array(
                array('type' => 'service', 'name' => 'response'),
            )
        ),
        array(
            'method' => 'setFlag',
            'arguments' => array(
                array('type' => 'parameter', 'value' => true)
            )
        )
    )
));

Properties Injection
属性注入
A less common strategy is to inject dependencies or parameters directly into public attributes of the class:

<?php

namespace SomeApp;

use Phalcon\Http\Response;

class SomeComponent
{

    public $response;

    public $someFlag;

}

$di->set('response', array(
    'className' => 'Phalcon\Http\Response'
));

$di->set('someComponent', array(
    'className' => 'SomeApp\SomeComponent',
    'properties' => array(
        array(
            'name' => 'response',
            'value' => array('type' => 'service', 'name' => 'response')
        ),
        array(
            'name' => 'someFlag',
            'value' => array('type' => 'parameter', 'value' => true)
        )
    )
));

Supported parameter types include the following:

Type Description Example
parameter Represents a literal value to be passed as parameter array(‘type’ => ‘parameter’, ‘value’ => 1234)
service Represents another service in the service container array(‘type’ => ‘service’, ‘name’ => ‘request’)
instance Represents an object that must be built dynamically array(‘type’ => ‘instance’, ‘className’ => ‘DateTime’, ‘arguments’ => array(‘now’))

(注意看,type是service时,对应Di容器中存在的服务)

Resolving a service whose definition is complex may be slightly slower than simple definitions seen previously. However, these provide a more robust approach to define and inject services.

Mixing different types of definitions is allowed, everyone can decide what is the most appropriate way to register the services according to the application needs.

Resolving Services
获取服务
Obtaining a service from the container is a matter of simply calling the “get” method. A new instance of the service will be returned:

<?php $request = $di->get("request");

Or by calling through the magic method:

<?php

$request = $di->getRequest();

Or using the array-access syntax:

<?php

$request = $di['request'];

Arguments can be passed to the constructor by adding an array parameter to the method “get”:

<?php

// new MyComponent("some-parameter", "other")
$component = $di->get("MyComponent", array("some-parameter", "other"));

(这个搞法…)

Shared services
共享服务
Services can be registered as “shared” services this means that they always will act as singletons. Once the service is resolved for the first time the same instance of it is returned every time a consumer retrieve the service from the container:如果是共享的,则全局总是只有一个:

<?php

//Register the session service as "always shared"
$di->setShared('session', function() {
    $session = new Phalcon\Session\Adapter\Files();
    $session->start();
    return $session;
});

$session = $di->get('session'); // Locates the service for the first time
$session = $di->getSession(); // Returns the first instantiated object

An alternative way to register shared services is to pass “true” as third parameter of “set”:

<?php

//Register the session service as "always shared"
$di->set('session', function() {
    //...
}, true);

If a service isn’t registered as shared and you want to be sure that a shared instance will be accessed every time the service is obtained from the DI, you can use the ‘getShared’ method:

<?php

$request = $di->getShared("request");

Manipulating services individually
Once a service is registered in the service container, you can retrieve it to manipulate it individually:

<?php

//Register the "register" service
$di->set('request', 'Phalcon\Http\Request');

//Get the service
$requestService = $di->getService('request');

//Change its definition
$requestService->setDefinition(function() {
    return new Phalcon\Http\Request();
});

//Change it to shared
$requestService->setShared(true);

//Resolve the service (return a Phalcon\Http\Request instance)
$request = $requestService->resolve();

(这个搞法也让我惊叹….)

Instantiating classes via the Service Container
When you request a service to the service container, if it can’t find out a service with the same name it’ll try to load a class with the same name. With this behavior we can replace any class by another simply by registering a service with its name:

<?php

//Register a controller as a service
$di->set('IndexController', function() {
    $component = new Component();
    return $component;
}, true);

//Register a controller as a service
$di->set('MyOtherComponent', function() {
    //Actually returns another component
    $component = new AnotherComponent();
    return $component;
});

//Create an instance via the service container
$myComponent = $di->get('MyOtherComponent');

You can take advantage of this, always instantiating your classes via the service container (even if they aren’t registered as services). The DI will fallback to a valid autoloader to finally load the class. By doing this, you can easily replace any class in the future by implementing a definition for it.
(这个节没有看懂)

If a class or component requires the DI itself to locate services, the DI can automatically inject itself to the instances it creates, to do this, you need to implement the Phalcon\DI\InjectionAwareInterface in your classes:

<?php

class MyClass implements \Phalcon\DI\InjectionAwareInterface
{

    protected $_di;

    public function setDi($di)
    {
        $this->_di = $di;
    }

    public function getDi()
    {
        return $this->_di;
    }

}

Then once the service is resolved, the $di will be passed to setDi automatically:

<?php

//Register the service
$di->set('myClass', 'MyClass');

//Resolve the service (NOTE: $myClass->setDi($di) is automatically called)
$myClass = $di->get('myClass');

(实现一个接口,自动调用某方法)。

Avoiding service resolution
Some services are used in each of the requests made to the application, eliminate the process of resolving the service could add some small improvement in performance.

<?php

//Resolve the object externally instead of using a definition for it:
$router = new MyRouter();

//Pass the resolved object to the service registration
$di->set('router', $router);

Organizing services in files
在文件中组织服务
You can better organize your application by moving the service registration to individual files instead of doing everything in the application’s bootstrap:

<?php

$di->set('router', function() {
    return include "../app/config/routes.php";
});

//Then in the file (”../app/config/routes.php”) return the object resolved:
<?php

$router = new MyRouter();

$router->post('/login');

return $router;

Accessing the DI in a static way
以静态方法获取DI
If needed you can access the latest DI created in a static function in the following way:

<?php

class SomeComponent
{

    public static function someMethod()
    {
        //Get the session service
        $session = Phalcon\DI::getDefault()->getSession();
    }

}

Factory Default DI
Although the decoupled character of Phalcon offers us great freedom and flexibility, maybe we just simply want to use it as a full-stack framework. To achieve this, the framework provides a variant of Phalcon\DI called Phalcon\DI\FactoryDefault. This class automatically registers the appropriate services bundled with the framework to act as full-stack.
框架提供了Phalcon\DI\FactoryDefault,这个类自动注册适当的捆绑到框架的服务来作为全栈。(就是为框架服务的服务)

<?php $di = new Phalcon\DI\FactoryDefault();

Service Name ConventionsAlthough you can register services with the names you want, Phalcon has a several naming conventions that allow it to get the the correct (built-in) service when you need it.(内建服务)

Service Name Description Default Shared
dispatcher Controllers Dispatching Service Phalcon\Mvc\Dispatcher Yes
router Routing Service Phalcon\Mvc\Router Yes
url URL Generator Service Phalcon\Mvc\Url Yes
request HTTP Request Environment Service Phalcon\Http\Request Yes
response HTTP Response Environment Service Phalcon\Http\Response Yes
cookies HTTP Cookies Management Service Phalcon\Http\Response\Cookies Yes
filter Input Filtering Service Phalcon\Filter Yes
flash Flash Messaging Service Phalcon\Flash\Direct Yes
flashSession Flash Session Messaging Service Phalcon\Flash\Session Yes
session Session Service Phalcon\Session\Adapter\Files Yes
eventsManager Events Management Service Phalcon\Events\Manager Yes
db Low-Level Database Connection Service Phalcon\Db Yes
security Security helpers Phalcon\Security Yes
crypt Encrypt/Decrypt data Phalcon\Crypt Yes
tag HTML generation helpers Phalcon\Tag Yes
escaper Contextual Escaping Phalcon\Escaper Yes
annotations Annotations Parser Phalcon\Annotations\Adapter\Memory Yes
modelsManager Models Management Service Phalcon\Mvc\Model\Manager Yes
modelsMetadata Models Meta-Data Service Phalcon\Mvc\Model\MetaData\Memory Yes
transactionManager Models Transaction Manager Service Phalcon\Mvc\Model\Transaction\Manager Yes
modelsCache Cache backend for models cache None
viewsCache Cache backend for views fragments None

(这个表格是需要记忆的)

Implementing your own DI
The Phalcon\DiInterface interface must be implemented to create your own DI replacing the one provided by Phalcon or extend the current one.构建自定的DI。

PHP框架Phalcon 之 安装

PHP框架Phalcon是一个PHP的扩展,它对于框架的实现完全使用使用底层语言来编写,它的MVC实现比较现代,提供的ORM方案也很优秀。是一个高性能框架,发展潜力巨大。

访问https://github.com/phalcon/cphalcon获取文件,在Github上指导了安装法:

git clone git://github.com/phalcon/cphalcon.git
cd cphalcon/build
sudo ./install

玛尼我要先安装Git? 看起来很牛逼,实际就是通过Git获取文件,然后执行cphalcon/build/install这个shell脚本。这个脚本负责编译Phalcon扩展。在我本机上编译安装有多份PHP(5.3.* 5.4.* 5.5.* 5.6.*),我只想使用其中一个版本添加这个扩展,所以需要修改一下这个脚本:

###########
##.........

cd $DIR

#Clean current compilation
if [ -f Makefile ]; then
	make clean
	phpize --clean
fi

#Perform the compilation
phpize && ./configure --enable-phalcon && make && make install && echo -e "\nThanks for compiling Phalcon!\nBuild succeed: Please restart your web server to complete the installation"

这部分修改为类似如下:

###########
##.........

cd $DIR

#Clean current compilation
if [ -f Makefile ]; then
	make clean
	/usr/local/php-5.5.15/bin/phpize --clean
fi

#Perform the compilation
/usr/local/php-5.5.15/bin/phpize && ./configure --with-config-file=/usr/local/php-5.5.15/bin/php-config --enable-phalcon && make && make install && echo -e "\nThanks for compiling Phalcon!\nBuild succeed: Please restart your web server to complete the installation"

实际就是定位phpize工具 和 php-config文件。

修改好后运行这个脚本就能自动完成编译。实际你也可以直接进入对应目录按照常规编译PHP模块。

添加extension=phalcon.so到php.ini中,然后坚持模块是否可以加载:

/usr/local/php-5.5.15/bin/php -m | grep phalcon
phalcon

安装完毕。

题外话:

# Check best compilation flags for GCC
export CC="gcc"
export CFLAGS="-march=native -mtune=native -O2 -finline-functions -fomit-frame-pointer"
export CPPFLAGS="-DPHALCON_RELEASE"
echo "int main() {}" > t.c
gcc $CFLAGS t.c -o t 2> t.t
if [ $? != 0 ]; then
	chmod +x gcccpuopt
	BFLAGS=`./gcccpuopt`
	export CFLAGS="-O2 -finline-functions -fomit-frame-pointer $BFLAGS"
	gcc $CFLAGS t.c -o t 2> t.t
	if [ $? != 0 ]; then
		export CFLAGS="-O2"
	fi
fi

if [ $(gcc -dumpversion | cut -f1 -d.) -ge 4 ]; then
	gcc $CFLAGS -fvisibility=hidden t.c -o t 2> t.t && export CFLAGS="$CFLAGS -fvisibility=hidden"
fi

以上这段代码是针对GCC编译的优化代码,值得参考。

原创文章,装载务必保留出处。
永久链接:http://blog.ifeeline.com/1200.html

HTML中的CHECKBOX和SELECT

HTML中的CHECKBOX只要添加了checked属性,这个复选框就会被选中,SELECT中的option只要添加selected属性就会被选择。实际上只要添加属性就可以了,但是通过firebug可以看到,实际解析时,添加的checked会变成checked=”checked”,selected会变成selectd=”selected”。

如果CHECKBOX没有指定value,并且这个CHECKBOX选中了,这个时候提交表单过去,传递过去的是字符串“on”。

如果SELECT的option没有指定value,并且这个select的option被选中了,这个时候提交表单过去,传递过去的是option节点的值。

按照这个思维,为了实现全选、反选的功能:
select_all
当我点击全选时,希望下面的所有CHECKBOX也勾选,那么这要把它们每个都添加checked属性即可,反选时把它们全部去掉即可。我使用jQuery中的attr()方法来操作,不过很快就出现了问题,属性被正常添加去除,但是复选框的勾却没有同时显示勾选。折腾了一会,在jQuery中找到prop()方法:

////////////////////
http://api.jquery.com/prop/

Attributes vs. Properties

The difference between attributes and properties can be important in specific situations. Before jQuery 1.6, the .attr() method sometimes took property values into account when retrieving some attributes, which could cause inconsistent behavior. As of jQuery 1.6, the .prop() method provides a way to explicitly retrieve property values, while .attr() retrieves attributes.

For example, selectedIndex, tagName, nodeName, nodeType, ownerDocument, defaultChecked, and defaultSelected should be retrieved and set with the .prop() method. Prior to jQuery 1.6, these properties were retrievable with the .attr() method, but this was not within the scope of attr. These do not have corresponding attributes and are only properties.

这里说明了Attributes 和 Properties的区别。

http://blog.ifeeline.com/1017.html

WINDOWS中的计划任务详解

WINDOWS中的计划任务位置 控制面板–管理工具–任务计划程序

想在WINDOWS中周期执行一个程序,就需要依赖它的任务计划程序,这个玩意类似Linux下的Crontab,不过它是个图形界面的,我本来是打算要周期运行本机的一段程序,而我本机运行的是WINDOWS,所以必须搬出它的“任务计划程序”。

WINDOWS计划任务

这里有的两种操作,一是创建基本任务,二是创建任务,创建基本任务是创建任务操作的阉割版本。点击创建任务,在常规按提示填写后,切换到触发器Tab:
WINDOWS事件触发
这里是设置每天运行一次,不过让人困惑的是这里说一天运行一次,底下高级设置又有设置多久重复任务的间隔,然后又有这个重复的时间长度。不过不用管了,你设置5分钟间隔,持续一天就相当是这一天每隔5分钟执行一次任务。

然后切换到“操作”TAB,点击新建:
WINDOWS计划任务添加操作
在程序或脚本这里填入需要执行程序的路径,应该可以可以执行的程序都可以(exe bat vbs)。不过附加的参数需要添加到“添加参数(可选)”这里。

WINDOWS下的计划任务主要的就这些东西了。其它的看图就可以了。

我原本是让其执行一个CURL程序,由于它是命令行工具,所以每次运行时它都弹出一个“黑框”,比较让人郁闷。后把它写入一个bat文件中,让其执行,它还是弹出黑框。实际bat文件的运行是在WINDOW的shell中运行的,也就是所谓的cmd.exe。所以自然它会弹出黑框。不过可以通过VBS包装可以避免弹出:

set ws=wscript.createobject("wscript.shell")
ws.run "D:/cron.bat /start",0

这里的cron.bat是要执行的bat脚本:

@echo off
D:\Zend\ZendServer\bin\curl.exe -s http://***/cron.php >> D:/Cron_Debug.txt
exit

BAT脚本是在CMD这个SHELL中执行的,所以在CMD SHELL中所有的命令与操作符都能使用,比如这里的输出重定向。

这样弹出框没有出现了,脚本周期执行。不过我本机上按照的360卫士就报告了木马,我靠,好高级啊,大概这个操作符合木马特征了,不需要犹豫,把它放入信任列表。至此,达到预期。

永久链接:http://blog.ifeeline.com/1012.html