博客 / 詳情

返回

Phalcon模型

使用模型(Working with Models)

模型表示應用程序信息(數據)以及這些數據的處理規則,主要用於管理與對應數據表的交互規則。大多數情況下,數據庫中的每一張表都有對應的模型。應用程序中的大部分業務邏輯集中在模型中。

Phalcon應用中,Phalcon\Mvc\Model是所有模型的基類。它提供了數據庫獨立、基礎CRUD、高級查找、模型關聯以及其他服務。

Phalcon\Mvc\Model將調用的方法動態轉換為相應的數據庫操作,避免了直接使用SQL。

模型使用數據庫高級抽象層,如果想要使用更為底層的方式操作數據庫,請參考Phalcon\Db組件文檔。

創建模型(Creating Models)

模型需繼承Phalcon\Mvc\Model類,以大駝峯格式命名。

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{

}

如果使用PHP 5.4、5.5版本,建議在模型中聲明對應數據表的所有字段,以節約內存。

模型Store\Toys\RobotParts默認映射robot_parts表,可以調用setSource()方法手動指定映射表:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{
    public function initialize()
    {
        $this->setSource('toys_robot_parts');
    }
}

模型RobotParts現在映射toys_robot_parts表。initialize()方法有助於在模型中創建自定義行為,如為模型指定映射表。

initialize()方法在請求期間只調用一次,目的是為該模型的所有實例執行初始化操作。如果每次實例化模型的時候都需要進行初始化,可以使用onConstruct()方法:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class RobotParts extends Model
{
    public function onConstruct()
    {
        // ...
    }
}

公共屬性和Setters、Getters方法(Public properties vs. Setters/Getters)

模型可以定義公共屬性,在任何獲取了模型實例的地方都可以讀寫模型的公共屬性:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

另一種實現方式是getterssetters方法,控制哪些模型屬性可以公開訪問。這種方式的好處是,開發者可以在對模型屬性進行寫操作時執行轉換和驗證,這是使用公共屬性方式無法實現的。此外,getterssetters可以在不改動模型和接口的前提下,應對未來可能的改動。如果字段名稱改變,唯一需要的改動的地方是getterssetters中引用的模型私有屬性。

<?php

namespace Store\Toys;

use InvalidArgumentException;
use Phalcon\Mvc\Model;

class Robots extends Model
{
    protected $id;

    protected $name;

    protected $price;

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

    public function setName($name)
    {
        // name不能太短
        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)
    {
        // price不能為負
        if ($price < 0) {
            throw new InvalidArgumentException(
                "Price can't be negative"
            );
        }

        $this->price = $price;
    }

    public function getPrice()
    {
        // 返回前,將該值轉換為double類型
        return (double) $this->price;
    }
}

雖然公共屬性在開發中的複雜度更低,但是getterssetters可以大大提高程序的測試性、擴展性和可維護性。開發者可以根據需求決定哪種方式更適合他們的應用。ORM兼容這兩種方式。

使用getterssetters時,屬性名中的下劃線可能會導致問題。

如果在屬性名中使用下劃線,在聲明getterssetters魔術方法時,仍然要使用駝峯格式($model->getPropertyName()代替$model->getProperty_name()$model->findByPropertyName()代替$model->findByProperty_name()等)。大多數系統推薦駝峯寫法,而不是下劃線寫法,所以建議按照文檔中的寫法為屬性命名。可以使用字段映射(如上所述)以確保屬性正確映射到數據表中對應字段。

理解記錄對象(Understanding Records To Objects)

模型的每一個實例代表數據表中的一條記錄,可以通過讀取模型對象屬性來訪問記錄數據。例如,表robots有如下記錄:

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)

通過主鍵查找某條記錄:

<?php

use Store\Toys\Robots;

// 查找id = 3的記錄
$robot = Robots::findFirst(3);

// 輸出'Terminator'
echo $robot->name;

一旦記錄存儲在內存中,就可以修改其中的數據並保存:

<?php

use Store\Toys\Robots;

$robot = Robots::findFirst(3);

$robot->name = 'RoboCop';

$robot->save();

Phalcon\Mvc\Model為web應用提供了數據庫高級抽象層,不需要使用原生SQL語句。

查找記錄(Finding Records)

Phalcon\Mvc\Model提供了多種查詢記錄的方法,下面例子演示如何用模型查找一條或多條記錄:

<?php

use Store\Toys\Robots;

// 查詢所有記錄
$robots = Robots::find();
echo 'There are ', count($robots), "\n";

// 查詢type = 'mechanical'的記錄
$robots = Robots::find("type = 'mechanical'");
echo 'There are ', count($robots), "\n";

// 獲取type = 'virtual'的記錄,根據name排序
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' => 'name',
    ]
);
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// 獲取type = 'virtual'的前100條記錄,根據name排序
$robots = Robots::find(
    [
        "type = 'virtual'",
        'order' => 'name',
        'limit' => 100,
    ]
);
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

如果要使用外部數據(如用户輸入)或變量查找記錄,必須進行參數綁定。

使用findFirst()方法,獲取滿足給定條件的第一條記錄:

<?php

use Store\Toys\Robots;

// 獲取robots表的第一條記錄
$robot = Robots::findFirst();
echo 'The robot name is ', $robot->name, "\n";

// 獲取robots表中type = 'mechanical'的第一條記錄
$robot = Robots::findFirst("type = 'mechanical'");
echo 'The first mechanical robot name is ', $robot->name, "\n";

// 獲取robots表中type = 'virtual'的第一條記錄,根據name排序
$robot = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' => 'name',
    ]
);

echo 'The first virtual robot name is ', $robot->name, "\n";

find()方法和findFirst()方法都接受一個包含指定搜索條件的關聯數組:

<?php

use Store\Toys\Robots;

$robots = Robots::findFirst(
    [
        "type = 'virtual'",
        'order' => 'name DESC',
        'limit' => 30,
    ]
);

$robots = Robots::find(
    [
        'conditions' => 'type = ?1',
        'bind'       => [
            1 => 'virtual',
        ],
    ]
);

有下列查詢選項:

參數 説明 示例
conditions 查詢操作的搜索條件,用於篩選符合指定條件的記錄。默認情況下,PhalconMvcModel假定第一個參數就是搜索條件 'conditions' => "name LIKE 'steve%'"
columns 獲取模型中的指定字段,而不是所有字段。使用此選項時,返回不完整對象。 'columns' => 'id, name'
bind 參數綁定與conditions一起使用,通過替換佔位符、轉義特殊字符從而提高安全性 'bind' => ['status' => 'A', 'type' => 'some-time']
bindTypes 使用參數綁定時,可以使用這個參數為綁定參數定義額外的類型限制,從而提高安全性 'bindTypes' => [Column::BIND_PARAM_STR, Column::BIND_PARAM_INT]
order 用於對結果集進行排序,使用逗號分隔多個字段 'order' => 'name DESC, status'
limit 將查詢結果的數量限制在一定範圍內 'limit' => 10
offset 設定查詢結果偏移量 'offset' => 5
group 允許跨記錄收集數據,並將結果集按一個或多個字段分組 'group' => 'name, status'
for_update 使用此選項,PhalconMvcModel將讀取最新的可用數據,併為讀取到的每一條記錄設置獨佔鎖 'for_update' => true
shared_lock 使用此選項,PhalconMvcModel將讀取最新的可用數據,併為讀取到的每一條記錄設置共享鎖 'shared_lock' => true
cache 緩存結果集,減少對數據庫的持續訪問 'cache' => ['lifetime' => 3600, 'key' => 'my-find-key']
hydration 設置結果集返回模式 'hydration' => Resultset::HYDRATE_OBJECTS

除了使用參數數組,還可以使用面向對象的方式創建查詢:

<?php

use Store\Toys\Robots;

$robots = Robots::query()
    ->where('type = :type:')
    ->addWhere('year < 2000')
    ->bind(['type' => 'machanical'])
    ->order('name')
    ->execute();

靜態方法query()返回一個IDE自動完成友好的Phalcon\Mvc\Model\Criteria對象。

所有查詢在內部都以PHQL查詢的方式處理。PHQL是一種高級的、面向對象的類SQL語言,這種語言提供了多種功能來執行查詢,如join其他模型,定義分組,添加聚合等。

最後,還有一個findFirstBy<property-name>()方法,該方法擴展了findFirst()方法,它允許通過使用方法中的屬性名稱並向它傳遞一個包含要在該字段中搜索數據的參數,從表中快速執行檢索。以上面的Robots模型為例:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $price;
}

這裏有三個屬性:$id$name$price,假設要檢索name為'Terminator'的第一條記錄,代碼如下:

<?php

use Store\Toys\Robots;

$name = 'Terminator';

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

if ($robot) {
    echo 'The first robot with the name ', $name . ' cost ' . $robot->price, '.';
} else {
    echo 'There were no robots found in our table with the name ' . $name . '.';
}

請注意,我們在調用的方法中使用了Name並傳遞了變量$name,表中name字段值為$name的記錄就是我們要查找的。

模型結果集(Model Resultsets)

findFirst()方法返回被調用類的實例(如果有結果返回),而find()方法返回Phalcon\Mvc\Model\Resultsets\Simple對象,該對象封裝了結果集應有的所有功能,如遍歷,查找特定記錄,統計等。

這些對象比一般數組功能強大,Phalcon\Mvc\Model\Resultset一個最大的特點是,任何時刻,只有一條記錄保存在內存中。這對內存管理有極大幫助,特別是在處理大批量數據時。

<?php

use Store\Toys\Robots;

// 獲取所有記錄
$robots = Robots::find();

// foreach遍歷
foreach ($robots as $robot) {
    echo $robot->name, "\n";
}

// while遍歷
while ($robots->valid()) {
    $robot = $robots->current();

    echo $robot->name, "\n";

    $robots->next();
}

// 結果集計數
echo count($robots);

// 另一種結果集計數方法
echo $robots->count();

// 移動遊標到第三條記錄
$robots->seek(2);

$robot = $robots->current();

// 通過位置訪問結果集中的記錄
$robot = $robots[5];

// 檢查指定位置是否有記錄
if (isset($robots[3])) {
    $robot = $robots[3];
}

// 獲取結果集中第一條記錄
$robot = $robots->getFirst();

// 獲取結果集中最後一條記錄
$robot = $robots->getLast();

Phalcon結果集模擬遊標,可以通過訪問其位置或內部指針獲取任一條記錄。注意,某些數據庫系統不支持遊標,這會導致查詢被反覆執行,以重置遊標到初始位置並獲取被請求位置的記錄。同樣的,遍歷多少次結果集,查詢便要執行多少次。

將大量查詢結果保存在內存中會消耗太多資源,因此,在某些情況下,以32條記錄為一塊從數據庫中獲取記錄,可以降低重複執行請求的內存消耗。

注意,結果集可以序列化後存儲在緩存中,Phalcon\Cache可以實現該需求。但是序列化數據會導致Phalcon\Mvc\Model以數組形式保存從數據庫中檢索到的數據,這會導致更多的內存消耗。

<?php

// 查詢表parts所有記錄
$parts = Parts::find();

// 將結果集保存到文件中
file_put_contents(
    'cache.txt',
    serialize($parts)
);

// 從文件中獲取結果集
$parts = unserialize(
    file_get_contents('cache.txt')
);

// 遍歷
foreach ($parts as $part) {
    echo $part->id;
}

自定義結果集(Custom Resultsets)

有時候應用程序需要對數據庫中檢索到的數據進行額外處理。以前,我們只需要擴展模型或將功能封裝在模型或trait當中,然後返回一組經過轉換的數據。

通過自定義結果集,不需要再如此處理。自定義結果集將封裝原本封裝在模型中並能被其他模型重用的功能,這樣可以簡化代碼。如此,find()方法返回自定義對象,而不再返回Phalcon\Mvc\Model\Resultset對象。Phalcon允許在模型中定義getResultsetClass()方法來實現此操作。

首先,聲明resultset類:

<?php

namespace Application\Mvc\Model\Resultset;

use Phalcon\Mvc\Model\Resultset\Simple;

class Custom extends Simple
{
    public function getSomeData()
    {
        /** CODE */
    }
}

模型中,在getResultsetClass()方法裏設置resultset類,如下:

<?php

namespace Phalcon\Test\Models\Statistics;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSource()
    {
        return 'robots';
    }

    public function getResultsetClass()
    {
        return 'Application\Mvc\Model\Resultset\Custom';
    }
}

最後,代碼裏應包含如下內容:

<?php

/**
 * 查找robots表記錄
 */
$robots = Robots::find(
    [
        'conditions' => 'date between "2017-01-01" AND "2017-12-31"',
        'order'      => 'date',
    ]
);

/**
 * 傳遞數據到視圖
 */
$this->view->mydata = $robots->getSomeData();

過濾結果集(Filtering Resultsets)

過濾數據最有效的方法之一是設置搜索條件,數據庫將使用表索引以更快的返回數據。Phalcon允許使用PHP函數或是數據庫不支持的方式過濾數據:

<?php

$customers = Customers::find();

$customers = $customers->filter(
    function ($customer) {
        // 只返回e-mail合法的記錄
        if (filter_var($customer->email, FILTER_VALIDATE_EMAIL)) {
            return $customer;
        }
    }
);

參數綁定(Binding Parameters)

Phalcon\Mvc\Model支持參數綁定,建議使用這種方法以避免SQL注入,字符串佔位符和數字佔位符均被支持。參數綁定簡單實現如下:

<?php

use Store\Toys\Robots;

// 字符串佔位符
$robots = Robots::find(
    [
        'name = :name: AND type = :type:',
        'bind' => [
            'name' => 'Robotina',
            'type' => 'maid',
        ],
    ]
);

// 數字佔位符
$robots = Robots::find(
    [
        'name = ?1 AND type = ?2',
        'bind' => [
            1 => 'Robotina',
            2 => 'maid',
        ],
    ]
);

// 同時使用字符串佔位符和數字佔位符
$robots = Robots::find(
    [
        'name = :name: AND type = ?1',
        'bind' => [
            'name' => 'Robotina',
            1      => 'maid',
        ],
    ]
);

使用數字佔位符時,需要將它們定義成整數形式,即1或2。而'1'或'2'會被當成字符串,所以佔位符不能被成功替換。

字符串會自動使用PDO轉義,該功能會考慮連接字符集,因此建議在連接參數或數據庫配置中定義正確字符集,因為錯誤字符集會在存儲和檢索數據時產生不良影響。

還可以設置bindTypes參數,定義參數如何根據其類型綁定:

<?php

use Phalcon\Db\Column;
use Store\Toys\Robots;

// 綁定參數
$parameters = [
    'name' => 'Robotina',
    'year' => 2008,
];

// 參數類型轉換
$types = [
    'name' => Column::BIND_PARAM_STR,
    'year' => Column::BIND_PARAM_INT,
];

// 字符串佔位符
$robots = Robots::find(
    [
        'name = :name: AND year = :year:',
        'bind'      => $parameters,
        'bindTypes' => $types,
    ]
);

由於默認的綁定類型是Phalcon\Db\Column::BIND_PARAM_STR,如果所有字段都是字符串類型,則沒有必要指定bindTypes參數。

如果使用數組作為綁定參數,則數組必須是鍵名從0開始的索引數組:

<?php

use Store\Toys\Robots;

$array = ['a', 'b', 'c']; // $array: [[0] => 'a', [1] => 'b', [2] => 'c']

unset($array[1]); // $array: [[0] => 'a', [2] => 'c']

// 現在必須重建數組索引
$array = array_values($array); // $array: [[0] => 'a', [1] => 'c']

$robots = Robots::find(
    [
        'letter IN ({letter:array})',
        'bind' => [
            'letter' => $array,
        ],
    ]
);

參數綁定除了可用於所有查詢方法,如find()findFirst()外,還可用於count()sum()average()等統計方法。

使用finders時,會自動使用參數綁定:

<?php

use Store\Toys\Robots;

// 顯式使用參數綁定
$robots = Robots::find(
    [
        'name = ?0',
        'bind' => [
            'Ultron',
        ],
    ]
);

// 隱式使用參數綁定
$robots = Robots::findByName('Ultron');

初始化已獲取記錄(Initializing / Preparing fetched records)

有時從數據庫獲取記錄之後,在數據被應用程序使用之前,需要對數據進行初始化。可以在模型中實現afterFetch()方法,實例化模型時會執行該方法,並將數據傳遞給它:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

    public function beforeSave()
    {
        // 將數組轉換成字符串
        $this->status = join(',', $this->status);
    }

    public function afterFetch()
    {
        // 將字符串轉換成數組
        $this->status = explode(',', $this->status);
    }

    public function afterSave()
    {
        // 將字符串轉換成數組
        $this->status = explode(',', $this->status);
    }
}

如果使用getters/setters代替公共屬性,或同時使用它們,可以在字段被訪問時初始化字段:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $id;

    public $name;

    public $status;

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

生成運算(Generating Calculations)

運算(或聚合)是數據庫中常用的輔助方法,如COUNTSUMMAXMINAVGPhalcon\Mvc\Model可以直接使用這些方法。
Count示例:

<?php

// 表employees總記錄數
$rowcount = Employees::count();

// 表employees共有多少不同areas值
$rowcount = Employees::count(
    [
        'distinct' => 'area',
    ]
);

// 表employees共有多少area為'Testing'的記錄
$rowcount = Employees::count(
    'area = "Testing"'
);

// 按area分組統計表employees記錄
$group = Employees::count(
    [
        'group' => 'area',
    ]
);
foreach ($group as $row) {
    echo 'There are ', $row->rowcount, ' in ', $row->area;
}

// 按area分組統計表employees記錄,並根據數目排序
$group = Employees::count(
    [
        'group' => 'area',
        'order' => 'rowcount',
    ]
);

// 使用參數綁定避免SQL注入
$group = Employees::count(
    [
        'type > ?0',
        'bind' => [
            $type,
        ],
    ]
);

Sum示例:

<?php

// 所有employees的salaries總和
$total = Employees::sum(
    [
        'column' => 'salary',
    ]
);

// area = 'Sales'的所有employees的salaries總和
$total = Employees::sum(
    [
        'column'     => 'salary',
        'conditions' => 'area = "Sales"',
    ]
);

// 根據area分組統計salaries
$group = Employees::sum(
    [
        'column' => 'salary',
        'group'  => 'area',
    ]
);
foreach ($group as $row) {
    echo 'The sum of salaries of the ', $row->area, ' is ', $row->sumatory;
}

// 根據area分組統計salaries,salaries由高到低排序
$group = Employees::sum(
    [
        'column' => 'salary',
        'group'  => 'area',
        'order'  => 'sumatory DESC',
    ]
);

// 使用參數綁定避免參數綁定
$group = Employees::sum(
    [
        'conditions' => 'area > ?0',
        'bind'       => [
            $area,
        ],
    ]
);

Average示例:

<?php

// 所有employees的平均salary
$average = Employees::average(
    [
        'column' => 'salary',
    ]
);

// area = 'Sales'的employees的平均salary
$average = Employees::average(
    [
        'column'     => 'salary',
        'conditions' => 'area = "Sales"',
    ]
);

// 使用參數綁定避免SQL注
$average = Employees::average(
    [
        'column'     => 'age',
        'conditions' => 'area > ?0',
        'bind'       => [
            $area,
        ],
    ]
);

Max / Min示例:

<?php

// 所有employees中age最大的
$age = Employees::maximum(
    [
        'column' => 'age',
    ]
);

// area = 'Sales'的employees中age最大的
$age = Employees::maximum(
    [
        'column'     => 'age',
        'conditions' => 'area = "Sales"',
    ]
);

// 所有employees中salary最低的
$salary = Employees::minimum(
    [
        'column' => 'salary',
    ]
);

創建 / 更新記錄(Creating / Updating Records)

Phalcon\Mvc\Model::save()方法會根據記錄是否存在於模型映射表中而創建 / 更新記錄,Phalcon\Mvc\Model的創建和更新方法會在內部調用該方法。為此,必須在實體中正確定義主鍵,以確定是創建記錄還是更新記錄。

該方法會執行相關驗證器,虛擬外鍵和模型中定義的事件:

<?php

use Store\Toys\Robots;

$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";

    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was saved successfully!';
}

直接傳遞或者通過屬性數組傳遞的值會根據其數據類型自動被轉義 / 過濾,所以可以傳遞一個不安全的數組而不用擔心SQL注入:

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->save($_POST);

毫無防護的批量傳值可能會允許攻擊者設置任意字段的值,僅在允許用户插入 / 更新模型中所有字段的情況下使用上述功能,即使這些字段不是使用表單提交的。

可以在save()方法中設置額外參數,以設置批量傳值時,執行插入 / 更新操作的白名單字段。

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->save(
    $_POST,
    [
        'name',
        'type',
    ]
);

創建 / 更新執行結果(Create / Update with Confidence)

應用程序高併發時,創建記錄操作可能會變成更新操作。使用Phalcon\Mvc\Model::save()方法保存記錄時,可能發生這種情況。如果想確保執行創建或更新,可以使用create()update()方法替換save()

<?php

use Store\Toys\Robots;

$robot = new Robots();

$robot->type = 'mechanical';
$robot->name = 'Astro Boy';
$robot->year = 1952;

// 僅創建記錄
if ($robot->create() === false) {
    echo "Umh, We can't store robots right now: \n";

    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Great, a new robot was created successfully!';
}

create()方法和update()方法同樣接受一個數組作為參數。

刪除記錄(Deleting Records)

Phalcon\Mvc\Model::delete()方法允許刪除記錄,使用示例:

<?php

use Store\Toys\Robots;

$robot = Robots::findFirst(11);

if ($robot !== false) {
    if ($robot->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robots was deleted successfully!';
    }
}

也可以通過使用foreach遍歷結果集來刪除多條記錄:

<?php

use Store\Toys\Robots;

$robots = Robots::find(
    "type = 'mechanical'"
);

foreach ($robots as $robot) {
    if ($robot->delete() === false) {
        echo "Sorry, we can't delete the robot right now: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'The robot was deleted successfully!';
    }
}

以下事件可以用於定義在執行刪除操作時,要執行的自定義業務規則:

操作 事件名稱 能否終止操作 説明
刪除 afterDelete 刪除操作後執行
刪除 beforeDelete 刪除操作前執行

通過上述事件,可以在模型中定義業務規則:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function beforeDelete()
    {
        if ($this->status === 'A') {
            echo "The robot is active, it can't be deleted";

            return false;
        }

        return true;
    }
}

Hydrations模式(Hydration Modes)

如前所述,結果集是完整對象的集合,這意味着每條返回結果都是一個對象,代表數據表中的一行。這些對象可以修改並永久保存:

<?php

use Store\Toys\Robots;

$robots = Robots::find();

// 操作完整對象結果集
foreach ($robots as $robot) {
    $robot->year = 2000;

    $robot->save();
}

有時記錄只能以只讀模式呈現給用户,這種情況下,改變記錄的展現方式有助於用户處理數據。用於表示結果集中返回的對象的策略稱為'hydration mode':

<?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find();

// 返回數組
$robots->setHydrateMode(
    Resultset::HYDRATE_ARRAYS
);

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

// 返回stdClass對象
$robots->setHydrateMode(
    Resultset::HYDRATE_OBJECTS
);

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

// 返回模型實例
$robots->setHydrateMode(
    Resultset::HYDRATE_RECORDS
);

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

Hydration mode也可以作為find()方法的參數傳遞:

<?php

use Phalcon\Mvc\Model\Resultset;
use Store\Toys\Robots;

$robots = Robots::find(
    [
        'hydration' => Resultset::HYDRATE_ARRAYS,
    ]
);

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

表前綴(Table prefixes)

如果希望所有表名稱都有特定前綴,並且不想在每個模型中都調用setSource()方法,則可以調用Phalcon\Mvc\Model\ManagersetModelprefix()方法:

<?php

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager;

class Robots extends Model
{

}

$manager = new Manager();
$manager->setModelPrefix('wp_');
$robots = new Robots(null, null, $manager);
echo $robots->getSource(); // 返回wp_robots

自動生成的標識字段(Auto-generated identity columns)

某些模型有標識字段,這些字段通常是映射表的主鍵。Phalcon\Mvc\Model能夠識別標識字段,並在生成INSERT語句時忽略它,所以數據庫能夠自動為它生成一個值。創建記錄之後,標識字段的值會被註冊為數據庫為其生成的值:

<?php

$robot->save();

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

Phalcon\Mvc\Model能夠識別標識字段,根據數據庫系統,這些字段可能是PostgreSQL的串行列,或者是MySQL的自增列。

PostgreSQL使用序列生成自增值,默認情況下,Phalcon試圖從序列table_field_seq中獲取生成的值,例如:robots_id_seq,如果序列具有其他名稱,則需要實現getSequenceName()方法:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function getSequenceName()
    {
        return 'robots_sequence_name';
    }
}

忽略字段(Skipping Columns)

Phalcon\Mvc\Model指定創建 / 更新記錄時需要被忽略的字段,以便數據庫為其賦默認值:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        // INSERT / UPDATE操作均忽略字段
        $this->skipAttributes(
            [
                'year',
                'price',
            ]
        );

        // INSERT操作忽略字段
        $this->skipAttributes(
            [
                'created_at',
            ]
        );

        // UPDATE操作忽略字段
        $this->skipAttributes(
            [
                'modified_in',
            ]
        );
    }
}

這將全局忽略應用程序中每個INSERT / UPDATE操作的這些字段。如果想在不同的INSERT / UPDATE操作時忽略不同字段,可以傳遞第二個參數(布爾值) - true。強制使用默認值,實現方式如下:

<?php

use Phalcon\Db\RawValue;
use Store\Toys\Robots;

$robot = new Robots();

$robot->name       = 'Bender';
$robot->year       = 1999;
$robot->created_at = new RawValue('default');

$robot->create();

回調函數也可以用於為默認值創建分配條件:

<?php

namespace Store\Toys;

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

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

切勿使用Phalcon\Db\RawValue傳遞外部數據(如用户輸入)或可變數據,因為參數綁定時,這些字段的值也會被忽略,所以有可能會被用來實施注入攻擊。

動態更新(Dynamic Updates)

UPDATE語句默認使用模型中定義的所有字段創建(全字段更新SQL),可以更改特定模型以進行動態更新,用需要更新的字段創建SQL語句。

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

獨立列映射(Independent Column Mapping)

ORM支持獨立的列映射,它允許開發者在模型中定義與映射表列名稱不相同的字段名,Phalcon會識別新的字段名稱,並重命名字段以匹配數據庫中相應的列。這是一項很棒的功能,當需要重命名數據庫中的列名稱時,不需要更改查詢代碼,模型中的映射列會處理好這一切。例如:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public $code;

    public $theName;

    public $theType;

    public $theYear;

    public function columnMap()
    {
        return [
            'id'       => 'code',
            'the_name' => 'theName',
            'the_type' => 'theType',
            'the_year' => 'theYear',
        ];
    }
}

然後,可以使用新的字段名:

<?php

use Store\Toys\Robots;

// 根據name查找一個記錄
$robot = Robots::findFirst(
    'theName = "Voltron"'
);

echo $robot->theName, "\n";

// 根據type排序
$robot = Robots::find(
    [
        'order' => 'theType DESC',
    ]
);

foreach ($robots as $robot) {
    echo 'Code: ', $robot->code, "\n";
}

// 添加記錄
$robot = new Robots();

$robot->code    = '10101';
$robot->theName = 'Bender';
$robot->theType = 'Industrial';
$robot->theYear = 2999;

$robot->save();

重命名字段時需要注意以下事項:

  • 關係 / 驗證器中對屬性的引用必須使用新名稱
  • 引用實際列名稱將導致ORM異常

獨立列映射允許:

  • 使用自定義約定編寫應用程序
  • 清除代碼中列的前後綴
  • 改變列名稱時,無需更改應用代碼

記錄快照(Record Snapshots)

查詢時可以設置特定的模型以保持記錄快照。可以使用此功能來實現審計,或是根據持久性查詢的數據瞭解哪些字段發生了改變:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

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

當激活此功能時,應用程序會消耗更多的內存來與持久性查詢的原始數據保持同步。在激活此功能的模型中,可以按如下方式檢查發生改變的字段:

<?php

use Store\Toys\Robots;

// 查找一條記錄
$robot = Robots::findFirst();

// 改變列值
$robot->name = 'Other name';

var_dump($robot->getChangedFields()); // ['name']

var_dump($robot->hasChanged('name')); // true

var_dump($robot->hasChanged('type')); // false

快照會在模型創建 / 更新時自動更新,使用hasUpdated()方法和getUploadedFields()方法檢查create / save / update操作後,字段是否更新。但是在afterUpdate()afterSave()afterCreate()方法中調用getChangedFields()方法,會導致應用程序出問題。

可以禁用此功能:

<?php

Phalcon\Mvc\Model::setup(
    [
        'updateSnapshotOnSave' => false,
    ]
);

也可以在php.ini中設置:

phalcon.orm.update_snapshot_on_save = 0

使用此功能會產生以下效果:

<?php

use Phalcon\Mvc\Model;

class User extends Model
{
    public function initialize()
    {
        $this->keepSnapshots(true);
    }
}

$user       = new User();
$user->name = 'Test User';
$user->create();
var_dump($user->getChangedFields());
$user->login = 'testuser';
var_dump($user->getChangedFields());
$user->update();
var_dump($user->getChangedFields());

在Phalcon 3.1.0及之後的版本中:

array(0) {
}
array(1) {
[0] => string(5) "login"
}
array(0) {
}

getUpdatedFields()方法將正確返回更新的字段,或如上所述,可以通過設置相關的ini值回到先前的行為。

指向不同模式(Pointing to a different schema)

如果模型映射到非默認的模式 / 數據庫,可以使用setSchema()方法重新定義它:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setSchema('toys');
    }
}

多數據庫配置(Setting multiple databases)

Phalcon應用中,所有模型可以屬於相同的數據庫連接,或具有獨立的數據庫連接。實際上,當Phalcon\Mvc\Model需要連接數據庫時,它會在應用程序的服務容器中請求數據庫服務。可以在initialize()方法中設置,覆蓋該服務:

<?php

use Phalcon\Db\Adapter\Pdo\Mysql as MysqlPdo;
use Phalcon\Db\Adapter\Pdo\PostgreSQL as PostgreSQLPdo;

// 註冊MySQL數據庫服務
$di->set(
    'dbMysql',
    function () {
        return new MysqlPdo(
            [
                'host'     => 'localhost',
                'username' => 'root',
                'password' => 'secret',
                'dbname'   => 'invo',
            ]
        );
    }
);

// 註冊PostgreSQL數據庫服務
$di->set(
    'dbPostgres',
    function () {
        return new PostgreSQLPdo(
            [
                'host'     => 'localhost',
                'username' => 'postgres',
                'password' => '',
                'dbname'   => 'invo',
            ]
        );
    }
);

然後,在initialize()方法中為模型定義連接服務:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setConnectionService('dbPostgres');
    }
}

Phalcon提供了更靈活的操作,可以定義只讀連接或寫連接,這對於負載均衡和主從架構的的數據庫非常有用:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function initialize()
    {
        $this->setReadConnectionService('dbSlave');

        $this->setWriteConnectionService('dbMaster');
    }
}

ORM還支持分片功能,允許根據當前查詢條件實施分片選擇:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    /**
     * 動態選擇分片
     * @param  array $intermediate
     * @param  array $bindParams
     * @param  array $bindTypes
     */
    public function selectReadConnection($intermediate, $bindParams, $bindTypes)
    {
        // 檢查select語句中是否有where子句
        if (isset($intermediate['where'])) {
            $conditions = $intermediate['where'];

            // 根據條件選擇可能的分片
            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');
                }
            }
        }

        // 使用默認分片
        return $this->getDI()->get('dbShard0');
    }
}

selectReadConnection()方法選擇合適的連接,會影響任何新執行的查詢:

<?php

use Store\Toys\Robots;

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

注入服務到模型(Injecting services into Models)

有時可能需要在模型中訪問應用程序服務,以下示例介紹瞭如何執行此操作:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Model;

class Robots extends Model
{
    public function notSaved()
    {
        // 從DI容器中獲取flash服務
        $flash = $this->getDI()->getFlash();

        $messages = $this->getMessages();

        // 顯示驗證消息
        foreach ($messages as $message) {
            $flash->error($message);
        }
    }
}

創建或更新操作執行失敗時會觸發notSaved事件,因此我們從DI容器中獲取flash服務,然後閃存驗證消息。這樣,我們不必在每次保存後打印消息。

禁用 / 啓用功能(Disabling / Enabling Features)

在ORM中,我們實現了一種機制,允許在全局範圍內啓用 / 禁用特定功能或選項,根據ORM的使用情況,可以禁用那些你沒有使用到的功能。如有需要,下面這些選項可以暫時禁用:

<?php

use Phalcon\Mvc\Model;

Model::setup(
    [
        'events'         => false,
        'columnRenaming' => false,
    ]
);

可用選項:

選項 説明 默認值
astCache 啓用 / 禁用模型中的回調、鈎子和事件通知 null
cacheLevel 設置ORM的緩存級別 3
castOnHydrate false
columnRenaming 啓用 / 禁用字段重命名 true
disableAssignSetters 模型中禁用setter false
enableImplicitJoins true
enableLiterals true
escapeIdentifiers true
events 啓用 / 禁用模型中的回調、鈎子和事件通知 true
exceptionOnFailedSave 啓用 / 禁用save()操作失敗時拋出異常 false
forceCasting false
ignoreUnknownColumns 啓用 / 禁用忽略模型上的未知字段 false
lateStateBinding 啓用 / 禁用PhalconMvcModel::cloneResultMap()方法的延遲綁定 false
notNullValidations ORM自動驗證映射表中的非空列 true
parserCache null
phqlLiterals 啓用 / 禁用PHQL解析器中的字面量 true
uniqueCacheId 3
updateSnapshotOnSave 啓用 / 禁用save()方法的更新快照 true
virtualForeignKeys 啓用 / 禁用虛擬外鍵 true

注意,給Phalcon\Mvc\Model::assign()方法(創建 / 更新 / 保存模型中常用到它)傳遞參數時,setters始終會被調用,這會給應用程序增加額外開銷。可以在php.ini文件中添加配置phalcon.orm.disable_assign_setters = 1,這樣就只會簡單的使用$this->property = value

獨立組件(Stand-Along component)

下面演示以獨立組件模式使用Phalcon\Mvc\Model

<?php

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

$di = new Di();

// 建立連接
$di->set(
    'db',
    new Connection(
        [
            'dbname' => 'sample.db',
        ]
    )
);

// 創建模型管理器
$di->set(
    'modelsManager',
    new ModelsManager()
);

// 使用內存元數據適配器或其他
$di->set(
    'modelsMetadata',
    new MetaData()
);

// 創建模型
class Robots extends Model
{

}

// 使用模型
echo Robots::count();
user avatar darkgel 頭像 mafa1993 頭像 philip-tellis 頭像 lucups 頭像 emtalk_qf 頭像
5 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.