---
title: "Models"
version: "5.19"
---

> Documentation Index
> Fetch the complete documentation index at: https://docs.phalcon.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Models

## Overview

The [Phalcon\Mvc\Model][mvc-model] is the `M` in MVC. It is a class that connects business objects and database tables, to create a persistent domain model, where logic and data are wrapped into one. It is an implementation of the object-relational mapping (ORM).

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.

The [Phalcon\Mvc\Model][mvc-model] is the first ORM written in Zephir/C languages for PHP, giving developers high performance when interacting with databases while also being straightforward to use.

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

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{

}
```

```php
<?php

use MyApp\Models\Invoices;

/**
 * Create an invoice 
 */
$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

echo 'Error saving Invoice: ';

$messages = $invoice->getMessages();

foreach ($messages as $message) {
    echo $message . PHP_EOL;
}
} else {

echo 'Record Saved';

}
```

:::info[NOTE]
For information on how to create a model please check the [Creating Models][creating-models] section
:::

## Constants

| Constant               |     Value     |
|------------------------|:-------------:|
| DIRTY_STATE_DETACHED   |       2       |
| DIRTY_STATE_PERSISTENT |       0       |
| DIRTY_STATE_TRANSIENT  |       1       |
| OP_CREATE              |       1       |
| OP_DELETE              |       3       |
| OP_NONE                |       0       |
| OP_UPDATE              |       2       |
| TRANSACTION_INDEX      | 'transaction' |

## Methods

```php
final public function __construct(
mixed $data = null, 
DiInterface $container = null,
ManagerInterface $modelsManager = null
)
```

Constructs the model object. The method accepts an array of data that are used to populate the object by internally using `assign`. Optionally you can pass a DI container and a Models Manager object. If they are not passed, the defaults will be used.

```php
public function __call(string $method, array $arguments): mixed
```

Handles method calls when a method is not implemented. Throws [Phalcon\Mvc\Model\Exception][mvc-model-exception] if the method doesn't exist

```php
public static function __callStatic(
string $method, 
array $arguments
): mixed
```

Handles method calls when a static method is not implemented. Throws [Phalcon\Mvc\Model\Exception][mvc-model-exception] if the method doesn't exist

```php
public function __get(string $property)
```

Magic method to get related records using the relation alias as a property

```php
public function __isset(string $property): bool
```

Magic method to check if a property is a valid relation

```php
public function __set(string $property, mixed $value)
```

Magic method to assign values to the model

```php
public function addBehavior(
BehaviorInterface $behavior
): void
```

Setup a behavior in a model

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Behavior\Timestampable;

class Invoices extends Model
{
public function initialize()
{
    $this->addBehavior(
        new Timestampable(
            [
                'onCreate' => [
                    'field'  => 'inv_created_at',
                    'format' => 'Y-m-d H:i:s',
                ],
            ]
        )
    );
}
}
```

```php
public function appendMessage(
MessageInterface $message
): ModelInterface
```

Appends a customized message on the validation process

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message as Message;

class Invoices extends Model
{
public function beforeSave()
{
    if (0 === $this->inv_status_flag) {
        $message = new Message(
            'Sorry, an invoice cannot be unpaid'
        );

        $this->appendMessage($message);
    }
}
}
```

```php
public function assign(
mixed $data, 
array $whiteList = null, 
array $dataColumnMap = null
): ModelInterface
```

Assign data to the model. The `data` parameter can be an array or a database row. The `whitelist` is an array of model properties that will be updated during the assignment process. Omitted properties will NOT be accepted even if they are included in the array or database row; nevertheless, if one of them is required by the model, the data will not be saved and the model will produce an error. The `dataColumnMap` is an array that maps columns from the `data` to the actual model. This helps when you want to map input from an array such as `$_POST` to fields in the database.

Assign values to a model from an array

```php
<?php

$invoice->assign(
[
    'inv_cst_id'      => 1,
    'inv_status_flag' => 1,
    'inv_title'       => 'Invoice for ACME Inc.',
    'inv_total'       => 100,
    'inv_created_at'  => '2019-12-25 01:02:03',
]
);
```

`assign` with a database row. - Requires a Column Map

```php
<?php

$invoice->assign(
$row,
null,
[
    'inv_cst_id'      => 'customerId',
    'inv_status_flag' => 'status',
    'inv_title'       => 'title',
    'inv_total'       => 'total',
]
);
```

Update only the `inv_status_flag`, `inv_title`, `inv_total` fields.

```php
<?php

$invoice->assign(
$_POST,
[
    'inv_status_flag',
    'inv_title',
    'inv_total',
]
);
```

By default `assign` will use setters if they exist, you can disable it by using `ini_set` to directly use properties

```php
ini_set('phalcon.orm.disable_assign_setters', true);
```

```php
<?php

$invoice->assign(
$_POST,
null,
[
    'inv_status_flag',
    'inv_title',
    'inv_total',
]
);
```

```php
public static function average(
mixed $parameters = null
): float
```

Returns the average value on a column for a result-set of rows matching the specified conditions

```php
<?php

use MyApp\Models\Invoices;

$average = Invoices::average(
[
    'column' => 'inv_total',
]
);

echo 'AVG: ', $average, PHP_EOL;

$average = Invoices::average(
[
    'inv_cst_id = 1',
    'column' => 'inv_total',
]
);

echo 'AVG [Customer: 1] ', $average, PHP_EOL;
```

```php
public static function cloneResult(
ModelInterface $base, 
array $data, 
int $dirtyState = 0
): ModelInterface
```

Assigns values to a model from an array returning a new model

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::cloneResult(
 new Invoices(),
[
    'inv_cst_id'      => 1,
    'inv_status_flag' => 0,
    'inv_title'       => 'Invoice for ACME Inc. #2',
    'inv_total'       => 400,
    'inv_created_at'  => '2019-12-25 01:02:03',
]
 );
```

```php
public static function cloneResultMap(
mixed $base, 
array $data, 
array $columnMap, 
int $dirtyState = 0, 
bool $keepSnapshots = null
): ModelInterface
```

Assign values to a model from an array, returning a new model, using the column map.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::cloneResultMap(
 new Invoices(),
 [
    'customerId' => 1,
    'status'     => 0,
    'title'      => 'Invoice for ACME Inc. #2',
    'total'      => 400,
    'created'    => '2019-12-25 01:02:03',
 ]
);
```

```php
public static function cloneResultMapHydrate(
array $data, 
array $columnMap, 
int $hydrationMode
): mixed
```

Returns a hydrated result based on the data and the column map

```php
public static function count(
mixed $parameters = null
): int
```

Returns a count of how many records match the specified conditions

```php
<?php

use MyApp\Models\Invoices;

$average = Invoices::count();

echo 'COUNT: ', $average, PHP_EOL;

$average = Invoices::count(
'inv_cst_id = 1'
);

echo 'COUNT [Customer: 1] ', $average, PHP_EOL;
```

```php
public function create(): bool
```

Inserts a model in the database. If the record exists in the database, `create()` will throw an exception. It will return `true` on success, `false` otherwise.

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();
$invoice->assign(
[
    'inv_cst_id'      => 1,
    'inv_status_flag' => 1,
    'inv_title'       => 'Invoice for ACME Inc.',
    'inv_total'       => 100,
    'inv_created_at'  => '2019-12-25 01:02:03',
]
);

$result = $invoice->create();
```

```php
public function delete(): bool
```

Deletes a model instance. Returning true on success or false otherwise.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');
$result  = $invoice->delete();

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 1,
    ]
]
);

foreach ($invoices as $invoice) {
$invoice->delete();
}
```

```php
public function dump(): array
```

Returns a simple representation of the object that can be used with `var_dump()`

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

var_dump(
$invoice->dump()
);
```

```php
public static function find(
mixed $parameters = null
): ResultsetInterface
```

Query for a set of records that match the specified conditions. `find()` is flexible enough to accept a variety of parameters to find the data required. You can check the [Finding Records][finding-records] section for more information.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::find();
```

```php
public static function findFirst(
mixed $parameters = null
): ModelInterface | null
```

Query the first record that matches the specified conditions. It will return a resultset or `null` if the record was not found.

:::warning[WARNING]
`findFirst()` no longer returns `false` if records were not found.
:::

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();
```

```php
public function fireEvent(string $eventName): bool
```

Fires an event, implicitly calls behaviors, and listeners in the events manager are notified

```php
public function fireEventCancel(string $eventName): bool
```

Fires an event, implicitly calls behaviors, and listeners in the events manager are notified. This method stops if one of the callbacks/listeners returns `false`

```php
public function getChangedFields(): array
```

Returns a list of changed values.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

print_r(
$invoice->getChangedFields()
); 
// []

$invoice->inv_total = 120;;

$invoice->getChangedFields();

print_r(
$invoice->getChangedFields()
);
// ['inv_total']
```

```php
public function getDirtyState(): int
```

Returns one of the `DIRTY_STATE_*` constants telling if the record exists in the database or not

```php
public function getMessages(
mixed $filter = null
): MessageInterface[]
```

Returns an array of validation messages

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

echo 'Error saving Invoice: ';

$messages = $invoice->getMessages();

foreach ($messages as $message) {
    echo $message . PHP_EOL;
}
} else {

echo 'Record Saved';

}
```

:::warning[WARNING]
`save()` no longer accepts parameters to set data. You can use `assign` instead.
:::

```php
public function getModelsManager(): ManagerInterface
```

Returns the models manager related to the entity instance

```php
public function getModelsMetaData(): MetaDataInterface
```

Returns the model's meta-data service related to the entity instance

```php
public function getOperationMade(): int
```

Returns the type of the latest operation performed by the ORM. Returns one of the `OP_*` class constants

```php
public function getOldSnapshotData(): array
```

Returns the internal old snapshot data

```php
final public function getReadConnection(): AdapterInterface
```

Gets the connection used to read data for the model

```php
final public function getReadConnectionService(): string
```

Returns the DependencyInjection connection service name used to read data related to the model

```php
public function getRelated(
string $alias, 
mixed $arguments = null
): Phalcon\Mvc\Model\Resultset\Simple | null
```

Returns related records based on defined relations. If the relationship is one to one and no records have been found, it will return `null`

:::warning[WARNING]
`getRelated()` no longer returns `false` if a record was not found on a one-to-one relationship.
:::

```php
<?php

use MyApp\Models\Customers;

$customer = Customers::findFirst('cst_id = 1');
$invoices = $customer->getRelated('invoices');
```

```php
public function isRelationshipLoaded(
string $relationshipAlias
): bool
```

Checks if saved related records have already been loaded. Only returns `true` if the records were previously fetched through the model without any additional parameters.

```php
<?php

use MyApp\Models\Customers;

$customer = Customers::findFirst('cst_id = 1');
$invoices = $customer->isRelationshipLoaded('invoices'); // false

$invoices = $customer->getRelated('invoices');
$invoices = $customer->isRelationshipLoaded('invoices'); // true
```

```php
final public function getSchema(): string
```

Returns schema name where the mapped table is located

```php
public function getSnapshotData(): array
```

Returns the internal snapshot data

```php
final public function getSource(): string
```

Returns the table name mapped in the model

```php
public function getUpdatedFields(): array
```

Returns a list of updated values.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

print_r(
$invoice->getChangedFields()
); 
// []

$invoice->inv_total = 120;;

$invoice->getChangedFields();

print_r(
$invoice->getChangedFields()
);
// ['inv_total']

$invoice->save();

print_r(
$invoice->getChangedFields()
);
// []

print_r(
$invoice->getUpdatedFields()
);
// ['inv_total']
```

```php
final public function getWriteConnection(): AdapterInterface
```

Gets the connection used to write data to the model

```php
final public function getWriteConnectionService(): string
```

Returns the DependencyInjection connection service name used to write data related to the model

```php
public function hasChanged(
string | array $fieldName = null, 
bool $allFields = false
): bool
```

Check if a specific attribute has changed. This only works if the model is keeping data snapshots

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->create();

$invoice->inv_total = 120;

$hasChanged = $invoice->hasChanged('inv_title');
// false
$hasChanged = $invoice->hasChanged(
[
    'inv_total',
]
);
// true
$hasChanged = $invoice->hasChanged(
[
    'inv_title', 
    'inv_total'
], 
true
);
// false
```

```php
public function hasSnapshotData(): bool
```

Check if the object has internal snapshot data

```php
public function hasUpdated(
string | array $fieldName = null, 
bool $allFields = false
): bool
```

Check if a specific attribute was updated. This only works if the model is keeping data snapshots.

```php
public function jsonSerialize(): array
```

Serializes the object for json_encode

```php
echo json_encode($invoice);
```

```php
public static function maximum(
mixed $parameters = null
): mixed
```

Returns the maximum value of a column for a result-set of rows that match the specified conditions

```php
<?php

use MyApp\Models\Invoices;

$id = Invoices::maximum(
[
    'column' => 'inv_id',
]
);

echo 'MAX: ', $id, PHP_EOL;

$max = Invoices::maximum(
[
    'inv_cst_id = 1',
    'column' => 'inv_total',
]
);

echo 'MAX [Customer: 1] ', $max, PHP_EOL;
```

```php
public static function minimum(
mixed parameters = null
): mixed 
```

Returns the minimum value of a column for a result-set of rows that match the specified conditions

```php
<?php

use MyApp\Models\Invoices;

$id = Invoices::minimum(
[
    'column' => 'inv_id',
]
);

echo 'MIN: ', $id, PHP_EOL;

$max = Invoices::minimum(
[
    'inv_cst_id = 1',
    'column' => 'inv_total',
]
);

echo 'MIN [Customer: 1] ', $max, PHP_EOL;
```

```php
public static function query(
DiInterface $container = null
): CriteriaInterface
```

Create criteria for a specific model

```php
public function readAttribute(
string $attribute
): mixed | null
```

Reads an attribute value by its name

```php
echo $invoice->readAttribute('inv_title');
```

```php
public function refresh(): ModelInterface
```

Refreshes the model attributes re-querying the record from the database

```php
public function save(): bool
```

Inserts or updates a model instance. Returning `true` on success or `false` otherwise.

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

$invoice = Invoices::findFirst('inv_id = 100');

$invoice->inv_total = 120;

$invoice->save();
```

:::warning[WARNING]
`save()` no longer accepts parameters to set data. You can use `assign` instead.
:::

```php
public function serialize(): string
```

Serializes the object ignoring connections, services, related objects or static properties

```php
public function unserialize(mixed $data)
```

Unserializes the object from a serialized string

```php
final public function setConnectionService(
string $connectionService
): void
```

Sets the DependencyInjection connection service name

```php
public function setDirtyState(
int $dirtyState
): ModelInterface | bool
```

Sets the dirty state of the object using one of the `DIRTY_STATE_*` constants

```php
public function setEventsManager(
EventsManagerInterface $eventsManager
)
```

Sets a custom events manager

```php
final public function setReadConnectionService(
string $connectionService
): void
```

Sets the DependencyInjection connection service name used to read data

```php
public function setOldSnapshotData(
array $data, 
array $columnMap = null
)
```

Sets the record's old snapshot data. This method is used internally to set old snapshot data when the model was set up to keep snapshot data

```php
public function setRelated(
string $alias, 
mixed $records
): ModelInterface
```

Stores related records in the relation cache, so that a subsequent `getRelated()` or property access returns them without querying. Does not stage the records to be saved; see [Model Relationships][db-models-relationships] for the difference.

```php
public function setSnapshotData(
array $data, 
array $columnMap = null
): void
```

Sets the record's snapshot data. This method is used internally to set snapshot data when the model was set up to keep snapshot data

```php
public function setTransaction(
TransactionInterface $transaction
): ModelInterface
```

Sets a transaction related to the Model instance

```php
<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

try {
$txManager   = new Manager();
$transaction = $txManager->get();

$customer = new Customers();
$customer->setTransaction($transaction);
$customer->cst_name_last  = 'Vader';
$customer->cst_name_first = 'Darth';

if (false === $customer->save()) {
    $transaction->rollback('Cannot save Customer');
}

$invoice = new Invoices();
$invoice->setTransaction($transaction);

$invoice->inv_cst_id      = $customer->cst_id;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

if (false === $invoice->save()) {
    $transaction->rollback('Cannot save record');
}

$transaction->commit();
} catch (Failed $ex) {
echo 'ERROR: ', $ex->getMessage();
}
```

```php
public static function setup(
array $options
): void
```

Enables/disables options in the ORM such as events, column renaming, etc.

```php
final public function setWriteConnectionService(
string $connectionService
): void
```

Sets the DependencyInjection connection service name used to write data

```php
public function skipOperation(bool $skip): void
```

Skips the current operation forcing a success state

```php
public static function sum(
array $parameters = null
): float
```

Calculates the sum on a column for a result-set of rows that match the specified conditions

```php
<?php

use MyApp\Models\Invoices;

$total = Invoices::sum(
[
    'column' => 'inv_total',
]
);

echo 'SUM: ', $total, PHP_EOL;

$total = Invoices::sum(
[
    'inv_cst_id = 1',
    'column' => 'inv_total',
]
);

echo 'SUM [Customer: 1] ', $total, PHP_EOL;
```

```php
public function toArray(
array $columns = null,
bool $useGetters = true
): array
```

Returns the instance as an array representation. Accepts an array with column names to include in the result, it will use getters by default.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

print_r(
$invoice->toArray()
);

//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.',
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]

print_r(
$invoice->toArray(
    [
        'inv_status_flag',
        'inv_title',
        'inv_total',
    ]
)
);

//  [
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.',
//      'inv_total'       = 100,
//  ]

```

`toArray` uses getters by default, to disable this behavior, set `$useGetters` to `false`

```php
<?php

use MyApp\Models\InvoicesGetters;

$invoice = InvoicesGetters::findFirst('inv_id = 4');

print_r(
$invoice->inv_title
);

// 'Invoice for ACME Inc.'

print_r(
$invoice->getInvTitle()
);

// 'Invoice for ACME Inc. - Status 1'

print_r(
$invoice->toArray()
);

//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc. - Status 1' ,
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]

print_r(
$invoice->toArray(
    null,
    false
)
);

//  [
//      'inv_id'          => 4,
//      'inv_cst_id'      = $customer->cst_id,
//      'inv_status_flag' = 1,
//      'inv_title'       = 'Invoice for ACME Inc.' ,
//      'inv_total'       = 100,
//      'inv_created_at'  = '2019-12-25 01:02:03',
//  ]
```

```php
public function update(): bool
```

Updates a model instance. If the instance doesn't exist in the persistence it will throw an exception. Returning `true` on success or `false` otherwise.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 4');

$invoice->inv_total = 120;

$invoice->update();
```

:::warning[WARNING]
When retrieving the record with `findFirst()`, you need to get the full object back (no `columns` definition) but also retrieve it using the primary key. If not, the ORM will issue an `INSERT` instead of `UPDATE`.
:::

```php
public function writeAttribute(
string $attribute, 
mixed $value
): void
```

Writes an attribute value by its name

```php
$invoice->writeAttribute('inv_total', 120);
```

```php
protected function allowEmptyStringValues(
array $attributes
): void
```

Sets a list of attributes that must be skipped from the generated `UPDATE` statement

```php
<?php 

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->allowEmptyStringValues(
        [
            'inv_created_at',
        ]
    );
}
}
```

```php
protected function belongsTo(
string | array $fields, 
string $referenceModel, 
string | array $referencedFields, 
array options = null
): Relation
```

Set up a reverse 1-1 or n-1 relation between two models

```php
<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class InvoicesXProducts extends Model
{
public function initialize()
{
    $this->belongsTo(
        'ixp_inv_id',
        Invoices::class,
        'inv_id'
    );
}
}
```

```php
protected function hasMany(
string | array $fields, 
string $referenceModel, 
string | array $referencedFields, 
array options = null
): Relation
```

Setup a 1-n relation between two models

```php
<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Customers extends Model
{
public function initialize()
{
    $this->hasMany(
        'cst_id',
        Invoices::class,
        'inv_cst_id'
    );
}
}
```

```php
protected function hasManyToMany(
string | array $fields,
string $intermediateModel, 
string | array $intermediateFields,
string | array $intermediateReferencedFields,
string $referenceModel, 
string | array $referencedFields,
array $options = null
): Relation
```

Set up an n-n relation between two models, through an intermediate relation

```php
<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->hasManyToMany(
        'inv_id',
        InvoicesXProducts::class,
        'ixp_inv_id',
        'ixp_prd_id',
        Products::class,
        'prd_id'
    );
}
}
```

```php
protected function hasOne(
string | array $fields, 
string $referenceModel, 
string | array $referencedFields, 
array options = null
): Relation
```

Setup a 1-1 relation between two models

```php
<?php 

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->hasOne(
        'inv_cst_id',
        Customers::class,
        'cst_id'
    );
}
}
```

```php
protected function keepSnapshots(
bool $keepSnapshot
): void
```

Sets if the model must keep the original record snapshot in memory

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

```php
final protected function setSchema(
string $schema
): ModelInterface
```

Sets schema name where the mapped table is located

```php
final protected function setSource(
string $source
): ModelInterface
```

Sets the table name to which the model should be mapped

```php
protected function skipAttributes(array $attributes)
```

Sets a list of attributes that must be skipped from the generated `INSERT`/`UPDATE` statement

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->skipAttributes(
        [
            'inv_created_at',
        ]
    );
}
}
```

```php
protected function skipAttributesOnCreate(
array $attributes
): void
```

Sets a list of attributes that must be skipped from the generated `INSERT` statement

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->skipAttributesOnCreate(
        [
            'inv_created_at',
        ]
    );
}
}
```

```php
protected function skipAttributesOnUpdate(
array $attributes
): void
```

Sets a list of attributes that must be skipped from the generated `UPDATE` statement

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->skipAttributesOnUpdate(
        [
            'inv_modified_at',
        ]
    );
}
}
```

```php
protected function useDynamicUpdate(
bool dynamicUpdate
): void
```

Sets if a model must use dynamic update instead of the all-field update

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

```php
protected function validate(
ValidationInterface $validator
): bool
```

Executes validators on every validation call

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Validation;
use Phalcon\Validation\Validator\ExclusionIn;

class Invoices extends Model
{
public function validation()
{
    $validator = new Validation();

    $validator->add(
        'inv_status_flag',
        new ExclusionIn(
            [
                'domain' => [
                    0,
                    1,
                ],
            ]
        )
    );

    return $this->validate($validator);
}
}
```

```php
public function validationHasFailed(): bool
```

Check whether validation process has generated any messages

## Creating Models

A model is a class that extends from [Phalcon\Mvc\Model][mvc-model]. Its class name should be in camel case notation:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{

}
```

By default, the model `MyApp\Models\Invoices` will map to the table `invoices`. If you want to manually specify another name for the mapped table, you can use the `setSource()` method:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->setSource('co_invoices');
}
}
```

The model `Invoices` now maps to `co_invoices` table. The `initialize()` method helps with setting up this model with a custom behavior i.e. a different table.

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

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

**Properties vs. Setters/Getters**

:::warning[WARNING]
The model class uses some properties internally for services. The names of those properties are reserved and cannot be used as fields in the database. Please keep that in mind when naming the fields of your tables. If there are collisions, your models will not update properly.

`container`, `dirtyState`, `dirtyRelated`, `errorMessages`, `modelsManager`, `modelsMetaData`, `related`, `operationMade`, `oldSnapshot`, `skipped`, `snapshot`, `transaction`, `uniqueKey`, `uniqueParams`, `uniqueTypes`
:::

Models can be implemented with public properties, meaning that each property can be read and updated from any part of the code that has instantiated that model class:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_id;
public $inv_cst_id;
public $inv_status_flag;
public $inv_title;
public $inv_total;
public $inv_created_at;
}
```

Another implementation is to use getter and setter functions, which control which properties are publicly available for that model.

The benefit of using getters and setters is that the developer can perform transformations and validation checks on the values set or retrieved for the model, which is impossible when using public properties.

Additionally, getters and setters allow for future changes without changing the interface of the model class. So if a field name changes, the only change needed will be in the private property of the model referenced in the relevant getter/setter and nowhere else in the code.

```php
<?php

namespace MyApp\Models;

use InvalidArgumentException;
use Phalcon\Mvc\Model;

class Invoices extends Model
{
protected $inv_id;
protected $inv_cst_id;
protected $inv_status_flag;
protected $inv_title;
protected $inv_total;
protected $inv_created_at;

public function getId(): int
{
    return (int) $this->inv_id;
}

public function getCustomerId(): int
{
    return (int) $this->inv_cst_id;
}

public function getStatus(): int
{
    return (int) $this->inv_status_flag;
}

public function getTitle(): string
{
    return (string) $this->inv_title;
}

public function getTotal(): float
{
    return (float) $this->inv_total;
}

public function getCreatedAt(): string
{
    return (string) $this->inv_created_at;
}

public function setCustomerId(int $customerId): Invoices
{
    $this->inv_cst_id = $customerId;

    return $this;
}

public function setStatus(int $status): Invoices
{
    $this->inv_status_flag = $status;

    return $this;
}

public function setTitle(string $title): Invoices
{
    $this->inv_title = $title;

    return $this;
}

public function setTotal(float $total): Invoices
{
    if ($total < 0) {
        throw new InvalidArgumentException(
            'Incorrect total'
        );
    }

    $this->inv_total = $total;

    return $this;
}

public function setCreatedAt(string $date): Invoices
{
    $this->inv_created_at = $date;

    return $this;
}
}
```

Public properties provide less complexity in development. However, getters/setters can heavily increase the testability, extensibility, and maintainability of applications. You will need to decide which strategy is best for you depending on the needs of the application. The ORM is compatible with both schemes of defining properties.

:::warning[WARNING]
Underscores in property names can be problematic when using getters and setters.
:::

:::warning[WARNING]
When using the getters/setters approach, you will need to define your properties as `protected`.
:::

If you use underscores in your property names, you must still use camel case in your getter/setter declarations for use with magic methods. (e.g. `$model->getPropertyName` instead of `$model->getProperty_name`, `$model->findByPropertyName` instead of `$model->findByProperty_name`, etc.).

The ORM expects camel case naming and underscores are commonly removed. It is therefore recommended to name your properties in the manner shown throughout the documentation. You can use a column map (as described above) to ensure proper mapping of your properties to their database counterparts.

### Setters During Hydration

When the ORM loads records from the database - through `find()`, `findFirst()`, or any other path that runs through `Phalcon\Mvc\Model::cloneResultMap()` - it hydrates each row into a model instance. By default, hydration assigns the column values directly to the model properties and does not call setter methods.

This default keeps setter side effects out of the load path. A setter that runs an ORM query would otherwise execute on every hydrated record, and a setter that queries its own model would recurse: each `findFirst()` call hydrates a record, which calls the setter, which calls `findFirst()` again.

To call setters during hydration, enable `orm.call_setters_on_hydration` (default `false`):

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Support\Settings;

Settings::set('orm.call_setters_on_hydration', true);

// Setters now run while records are hydrated by find() / findFirst()
$invoice = Invoices::findFirst('inv_id = 4');
```

The ORM calls `set` followed by the camel-cased column name. For the `inv_title` column it calls `setInvTitle()`:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_title;

public function setInvTitle(?string $title): void
{
    $this->inv_title = strtoupper((string) $title);
}
}
```

With `orm.call_setters_on_hydration` enabled, `findFirst()` stores the upper-cased title. With the default, the raw database value is stored.

Hydration setters are independent of `Phalcon\Mvc\Model::assign()`. `assign()` - used for mass assignment, for example from `$_POST` - calls setters by default, and that behavior is controlled by the separate `orm.disable_assign_setters` setting. Changing one setting does not affect the other.

:::warning[WARNING]
Calling setters during hydration runs user code for every loaded record. Avoid setters that issue ORM queries or other expensive operations when this setting is enabled.
:::

## Records To Objects

Every instance of a model represents a row in the table. You can access record data by reading object properties. For example, for a table 'co_customers' with the records:

```sql
mysql> select * from co_customers;
+--------+---------------+----------------+
| cst_id | cst_name_last | cst_name_first |
+--------+---------------+----------------+
|      1 | Vader         | Darth          |
|      2 | Skywalker     | Like           |
|      3 | Skywalker     | Leia           |
+--------+---------------+----------------+
3 rows in set (0.00 sec)
```

You could find a certain record by its primary key and then print its name:

```php
<?php

use MyApp\Models\Customers;

// cst_id = 3
$customer = Customers::findFirst(3);

// 'Leia'
echo $customer->cst_name_first;
```

Once the record is in memory, you can make modifications to its data and then save changes:

```php
<?php

use MyApp\Models\Customers;

// cst_id = 3
$customer = Customers::findFirst(3);

$customer->cst_name_last = 'Princess';

$customer->save();
```

As you can see, there is no need to use raw SQL statements. [Phalcon\Mvc\Model][mvc-model] provides high database abstraction for web applications, simplifying database operations.

## Finding Records

[Phalcon\Mvc\Model][mvc-model] also offers several methods for querying records.

A model query can be expressed in four ways, each with an intended lane:

- find-parameter arrays (`find()` / `findFirst()`) for simple lookups;
- `Phalcon\Mvc\Model\Query\Builder` as the canonical programmatic API;
- `Phalcon\Mvc\Model\Criteria` as request-bound convenience;
- raw PHQL via `Phalcon\Mvc\Model\Query` for everything else.

### `find`

The method returns a [Phalcon\Mvc\Model\Resultset][mvc-model-resultset], [Phalcon\Mvc\Model\Resultset\Complex][mvc-model-resultset-complex] or [Phalcon\Mvc\Model\Resultset\Simple][mvc-model-resultset-simple] collection of records even if the result returned is only one record.

The method accepts a variety of parameters to retrieve data:

```php
<?php

use MyApp\Models\Customers;

$invoice = Invoices::findFirst('inv_id = 3');
```

You can also pass a string with a `WHERE` clause. In the above example, we are getting the same record, instructing the ORM to give us a record with `inv_cst_id = 3`

The most flexible syntax is to pass an array with different parameters:

```php
<?php

use MyApp\Models\Customers;

$invoice = Invoices::findFirst(
[
    'inv_id = 3',
]
);
```

The first parameter of the array (without a key) is treated the same way as the example above (passing a string). The array accepts additional parameters that offer additional options to customize the find operation.

### `findFirst`

You could also use the `findFirst()` method to get only the first record matching the given criteria:

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();
```

Calling `findFirst` without a parameter will return the first record the ORM finds. Usually, this is the first record in the table.

```php
<?php

use MyApp\Models\Invoices;

// cst_id = 3
$invoice = Invoices::findFirst(3);
```

Passing a number will query the underlying model using the primary key matching the number parameter passed. If there is no primary key defined or there is a compound key, you will not get any results.

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('inv_id = 3');
```

You can also pass a string with a `WHERE` clause. In the above example, we are getting the same record, instructing the ORM to give us a record with `inv_cst_id = 3`

:::warning[WARNING]
If the primary key of the table is not numeric, use condition. See examples below.
:::

```php
$uuid = '5741bfd7-6870-40b7-adf6-cbacb515b9a9';
$invoice = Invoices::findFirst([
'uuid = ?0',
'bind' => [$uuid],
]);

// OR

$uuid = '5741bfd7-6870-40b7-adf6-cbacb515b9a9';
$invoice = Invoices::findFirst([
'uuid = :primary:',
'bind' => ['primary' => $uuid],
]);
```

:::warning[WARNING]
If you do not use bound parameters in your conditions, PHQL will create a new plan internally, therefore consuming more memory. Using bound parameters is highly recommended!
:::

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst('uuid = "5741bfd7-6870-40b7-adf6-cbacb515b9a9"');
```

### Parameters

:::warning[WARNING]
It is highly recommended to use the array syntax with `conditions` and `bind` to shield yourself from SQL injections, especially when the criteria come from user input. For more information check the [Binding Parameters][binding-parameters]` section.
:::

Both `find()` and `findFirst()` methods accept an associative array specifying the search criteria.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'inv_cst_id = 3',
    'order' => 'inv_total desc'
]
);
```

You can (and should) use the `conditions` and `bind` array elements which bind parameters to the query parameters. Using this implementation will ensure that your parameters are bound thus reducing the possibility of SQL injections:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'order'      => 'inv_total desc',
]
);
```

The available query options are:

**`bind`**

Bind is used together with `conditions`, by replacing placeholders and escaping values thus increasing security

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
[
    'conditions' => 'inv_id = :inv_id:',
    'bind'       => [
        'inv_id' => 3,
    ],
]
);
```

**`bindTypes`**

When binding parameters, you can use this option to define additional casting to the bound parameters increasing even more the security of your query.

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Column;

$invoice = Invoices::findFirst(
[
    'conditions' => 'inv_id = :inv_id:',
    'bind'       => [
        'inv_id' => 3,
    ],
    'bindTypes'  => [
        Column::BIND_PARAM_INT,
    ],
]
);
```

**`cache`**

Cache the resultset, reducing the continuous access to the relational system.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'cache'      => [
        'key'      => 'customer.3',
        'lifetime' => 84600,
    ],
    'order'      => 'inv_total desc',
]
);
```

**`columns`**

Return specific columns in the model.

:::info[NOTE]
When using this option an incomplete object is returned, and therefore you cannot call methods such as `update()`, `getRelated()` etc.
:::

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'columns'    => [
        'inv_id',
        'total' => 'inv_total'
    ],
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
]
);
```

The columns array can return the columns directly if only a value has been set for one of the array elements. However, if you choose to specify a key, it will be used as an alias for that field. In the above example, the `cst_name_first` is aliased as `first`.

**`conditions`**

Search conditions for the find operation. Is used to extract only those records that fulfill a specified criterion. By default, [Phalcon\Mvc\Model][mvc-model] assumes the first parameter is the conditions.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
]
);
```

**`eager`**

Pre-loads the named relations, so that accessing them on the returned records issues no further queries. The value is an array of relation paths. See [Eager Loading][models-relationships-eager] for nested paths, per-relation options and the full behavior.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_total > :total:',
    'bind'       => [
        'total' => 100,
    ],
    'eager'      => ['customer'],
]
);

foreach ($invoices as $invoice) {
echo $invoice->customer->cst_name_last;
}
```

**`for_update`**

With this option, [Phalcon\Mvc\Model][mvc-model] reads the latest available data, setting exclusive locks on each row it reads

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'for_update' => true,
]
);
```

**`group`**

Allows to collect data across multiple records and group the results by one or more columns `'group' => 'name, status'`

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'group'      => 'inv_status_flag',
]
);
```

**`hydration`**

Sets the hydration strategy to represent each returned record in the result

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'hydration' => Resultset::HYDRATE_OBJECTS,
]
);
```

**`limit`**

Limit the results of the query to results to certain range

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'limit'      => 10,
]
);
```

**`offset`**

Offset the results of the query by a certain amount

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'limit'      => 10,
    'offset'     => 100,
]
);
```

**`order`**

Is used to sort the resultset. Use one or more fields separated by commas.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions' => 'inv_cst_id = :cst_id:',
    'bind'       => [
        'cst_id' => 3,
    ],
    'order'      => 'inv_status_flag, inv_total desc',
]
);
```

**`shared_lock`**

With this option, [Phalcon\Mvc\Model][mvc-model] reads the latest available data, setting shared locks on each row it reads

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions'  => 'inv_cst_id = :cst_id:',
    'bind'        => [
        'cst_id' => 3,
    ],
    'shared_lock' => true,
]
);
```

### `query`

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
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::query()
->where('inv_cst_id = :cst_id:')
->andWhere('inv_total > :total:')
->bind(
    [
        'cst_id' => 3,
        'total'  => 1000,
    ]
)
->orderBy('inv_status_flag, inv_total desc')
->execute()
;
```

The static method `query()` returns a [Phalcon\Mvc\Model\Criteria][mvc-model-criteria] object that is friendly with IDE auto-complete.

All the queries are internally handled as [PHQL][db-phql] queries. PHQL is a high-level, object-oriented, and SQL-like language. This language offers more features to perform queries such as joining other models, group records, aggregations, etc.

### `findBy*`

You can use the `findBy<property-name>()` method. This method expands on the `find()` method mentioned above. It allows you to quickly perform a select query 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.

For the following model:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_cst_id;
public $inv_id;
public $inv_status_flag;
public $inv_title;
public $inv_created_at;
}
```

We have the properties `inv_cst_id`, `inv_id`, `inv_status_flag`, `inv_title`, `inv_created_at`. If we want to find all the invoices with `inv_total = 100` we can use:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions'  => 'inv_total = :total:',
    'bind'        => [
        'total' => 100,
    ],
]
);
```

but we can also use:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::findByInvTotal(100);
```

:::info[NOTE]
The property names are changed to camel case if they have underscores. `inv_total` becomes `InvTotal`
:::

You can also pass parameters in an array as the second parameter. These parameters are the same as the ones you can pass in the `find` method.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::findByInvTotal(
100,
[
    'order' => `inv_cst_id, inv_created_at`
]
);
```

:::info[NOTE]
The same magic-method mechanism also provides `countBy<property-name>()`, which returns a count instead of records. An unrecognized magic method throws `Phalcon\Mvc\Model\Exceptions\MethodNotFound`. Magic property reads (`$model->property`) resolve a relation alias first, then a `get<Property>()` getter, and raise an "undefined property" notice if neither exists.
:::

### `findFirstBy*`

Finally, you can use the `findFirstBy<property-name>()` method. This method expands on the `findFirst()` method mentioned above. It allows you to quickly perform a select 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.

For the following model:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Guestbook extends Model
{
public $id;
public $email;
public $name;
public $text;
}
```

We have the properties `id`, `email`, `name`, and `text`. If we want to find the guest book entry for `Darth Vader` we can:

```php
<?php

use MyApp\Models\Guestbook;

$guest = Guestbook::findFirst(
[
    'conditions'  => 'name = :name:',
    'bind'        => [
        'name' => 'Darth Vader',
    ],
]
);
```

but we can also use:

```php
<?php

use MyApp\Models\Guestbook;

$name  = 'Darth Vader';
$guest = Guestbook::findFirstByName($name);
```

:::info[NOTE]
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][mvc-model-resultset-simple]. This is an object that encapsulates all the functionality a resultset has, such as seeking, traversing, counting, etc.

These objects offer more than standard arrays. One of the greatest features of the [Phalcon\Mvc\Model\Resultset][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.

Some examples of traversing resultsets are:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find();

// foreach
foreach ($invoices as $invoice) {
echo $invoice->inv_title, PHP_EOL;
}

// while
$invoices->rewind();
while ($invoices->valid()) {
$invoice = $invoices->current();

echo $invoice->inv_title, PHP_EOL;

$invoices->next();
}

// count
echo count($invoices);
echo $invoices->count();

// seek
$invoices->seek(2);
$invoice = $invoices->current();

// array
$invoice = $invoices[5];

// array - isset
if (true === isset($invoices[3])) {
   $invoice = $invoices[3];
}

// First
$invoice = $invoices->getFirst();

// Last
$invoice = $invoices->getLast();
```

Phalcon's resultsets emulate scrollable cursors. You can get any row by accessing its position or seeking the internal pointer to a specific position.

:::info[NOTE]
Some database systems do not support scrollable cursors. This forces Phalcon 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.
:::

Storing large query results in memory will consume many resources, so a resultset reads its rows from the database one at a time by default. You can instruct Phalcon to keep a small resultset in memory instead - which removes the re-execution above when it is traversed more than once - by setting the `orm.resultset_prefetch_records` setup value to the largest number of rows worth holding. A resultset with more rows than that keeps reading one row at a time, and the default of `0` never holds one in memory. This can be done either in `php.ini` or in the model `setup()`. More information about this can be found in the [features](#model-features) section.

:::info[NOTE]
On a database system that cannot report a row count without running a second query - SQLite - enabling this setting costs one extra statement per resultset, because the number of rows has to be known before Phalcon can decide whether to hold it in memory.
:::

You can re-execute the underlying query at any time to update the resultset with fresh data from the database by calling `refresh()`. This is useful in long-running scripts or when you suspect the data may have changed since the resultset was last loaded. The internal pointer returns to the beginning, so the refreshed resultset can be traversed again from the first row.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(['conditions' => 'inv_status = "new"']);

echo count($invoices); // current count

// ... time passes, other processes may have inserted rows ...

$invoices->refresh();

echo count($invoices); // updated count
```

Note that resultsets can be serialized and stored in a cache backend. [Phalcon\Cache\Cache][cache] can help with that task. However, serializing data causes [Phalcon\Mvc\Model][mvc-model] to retrieve all the data from the database in an array, thus consuming more memory while this process takes place.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find();

file_put_contents(
'invoices.cache',
serialize($invoices)
);

$invoices = unserialize(
file_get_contents('invoices.cache')
);

foreach ($invoices as $invoice) {
echo $invoice->inv_title;
}
```

### Custom Resultsets

There are times when the application logic requires additional manipulation of the data as it is retrieved from the database. Previously, we would extend the model and encapsulate the functionality in a class in the model or a trait, returning to the caller usually an array of transformed data.

With custom resultsets, you no longer need to do that. The custom resultset will encapsulate the functionality, that otherwise would be in the model, and can be reused by other models, thus keeping the code [DRY][dry]. This way, the `find()` method will no longer return the default [Phalcon\Mvc\Model\Resultset][mvc-model-resultset], but instead the custom one. Phalcon allows you to do this by using the `getResultsetClass()` in your model.

First, we need to define the resultset class:

```php
<?php

namespace MyApp\Mvc\Model\Resultset;

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

class Custom extends Simple
{
public function calculate() {
    // ....
}
}
```

In the model, we set the class in the `getResultsetClass()` as follows:

```php
<?php

namespace Phalcon\Test\Models\Statistics;

use MyApp\Mvc\Model\Resultset\Custom;
use Phalcon\Mvc\Model;

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

public function getResultsetClass()
{
    return Custom::class;
}
}
```

and finally, in your code, you will have something like this:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions'  => 'inv_cst_id = :cst_id:',
    'bind'        => [
        'cst_id' => 3,
    ],
]
);

$calculated = $invoices->calculate();
```

### Custom Resultset Rows

A query that does not map every row to a single model returns `Phalcon\Mvc\Model\Row` instances. This happens when a query selects individual columns instead of a full model, or when it joins more than one model. `Phalcon\Mvc\Model\Query::setResultsetRowClass()` replaces that default row class with one of your own, so the rows can expose reusable methods.

The class must exist and must be a subclass of `Phalcon\Mvc\Model\Row`. A class that cannot be found throws `Phalcon\Mvc\Model\Query\Exceptions\ResultsetRowClassNotFound`. A class that is not a subclass of `Phalcon\Mvc\Model\Row` throws `Phalcon\Mvc\Model\Query\Exceptions\InvalidResultsetRowClass`.

First, define the row class:

```php
<?php

namespace MyApp\Mvc\Model\Row;

use Phalcon\Mvc\Model\Row;

class InvoiceRow extends Row
{
public function getFormattedTotal(): string
{
    return number_format((float) $this->readAttribute('inv_total'), 2);
}
}
```

Set the class on the query before it runs. A query created from PHQL that selects columns returns the custom row:

```php
<?php

use MyApp\Mvc\Model\Row\InvoiceRow;
use MyApp\Models\Invoices;

$phql  = 'SELECT inv_id, inv_total FROM ' . Invoices::class;
$query = $this->modelsManager->createQuery($phql);

$query->setResultsetRowClass(InvoiceRow::class);

$rows = $query->execute();

foreach ($rows as $row) {
echo $row->getFormattedTotal();
}
```

The query builder has its own `Phalcon\Mvc\Model\Query\Builder::setResultsetRowClass()`. It stores the class and forwards it to the query returned by `getQuery()`, so column selections and joins built through the builder are covered as well:

```php
<?php

use MyApp\Mvc\Model\Row\InvoiceRow;
use MyApp\Models\Customers;
use MyApp\Models\Invoices;

$builder = $this->modelsManager->createBuilder();

$builder
->columns([Customers::class . '.*', Invoices::class . '.*'])
->from(Customers::class)
->join(
    Invoices::class,
    Invoices::class . '.inv_cst_id = ' . Customers::class . '.cst_id'
)
->setResultsetRowClass(InvoiceRow::class)
;

$rows = $builder->getQuery()->execute();
```

The builder stores the class without validating it. The checks above run when the builder creates the query, so an unknown class or a class that is not a subclass of `Phalcon\Mvc\Model\Row` throws when `getQuery()` is called.

`Phalcon\Mvc\Model\Query::getResultsetRowClass()` and `Phalcon\Mvc\Model\Query\Builder::getResultsetRowClass()` return the configured class name. They return an empty string when the default `Phalcon\Mvc\Model\Row` is used.

The custom row class also flows through pagination. `Phalcon\Paginator\Adapter\QueryBuilder` builds its query from the builder passed to it, so a class set with `Phalcon\Mvc\Model\Query\Builder::setResultsetRowClass()` applies to the paginated rows. Every item returned by `getItems()` is an instance of the custom class:

```php
<?php

use MyApp\Mvc\Model\Row\InvoiceRow;
use MyApp\Models\Invoices;
use Phalcon\Paginator\Adapter\QueryBuilder;

$builder = $this->modelsManager->createBuilder();

$builder
->columns('inv_id, inv_total')
->from(Invoices::class)
->setResultsetRowClass(InvoiceRow::class)
;

$paginator = new QueryBuilder(
[
    'builder' => $builder,
    'limit'   => 20,
    'page'    => 1,
]
);

$page = $paginator->paginate();

foreach ($page->getItems() as $row) {
echo $row->getFormattedTotal();
}
```

### Filtering Resultsets

The most efficient way to filter data is by 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:

```php
<?php

$invoices = Invoices::find();

$invoices = $invoices->filter(
function ($invoice) {
    if (1 === $invoice->inv_status_flag) {
        return $invoice;
    }
}
);
```

The above example will return only the paid invoices from our table (`inv_status_flag = 1`);

### Binding Parameters

Bound parameters are also supported in [Phalcon\Mvc\Model][mvc-model]. You are encouraged to use this methodology to eliminate the possibility of your code being subject to SQL injection attacks. Both `string` and `integer` placeholders are supported.

:::info[NOTE]
When using `integer` placeholders you must prefix them with `?` (`?0`, `?1`). When using `string` placeholders you must enclose the string in `:` (`:name:`, `:total:`).
:::

Some examples:

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
[
    'conditions'  => 'inv_title LIKE :title: AND ' .
                     'inv_total > :total:',
    'bind'        => [
        'title' => '%ACME%',
        'total' => 1000,
    ],
]
);

$invoices = Invoices::find(
[
    'conditions'  => 'inv_title LIKE ?0 = ?0 AND ' .
                     'inv_total > ?1',
    'bind'        => [
        0 => '%ACME%',
        1 => 1000,
    ],
]
);

$invoices = Invoices::find(
[
    'conditions'  => 'inv_title = ?0 AND ' .
                     'inv_total > :total:',
    'bind'        => [
        0       => '%ACME%',
        'total' => 1000,
    ],
]
);
```

Strings are automatically escaped using [PDO][pdo-statements]. This function takes into account the connection charset, therefore it is 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 their data type:

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Db\Column;

$parameters = [
'title' => '%ACME%',
'total' => 1000,
];

$types = [
'title' => Column::BIND_PARAM_STR,
'total' => Column::BIND_PARAM_INT,
];

$invoices = Invoices::find(
[
    'conditions'  => 'inv_title LIKE :title: AND ' .
                     'inv_total > :total:',
    'bind'        => $parameters,
    'bindTypes'   => $types,
]
);
```

:::info[NOTE]
Since the default bind type is `Phalcon\Db\Column::BIND_PARAM_STR`, there is no need to specify the 'bindTypes' parameter if all the columns are strings
:::

You can also bind arrays in the parameters, especially when using the `IN` SQL keyword.

:::info[NOTE]
You need to use a zero-based array for arrays without missing elements
:::

```php
<?php

use MyApp\Models\Invoices;

$customerIds = [1, 3, 4]; // $array: [[0] => 1, [1] => 2, [2] => 4]

$invoices = Invoices::find(
[
    'conditions'  => 'inv_cst_id IN ({customerId:array})',
    'bind'        => [
        'customerId' => $customerIds,
    ],
]
);

unset($customerIds[1]);  // $array: [[0] => 1, [2] => 4]

$customerIds = array_values($customerIds);  // $array: [[0] => 1, [1] => 4]

$invoices = Invoices::find(
[
    'conditions'  => 'inv_cst_id IN ({customerId:array})',
    'bind'        => [
        'customerId' => $customerIds,
    ],
]
);
```

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

If you're using _finders_ e.g. `find()`, `findFirst()`, etc., you can inject the bound parameters when using the string syntax for the first parameter instead of using the `conditions` array element. Also, when using `findFirstBy*` the parameters are automatically bound.

```php
<?php

use MyApp\Models\Invoices;

$invoices = Invoices::find(
'inv_total > ?0',
'bind'        => [
    1000,
]
);

$invoices = Invoices::findByInvTotal(1000);
```

## Before/After Fetching

There are cases where we need to manipulate the data after it has been fetched from the database so that our model contains what we need in the application layer. As seen in the [events][events] document, models act as listeners, so we can implement some events as methods in the model.

Such methods include `beforeSave`, `afterSave`, and `afterFetch` as shown in our example below. The `afterFetch` method will run right after the data populates the model from the database. We can utilize this method to modify or transform the data in the model.

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_id;
public $inv_cst_id;
public $inv_status_flag;
public $inv_total;
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);
}
}
```

In the above example, we receive a comma-delimited string from the database and `explode` it to an array so that it can be used from our application. After that, you can add or remove elements in the array; before the model saves it, `implode` will be called to store the array as a string in the database.

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

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_id;
public $inv_cst_id;
public $inv_status_flag;
public $inv_total;
public $status;

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

## Calculations

Calculations (or aggregations) are helpers for commonly used functions of database systems such as `COUNT`, `SUM`, `MAX`, `MIN`, or `AVG`. [Phalcon\Mvc\Model][mvc-model] allows to use of these functions directly from the exposed methods.

**`COUNT`**

```php
<?php

$rowcount = Invoices::count();

// inv_cst_id = 3
$rowcount = Invoices::count(
[
    'inv_cst_id = ?0',
    'bind'        => [
        3,
    ],
]
);
```

We can also use the `group` parameter to group our results. The count results appear in the `rowcount` property of each object in the collection returned.

```php
<?php

$group = Invoices::count(
[
    'group' => 'inv_cst_id',
]
);
foreach ($group as $row) {
   echo 'Count: ', $row->rowcount, ' - Customer: ', $row->inv_cst_id;
}

$group = Invoices::count(
[
    'group' => 'inv_cst_id',
    'order' => 'rowcount',
]
);
```

**`SUM`**

```php
<?php

$total = Invoices::sum(
[
    'column' => 'inv_total',
]
);

$total = Invoices::sum(
[
    'column'     => 'total',
    'conditions' => 'inv_cst_id = ?0',
    'bind'       => [
        3
    ]
]
);
```

You can also group results. The count results appear in the `sumatory` property of each object in the collection returned.

```php
<?php

$group = Invoices::sum(
[
    'column' => 'inv_total',
    'group'  => 'inv_cst_id',
]
);

foreach ($group as $row) {
   echo 'Customer: ', $row->inv_cst_id, ' - Total: ', $row->sumatory;
}

$group = Invoices::sum(
[
    'column' => 'inv_total',
    'group'  => 'inv_cst_id',
    'order'  => 'sumatory DESC',
]
);
```

**`AVERAGE`**

```php
<?php

$average = Invoices::average(
[
    'column' => 'inv_total',
]
);

$average = Invoices::average(
[
    'column'     => 'inv_total',
    'conditions' => 'inv_status_flag = ?0',
    'bind'       => [
        0
    ]
]
);
```

:::info[NOTE]
For a non-grouped query, `sum()` and `average()` always return a `float`. When no rows match the conditions, the result is `0.0` (not `null`). `maximum()` and `minimum()` instead return the column value, or `null` when there are no matching rows.
:::

**`MAX` - `MIN`**

```php
<?php

$max = Invoices::maximum(
[
    'column' => 'inv_total',
]
);

$max = Invoices::maximum(
[
    'column'     => 'inv_total',
    'conditions' => 'inv_status_flag = ?0',
    'bind'       => [
        0
    ],
]
);

$min = Invoices::minimum(
[
    'column' => 'inv_total',
]
);

$min = Invoices::minimum(
[
    'column'     => 'inv_total',
    'conditions' => 'inv_status_flag = ?0',
    'bind'       => [
        0
    ],
]
);
```

## Creating - Updating

The `Phalcon\Mvc\Model::save()` method 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][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 created or updated.

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

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->save();

if (false === $result) {

echo 'Error saving Invoice: ';

$messages = $invoice->getMessages();

foreach ($messages as $message) {
    echo $message . PHP_EOL;
}
} else {

echo 'Record Saved';
}
```

You can also use the `assign()` method and pass an array of `field => value` elements, to avoid assigning each column manually. [Phalcon\Mvc\Model][mvc-model] will check if there are setters implemented for the columns passed in the array, giving priority to them, instead of directly the values of the attributes:

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->assign(
[
    'inv_cst_id'      => 1,
    'inv_status_flag' => 1,
    'inv_title'       => 'Invoice for ACME Inc.',
    'inv_total'       => 100,
    'inv_created_at'  => '2019-12-25 01:02:03',
]
);

$result = $invoice->save();
```

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
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->assign($_POST);

$result = $invoice->save();
```

:::danger[DANGER]
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 `assign` to set a whitelist of fields that are only taken into account when doing the mass assignment:

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->assign(
$_POST,
[
    'inv_cst_id',
    'inv_status_flag',
    'inv_title',
    'inv_total',
]
);

$result = $invoice->save();
```

:::info[NOTE]
On really busy applications, you can use `create` or `update` for the respective operations. By using those two methods instead of save, we ensure that data will be saved or not in the database, since those throw exceptions on `create` if the record already exists, and on `update` if the record does not exist.
:::

```php
<?php

use MyApp\Models\Invoices;

$invoice = new Invoices();

$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$result = $invoice->update();

if (false === $result) {

echo 'Error saving Invoice: ';

$messages = $invoice->getMessages();

foreach ($messages as $message) {
    echo $message . PHP_EOL;
}
} else {

echo 'Record Updated';

}
```

The methods `create` and `update` also accepts an array of values as parameters.

## Deleting

The `delete()` method allows you to delete a record. It returns a boolean signifying success or failure

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
[
    'conditions' => 'inv_id = :id:',
    'bind'       => [
        'id' => 4,
    ]
]
);

if (false !== $invoice) {
if (false === $invoice->delete()) {
    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Deleted';
}
}
```

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

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::find(
[
    'conditions' => 'inv_cst_id = :id:',
    'bind'       => [
        'id' => 3,
    ]
]
);

foreach ($invoices as $invoice) {
if (false === $invoice->delete()) {
    $messages = $invoice->getMessages();

    foreach ($messages as $message) {
        echo $message . PHP_EOL;
    }
} else {

    echo 'Record Deleted';
}
}
```

:::info[NOTE]
Check the [transactions][transactions] section on how you can delete all the records in a loop with one operation
:::

## Hydration Modes

As mentioned earlier, resultsets are collections of complete objects. This means that every returned result is an object, representing a row in the database. These documents can be modified and later on, saved to persist the changes in the database.

However, there are times that you will need to get the data in a read-only mode, such as in cases of viewing data. In these cases, it is useful to change the way the records are returned to save resources and increase performance. The strategy used to represent these objects returned in a resultset is called `hydration`.

Phalcon offers three ways of hydrating data:

| Result  | Mode                                           |
|---------|------------------------------------------------|
| Arrays  | `Phalcon\Mvc\Model\Resultset::HYDRATE_ARRAYS`  |
| Objects | `Phalcon\Mvc\Model\Resultset::HYDRATE_OBJECTS` |
| Records | `Phalcon\Mvc\Model\Resultset::HYDRATE_RECORDS` |

The default hydration mode is to return records (`HYDRATE_RECORDS`). We can change the hydration mode to get arrays or objects back. Changing the hydration mode to anything other than `HYDRATE_RECORDS` will return objects (or arrays) that have no connection to the database i.e. we will not be able to perform any operations on those objects such as `save()`, `create()`, `delete()` etc.

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::findFirst(
[
    'conditions' => 'inv_id = :id:',
    'bind'       => [
        'id' => 4,
    ]
]
);

// Array
$invoices->setHydrateMode(
Resultset::HYDRATE_ARRAYS
);

foreach ($invoices as $invoice) {
echo $invoice['inv_total'], PHP_EOL;
}

// \stdClass
$invoices->setHydrateMode(
Resultset::HYDRATE_OBJECTS
);

foreach ($invoices as $invoice) {
echo $invoice->inv_total, PHP_EOL;
}

// Invoices
$invoices->setHydrateMode(
Resultset::HYDRATE_RECORDS
);

foreach ($invoices as $invoice) {
echo $invoice->inv_total, PHP_EOL;
}
```

Hydration mode can also be passed as a parameter of `find`, `findFirst`, `findFirstBy*` etc.:

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Resultset;

$invoices = Invoices::findFirst(
[
    'hydration'  => Resultset::HYDRATE_ARRAYS,
    'conditions' => 'inv_id = :id:',
    'bind'       => [
        'id' => 4,
    ],
]
);

foreach ($invoices as $invoice) {
echo $invoice['inv_total'], PHP_EOL;
}
```

## Table Prefixes

If you want all your tables to have a certain prefix without setting the source in all models, you can use the [Phalcon\Mvc\Model\Manager][mvc-model-manager] and the method `setModelPrefix()`:

```php
<?php

namespace MyApp\Models;

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

class Invoices extends Model
{

}

$manager = new Manager();

$manager->setModelPrefix('co_');

$invoices = new Invoices(null, null, $manager);

echo $invoices->getSource(); // will return co_invoices
```

## Identity Columns

Some models may have identity columns. These columns usually are the primary key of the mapped table. [Phalcon\Mvc\Model][mvc-model] can recognize the identity column omitting it in the generated `INSERT` SQL statements, to allow the database system to correctly generate a new value for that field. After creating a new record, the identity field will always be registered with the value generated in the database system for it:

```php
<?php

$invoice->save();

echo $invoice->inv_id; // 4
```

[Phalcon\Mvc\Model][mvc-model] attempts to recognize the identity column from each table. However, depending on the database system, these columns might be serial columns, such as in the case of PostgreSQL, or `auto_increment` columns in the case of MySQL.

PostgreSQL uses sequences to generate automatically numeric values for the primary key. Phalcon tries to obtain the generated value from the sequence `table_field_seq`, for example: `co_invoices_id_seq`. If the sequence name is different, you can always use the `getSequenceName()` method in the model, instructing Phalcon the sequence it needs to use for the primary key:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function getSequenceName()
{
    return 'invoices_sequence_name';
}
}
```

## Skipping Columns

Depending on how you implement business rules or model rules in your database, certain fields could very well be ignored in database operations. For instance, if we have a `inv_created_date` in our model, we can instruct the database system to inject the current timestamp on it:

```php
CREATE TABLE co_invoices (
// ...
inv_created_at datetime DEFAULT CURRENT_TIMESTAMP
)
```

The code above (for MySQL) instructs the RDBMS to assign the current timestamp on the `inv_created_at` field when the record is created. We can therefore omit this field when creating a record. Similarly, we might want to ignore some fields when we are updating records.

To achieve this task we can use the `skipAttributes` (for any operation), `skipAttributesOnCreate` (create) or `skipAttributesOnUpdate` (update)

To tell [Phalcon\Mvc\Model][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:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->skipAttributes(
        [
            'inv_total',
            'inv_created_at',
        ]
    );

    $this->skipAttributesOnCreate(
        [
            'inv_created_at',
        ]
    );

    $this->skipAttributesOnUpdate(
        [
            'inv_modified_at',
        ]
    );
}
}
```

If you want to set default values in your model properties (such as the `inv_created_at`) you can use the [Phalcon\Db\RawValue][db-rawvalue]:

```php
<?php

use MyApp\Models\Invoices;
use Phalcon\Db\RawValue;

$invoice = new Invoices();
$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = new RawValue('default');

$invoice->create();
```

We can also take advantage of the `beforeCreate` event in the model to assign the default value there:

```php
<?php

namespace MyApp\Models;

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

class Invoices extends Model
{
public function beforeCreate()
{
    $this->inv_created_at = new RawValue('default');
}
}
```

:::warning[WARNING]
Never use a [Phalcon\Db\RawValue][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 for SQL injection attacks.
:::

## Dynamic Updates

:::warning[WARNING]
Dynamic Update is now enabled by default, you can disable it by changing the "phalcon.orm.dynamic_update" in the php.ini or by ini_set.
:::

If the system-wide dynamic update is disabled, the 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, only 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, especially when the target table has blob/text fields:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

## 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.

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_id;
public $inv_cst_id;
public $inv_status_flag;
public $inv_title;
public $inv_total;
public $inv_created_at;

public function columnMap()
{
    return [
        'inv_id'          => 'id',
        'inv_cst_id'      => 'customerId',
        'inv_status_flag' => 'status',
        'inv_title'       => 'title',
        'inv_total'       => 'total',
        'inv_created_at'  => 'createdAt',
    ];
}
}
```

:::info[NOTE]
In the array defined in the column map, the keys are the actual names of the fields in the database, and the values are the _virtual_ fields we can use in your code
:::

Now we can use those _virtual_ fields (or column maps) in your code:

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst(
[
    'conditions' => 'inv_id = :id:',
    'bind'       => [
        'id' => 4,
    ]
]
);

echo $invoice->customerId, PHP_EOL,
 $invoice->total, PHP_EOL,
 $invoice->createdAt, PHP_EOL;

$invoices = Invoices::find(
[
    'order' => 'createdAt DESC',
]
);

foreach ($invoices as $invoice) {
echo $invoice->customerId, PHP_EOL,
     $invoice->total, PHP_EOL,
     $invoice->createdAt, PHP_EOL;
}

$invoice = new Invoices();

$invoice->customerId = 1;
$invoice->status     = 1;
$invoice->title      = 'Invoice for ACME Inc.';
$invoice->total      = 100;
$invoice->createdAt  = '2019-12-25 01:02:03';

$invoice->save();
```

**Considerations**

Consider the following when renaming your columns:

* References to attributes in relationships/validators **must use the virtual names**
* Refer the real column names will result in an exception by the ORM

The independent column map allows you to:

* Write applications using your own conventions
* Eliminate vendor prefixes/suffixes in your code
* Change column names without changing your application code

## Record Snapshots

Specific models could be set to maintain a record snapshot when they are queried. You can use this feature to implement auditing or to know what fields have been changed in the model compared to the data in the database.

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

When activating this feature, the application consumes a bit more memory, to keep track of the original values obtained from the database. In models that have this feature activated, you can check what fields changed as follows:

```php
<?php

use MyApp\Models\Invoices;

$invoice = Invoices::findFirst();

$invoice->inv_total = 120;

var_dump($invoice->getChangedFields()); // ['inv_total']

var_dump($invoice->hasChanged('inv_total')); // true

var_dump($invoice->hasChanged('inv_cst_id')); // false
```

Snapshots are updated on model creation/update. Using `hasUpdated()` and `getUpdatedFields()` can be used to check if fields were updated after a create/save/update, but it could potentially cause problems to your application if you execute `getChangedFields()` in `afterUpdate()`, `afterSave()` or `afterCreate()`.

You can disable this functionality by using:

```php
<?php

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

or if you prefer set this in your `php.ini`

```ini
phalcon.orm.update_snapshot_on_save = 0
```

Using this functionality will have the following effect:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public $inv_id;
public $inv_cst_id;
public $inv_status_flag;
public $inv_title;
public $inv_total;
public $inv_created_at;

public function initialize()
{
    $this->keepSnapshots(true);
}
}

$invoice = new Invoices();

$invoice->inv_id          = 1234;
$invoice->inv_cst_id      = 1;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

$invoice->create();

var_dump(
$invoice->getChangedFields() // []
);

$invoice->inv_total = 120;

var_dump(
$invoice->getChangedFields() // ['inv_total']
);

$invoice->update();

var_dump(
$invoice->getChangedFields() // []
);
```

`getUpdatedFields()` will properly return updated fields or as mentioned above you can go back to the previous behavior by setting the relevant ini value.

## Events

As mentioned before [Phalcon\Mvc\Model][mvc-model] acts as a listener to events. Therefore, all the events that the model is listening to can be implemented as methods in the model itself. You can check the [events][events] document for additional information.

The events supported are:

- `afterCreate`
- `afterDelete`
- `afterFetch`
- `afterSave`
- `afterUpdate`
- `afterValidation`
- `afterValidationOnCreate`
- `afterValidationOnUpdate`
- `beforeDelete`
- `beforeCreate`
- `beforeSave`
- `beforeUpdate`
- `beforeValidation`
- `beforeValidationOnCreate`
- `beforeValidationOnUpdate`
- `notDeleted`
- `notSaved`
- `onValidationFails`
- `prepareSave`
- `validation`

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;
use Phalcon\Messages\Message as Message;

class Invoices extends Model
{
public function beforeSave()
{
    if (0 === $this->inv_status_flag) {
        $message = new Message(
            'Sorry, an invoice cannot be unpaid'
        );

        $this->appendMessage($message);
    }
}
}
```

## Transactions

[Transactions][db-models-transactions] are necessary to ensure data integrity, when we need to insert or update data in more than one table during the same operation. [Phalcon\Mvc\Model][mvc-model] exposes the `setTransaction` method that allows you to bind each model to an active transaction.

```php
<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

try {
$txManager   = new Manager();
$transaction = $txManager->get();

$customer = new Customers();
$customer->setTransaction($transaction);
$customer->cst_name_last  = 'Vader';
$customer->cst_name_first = 'Darth';

if (false === $customer->save()) {
    $transaction->rollback('Cannot save Customer');
}

$invoice = new Invoices();
$invoice->setTransaction($transaction);

$invoice->inv_cst_id      = $customer->cst_id;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

if (false === $invoice->save()) {
    $transaction->rollback('Cannot save record');
}

$transaction->commit();
} catch (Failed $ex) {
echo 'ERROR: ', $ex->getMessage();
}
```

You can also include _finder_ results in your transactions or even have multiple transactions running at the same time:

```php
<?php

use MyApp\Models\Customers;
use MyApp\Models\Invoices;
use Phalcon\Mvc\Model\Transaction\Manager;
use Phalcon\Mvc\Model\Transaction\Failed;

try {
$txManager   = new Manager();
$transaction = $txManager->get();

$customer = new Customers();
$customer->setTransaction($transaction);
$customer->cst_name_last  = 'Vader';
$customer->cst_name_first = 'Darth';

if (false === $customer->save()) {
    $transaction->rollback('Cannot save Customer');
}

$average = Invoices::average(
    [
        Model::TRANSACTION_INDEX => $transaction,
        'column'     => 'inv_total',
        'conditions' => 'inv_cst_id = :customerId:',
        'bind'       => [
            'customerId' => 3,
        ],
    ]
);

$invoice = new Invoices();
$invoice->setTransaction($transaction);

$invoice->inv_cst_id      = $customer->cst_id;
$invoice->inv_status_flag = 1;
$invoice->inv_title       = 'Invoice for ACME Inc.';
$invoice->inv_total       = 100 + $average;
$invoice->inv_created_at  = '2019-12-25 01:02:03';

if (false === $invoice->save()) {
    $transaction->rollback('Cannot save record');
}

$transaction->commit();
} catch (Failed $ex) {
echo 'ERROR: ', $ex->getMessage();
}
```

## Changing Schema

If a model is mapped to a table that is located in a different schema than the default, you can use the `setSchema()` to point to the correct location:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function initialize()
{
    $this->setSchema('invoices');
}
}
```

## Multiple Databases

Phalcon models by default connect to the same database connection (`db` service) that has been defined in the dependency injection container. However, you might need to connect specific models to different connections, which could be connections to different databases.

We can define which model connects to which database in the `initialize` method of each model:

```php
<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Adapter\Pdo\PostgreSQL;

$container = new FactoryDefault();

// MySQL
$container->set(
'dbMysql',
function () {
    return new Mysql(
        [
            'host'     => 'localhost',
            'username' => 'root',
            'password' => 'secret',
            'dbname'   => 'tutorial',
        ]
    );
},
true
);

// PostgreSQL
$container->set(
'dbPostgres',
function () {
    return new PostgreSQL(
        [
            'host'     => 'localhost',
            'username' => 'postgres',
            'password' => '',
            'dbname'   => 'tutorial',
        ]
    );
}
);
```

and in the `initialize()` method:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

Additional flexibility is available regarding database connections. You can specify a different connection for `read` operations and a different one for `write` operations. This is particularly useful when you have memory databases that can be used for read operations and different databases that are used for `write` operations.

You can set two different connections and utilize each database in each model transparently

```php
<?php

use Phalcon\Di\FactoryDefault;
use Phalcon\Db\Adapter\Pdo\Mysql;
use Phalcon\Db\Adapter\Pdo\PostgreSQL;

$container = new FactoryDefault();

// MySQL - read
$container->set(
'mysqlRead',
function () {
    return new Mysql(
        [
            'host'     => '10.0.4.100',
            'username' => 'root',
            'password' => 'secret',
            'dbname'   => 'tutorial',
        ]
    );
},
true
);

// MySQL - write
$container->set(
'mysqlWrite',
function () {
    return new Mysql(
        [
            'host'     => '10.0.4.200',
            'username' => 'root',
            'password' => 'secret',
            'dbname'   => 'tutorial',
        ]
    );
},
true
);
```

and in the `initialize()` method:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

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

    $this->setWriteConnectionService('mysqlWrite');
}
}
```

The ORM also provides Horizontal Sharding features, by allowing you to implement a `shard` selection according to the query conditions:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
/**
 * Dynamically selects a shard
 *
 * @param array $intermediate
 * @param array $bindParams
 * @param array $bindTypes
 *
 * @return Phalcon\Db\Adapter\AdapterInterface
 */
public function selectReadConnection(
    array $intermediate, 
    array $bindParams, 
    array $bindTypes
) {
    if (true === 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');
}
}
```

In the above example, we are checking the `$intermediate` array, which is an array constructed internally in Phalcon, offering the intermediate representation of the query. We check if we have any `where` conditions. If not, we use the default shard `dbShard0`.

If conditions have been defined, we are checking if we have the `id` as a field in the conditions, and retrieve its value. If the `id` is between `0` and `100000` then we use `dbShard1`, alternatively `dbShard2`.

The `selectReadConnection()` method is called every time we need to get data from the database, and returns the correct connection to be used.

### Sticky Connections

When you split reads and writes across two connections, a read issued immediately after a write can reach the read connection before it reflects the change, returning stale data or missing a row you just inserted. To avoid this within a single request, the models manager offers an opt-in *sticky* mode.

When sticky is enabled and a model performs a write (`insert`, `update` or `delete`) during the request cycle, any subsequent read for that model's write connection service is served from the **write** connection instead of the read connection. This guarantees that data written earlier in the request can be read back immediately.

Sticky is disabled by default, so the standard read/write split is preserved unless you turn it on. Enable it on the models manager:

```php
<?php

use Phalcon\Mvc\Model\Manager;

/** @var Manager $manager */
$manager = $container->get('modelsManager');

$manager->setSticky(true);
```

With sticky active, a read after a write is routed to the write connection:

```php
<?php

$invoice            = new Invoices();
$invoice->inv_title = 'Sticky invoice';
$invoice->save();               // executed on the write connection

// Because a write happened during this request, the following read is
// served from the write connection, so the new row is guaranteed to be found.
$invoices = Invoices::find();
```

A few things to keep in mind:

- Tracking is scoped to the **write connection service**. Writing through one model only makes reads sticky for models that share the same write service; models on a different write service are unaffected.
- A model bound to a transaction still uses the transaction connection, which always takes precedence over sticky routing.
- The sticky state lives on the models manager for the duration of the request. In a traditional PHP-FPM setup a fresh manager is created on every request, so nothing needs to be reset. In long-running runtimes (e.g. Swoole or RoadRunner) where the manager instance is reused across requests, call `resetConnectionState()` between requests to clear the tracking:

```php
<?php

$manager->resetConnectionState();
```

Writes are recorded internally through the manager's `registerWrite()` method, which the model calls automatically after a successful `insert`, `update` or `delete`; you do not normally need to call it yourself.

## Dependency Injection

[Phalcon\Mvc\Model][mvc-model] is tightly bound to the DI container. You can retrieve the container by using the `getDI` method. Therefore, you have access to all services registered in the DI container.

The following example shows you how you can print any messages generated by an unsuccessful `save` operation in the model and show these messages in the [flash][flash] messenger. To do this, we use the `notSaved` event:

```php
<?php

namespace MyApp\Models;

use Phalcon\Mvc\Model;

class Invoices extends Model
{
public function notSaved()
{
    $flash    = $this->getDI()->getFlash();
    $messages = $this->getMessages();

    foreach ($messages as $message) {
        $flash->error($message);
    }
}
}
```

## Model Features

The ORM has several options that control specific behaviors globally. You can enable or disable these features by adding specific lines to your `php.ini` file, using the `setup` static method on the model, or using `Phalcon\Support\Settings` for a PHP-level override that does not call `ini_set()`.

```ini
phalcon.orm.column_renaming = false
phalcon.orm.events          = false
```

or by using the `Model`:

```php
<?php

use Phalcon\Mvc\Model;

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

:::info[NOTE]
`Model::setup()` writes these flags to process-global `Phalcon\Support\Settings`. They affect every model in the process at once, so call `setup()` once during bootstrap. It is not per-model or per-container configuration.
:::

The available options are:

| Option                          | Default | Description                                                            |
|---------------------------------|:-------:|------------------------------------------------------------------------|
| `caseInsensitiveColumnMap`      | `false` | Case insensitive column map                                            |
| `castLastInsertIdToInt`         | `false` | Casts the `lastInsertId` to an integer                                 |
| `castOnHydrate`                 | `false` | Automatic cast to original types on hydration                          |
| `columnRenaming`                | `true`  | Column renaming                                                        |
| `disableAssignSetters`          | `false` | Disable setters                                                        |
| `enableImplicitJoins`           | `true`  | Enable implicit joins                                                  |
| `events`                        | `true`  | Callbacks, hooks, and event notifications from all the models          |
| `exceptionOnFailedMetaDataSave` | `false` | Throw an exception when there is a failed meta-data save               |
| `exceptionOnFailedSave`         | `false` | Throw an exception when there is a failed `save()`                     |
| `forceCasting`                  | `false` | Force casting bound parameters to their native types                   |
| `ignoreUnknownColumns`          | `false` | Ignore unknown columns on the model                                    |
| `lateStateBinding`              | `false` | Late state binding of the `Phalcon\Mvc\Model::cloneResultMap()` method |
| `notNullValidations`            | `true`  | Automatically validate the not `null` columns present                  |
| `phqlLiterals`                  | `true`  | Literals in the PHQL parser                                            |
| `prefetchRecords`               |   `0`   | Largest resultset, in rows, held in memory (`0` = read row by row)     |                       
| `updateSnapshotOnSave`          | `true`  | Update snapshots on `save()`                                           |
| `virtualForeignKeys`            | `true`  | Virtual foreign keys                                                   |

`ini` options:

```
; phalcon.orm.cache_level = 3
; phalcon.orm.case_insensitive_column_map = false
; phalcon.orm.cast_last_insert_id_to_int = false
; phalcon.orm.cast_on_hydrate = false
; phalcon.orm.column_renaming = true
; phalcon.orm.disable_assign_setters = false
; phalcon.orm.enable_implicit_joins = true
; phalcon.orm.enable_literals = true
; phalcon.orm.events = true
; phalcon.orm.exception_on_failed_metadata_save = true
; phalcon.orm.exception_on_failed_save = false
; phalcon.orm.force_casting = false
; phalcon.orm.ignore_unknown_columns = false
; phalcon.orm.late_state_binding = false
; phalcon.orm.not_null_validations = true
; phalcon.orm.resultset_prefetch_records = "0"
; phalcon.orm.unique_cache_id = 3
; phalcon.orm.update_snapshot_on_save = true
; phalcon.orm.virtual_foreign_keys = true
; phalcon.db.escape_identifiers = On
; phalcon.db.force_casting = Off
```

**Using `Phalcon\Support\Settings`**

`Phalcon\Support\Settings` provides PHP-level overrides for all framework settings. Unlike `ini_set()`, changes made with `Settings::set()` are stored in a static array and never modify the underlying `php.ini` configuration, which prevents settings from one project from leaking into another sharing the same PHP process.

```php
<?php

use Phalcon\Support\Settings;

// Override a setting at the PHP level
Settings::set('orm.column_renaming', false);
Settings::set('orm.events', false);

// Read the effective value (PHP override → php.ini → compiled default)
var_dump(Settings::get('orm.events')); // bool(false)

// Clear all PHP-level overrides and revert to php.ini / compiled defaults
Settings::reset();
```

The `get()` method follows this resolution order: PHP-level override → `ini_get('phalcon.<key>')` → compiled-in default.

:::warning[WARNING]
`Phalcon\Mvc\Model::assign()` (which is used also when creating/updating/saving model) is always using setters if they exist when data arguments are passed, even when it's required or necessary. This will add some additional overhead to your application. You can change this behavior by adding `phalcon.orm.disable_assign_setters = 1` to your ini file, it will use `$this->property = value`.
:::

## Integers vs. Strings

If you want to get integer values back from `int` related database fields, you will need to do the following:

- Make sure that the `castOnHydrate` (or set `ini_set('phalcon.orm.cast_on_hydrate', 'on')`) is enabled

```php
<?php

use Phalcon\Mvc\Model;

Model::setup(
[
    'castOnHydrate' => true,
]
);
```

- Ensure that you are using the `mysqlnd` driver on your server. You can check that using `phpinfo()` (pdo_mysql > Client API version)
- In your database connection provider you need to pass the following options:

```php
[
PDO::ATTR_EMULATE_PREPARES  => false,
PDO::ATTR_STRINGIFY_FETCHES => false,
]
```

Registering the provider should look like this:

```php 
<?php

$parameters = [
  // ....
];

/** @var Manager $eventsManager */
$eventsManager = $container->getShared('eventsManager');

$container->setShared(
'db',
function () use ($eventsManager, $parameters) {
    $options = [
        'host'     => $parameters['host'] ?? 'localhost',
        'dbname'   => $parameters['dbname'] ?? 'phalcon',
        'username' => $parameters['user'] ?? 'root',
        'password' => $parameters['pass'] ?? 'secret',
        'encoding' => $parameters['encoding'] ?? 'utf8',
        'options'  => [
            PDO::ATTR_EMULATE_PREPARES  => false,
            PDO::ATTR_STRINGIFY_FETCHES => false,
        ]
    ];

    $connection = new Mysql($options);
    $connection->setEventsManager($eventsManager);

    return $connection;
}
);
```

## Spatial Column Hydration

When `orm.cast_on_hydrate` is enabled, spatial columns are hydrated into `Phalcon\Db\Geometry` value objects as records are read. The model reads the column type from the metadata and decodes the raw value with `Phalcon\Db\Geometry\WkbParser`.

- MySQL returns SRID-prefixed WKB, decoded on read
- PostgreSQL (PostGIS) returns EWKB, decoded on read
- SQLite has no native geometry type

With casting disabled, the attribute keeps the raw database value. A value that cannot be decoded is left unchanged.

```php
<?php

use Phalcon\Mvc\Model;

Model::setup(
[
    'castOnHydrate' => true,
]
);

$place = Places::findFirst("id = 1");

echo get_class($place->location); // Phalcon\Db\Geometry\Point
echo $place->location->getX();    // 1
echo $place->location->getY();    // 2
echo $place->location->toWkt();   // POINT(1 2)
```

The value-object classes are described under [Spatial / Geometry Columns](/5.19/db-layer/) in the database-layer documentation.

## Invalid parameter number

The parameters used to instantiate PDO use the default settings. Therefore, `PDO::ATTR_EMULATE_PREPARES` defaults to `true`.

However, if you encounter the following message in your code, when a bound parameter is used more than once in a query:

:::warning[Error]
Invalid parameter number
:::

You can check the PDO parameters and ensure that `PDO::ATTR_EMULATE_PREPARES` is set to `true`, when registering your database provider.

## Stand-Alone Component

You can use [Phalcon\Mvc\Model][mvc-model] on its own, performing the necessary setup on your own if you wish. The example below demonstrates how you can achieve that.

```php
<?php

use Phalcon\Di\Di;
use Phalcon\Mvc\Model;
use Phalcon\Mvc\Model\Manager;
use Phalcon\Db\Adapter\Pdo\Sqlite;
use Phalcon\Mvc\Model\Metadata\Memory;

$container = new Di();

$container->set(
'db',
new Sqlite(
    [
        'dbname' => 'sample.db',
    ]
)
);

$container->set(
'modelsManager',
new Manager()
);

$container->set(
'modelsMetadata',
new Memory()
);

class Invoices extends Model
{

}

echo Invoices::count();
```

## Exceptions

Any exceptions thrown in the `Phalcon\Mvc\Model` namespace will be of type `Phalcon\Mvc\Model\Exception`. You can use this exception to selectively catch exceptions thrown only from this component.

### Granular Exceptions

The component raises granular subclasses of `Phalcon\Mvc\Model\Exception` so callers can catch a specific failure mode. Existing `catch (Phalcon\Mvc\Model\Exception $e)` blocks continue to work unchanged.

| Class                                                          | Parent                        | Thrown when                                                                           |
|----------------------------------------------------------------|-------------------------------|---------------------------------------------------------------------------------------|
| `Phalcon\Mvc\Model\Exceptions\BelongsToRequiresObject`         | `Phalcon\Mvc\Model\Exception` | A `belongsTo` relation is resolved but the referenced value is not an object.         |
| `Phalcon\Mvc\Model\Exceptions\BindTypeNotDefined`              | `Phalcon\Mvc\Model\Exception` | A bind parameter references a type that has no PDO type mapping.                      |
| `Phalcon\Mvc\Model\Exceptions\CannotResolveAttribute`          | `Phalcon\Mvc\Model\Exception` | An attribute referenced by a relation cannot be resolved.                             |
| `Phalcon\Mvc\Model\Exceptions\ColumnNotInMap`                  | `Phalcon\Mvc\Model\Exception` | A column referenced via the column map is missing.                                    |
| `Phalcon\Mvc\Model\Exceptions\ColumnNotInTableColumns`         | `Phalcon\Mvc\Model\Exception` | A column is not present in the table's column list returned by the metadata.          |
| `Phalcon\Mvc\Model\Exceptions\ColumnNotInTableMap`             | `Phalcon\Mvc\Model\Exception` | A column is not present in the model's mapping table.                                 |
| `Phalcon\Mvc\Model\Exceptions\CorruptColumnType`               | `Phalcon\Mvc\Model\Exception` | A column has a type that the bind-type resolver cannot recognize.                     |
| `Phalcon\Mvc\Model\Exceptions\CursorIsImmutable`               | `Phalcon\Mvc\Model\Exception` | A write is attempted on a cursor that does not support mutation.                      |
| `Phalcon\Mvc\Model\Exceptions\DataTypeNotDefined`              | `Phalcon\Mvc\Model\Exception` | A column lacks a data-type definition in the metadata.                                |
| `Phalcon\Mvc\Model\Exceptions\HandlerMustImplementBindable`    | `Phalcon\Mvc\Model\Exception` | A controller used for bound-model resolution does not implement `BindModelInterface`. |
| `Phalcon\Mvc\Model\Exceptions\IdentityNotInColumnMap`          | `Phalcon\Mvc\Model\Exception` | The identity column is missing from the column map.                                   |
| `Phalcon\Mvc\Model\Exceptions\IdentityNotInTableColumns`       | `Phalcon\Mvc\Model\Exception` | The identity column is missing from the table's column list.                          |
| `Phalcon\Mvc\Model\Exceptions\IndexNotInCursor`                | `Phalcon\Mvc\Model\Exception` | A numeric index has no value in the cursor row.                                       |
| `Phalcon\Mvc\Model\Exceptions\IndexNotInRow`                   | `Phalcon\Mvc\Model\Exception` | A row does not contain the requested index.                                           |
| `Phalcon\Mvc\Model\Exceptions\InvalidConnectionService`        | `Phalcon\Mvc\Model\Exception` | The configured connection service does not produce an `AdapterInterface`.             |
| `Phalcon\Mvc\Model\Exceptions\InvalidContainer`                | `Phalcon\Mvc\Model\Exception` | The DI container injected into the model is not a `DiInterface`.                      |
| `Phalcon\Mvc\Model\Exceptions\InvalidDumpResultKey`            | `Phalcon\Mvc\Model\Exception` | A `dumpResult()` key is not a string.                                                 |
| `Phalcon\Mvc\Model\Exceptions\InvalidFindParameters`           | `Phalcon\Mvc\Model\Exception` | The parameters passed to `find()` / `findFirst()` are not a string, integer or array. |
| `Phalcon\Mvc\Model\Exceptions\InvalidGetModelNameReturn`       | `Phalcon\Mvc\Model\Exception` | `getModelName()` returns a value that is not a string or array of strings.            |
| `Phalcon\Mvc\Model\Exceptions\InvalidModelName`                | `Phalcon\Mvc\Model\Exception` | A model name passed at runtime is not a string.                                       |
| `Phalcon\Mvc\Model\Exceptions\InvalidModelsManagerService`     | `Phalcon\Mvc\Model\Exception` | The configured `modelsManager` service does not produce a `ManagerInterface`.         |
| `Phalcon\Mvc\Model\Exceptions\InvalidModelsMetadataService`    | `Phalcon\Mvc\Model\Exception` | The configured `modelsMetadata` service does not produce a `MetaDataInterface`.       |
| `Phalcon\Mvc\Model\Exceptions\InvalidResultsetCacheService`    | `Phalcon\Mvc\Model\Exception` | The configured `modelsCache` service does not produce an `AdapterInterface`.          |
| `Phalcon\Mvc\Model\Exceptions\InvalidReturnedRecord`           | `Phalcon\Mvc\Model\Exception` | A relation handler returns a value that is not a model or resultset.                  |
| `Phalcon\Mvc\Model\Exceptions\InvalidSerializationData`        | `Phalcon\Mvc\Model\Exception` | Unserializing a model produces data that is not an array.                             |
| `Phalcon\Mvc\Model\Exceptions\ManagerOrmServicesUnavailable`   | `Phalcon\Mvc\Model\Exception` | The DI container has no `modelsManager` service when one is required.                 |
| `Phalcon\Mvc\Model\Exceptions\MethodNotFound`                  | `Phalcon\Mvc\Model\Exception` | A magic method or static call cannot be resolved to a real method.                    |
| `Phalcon\Mvc\Model\Exceptions\MissingMethodName`               | `Phalcon\Mvc\Model\Exception` | A magic call has no method name to dispatch to.                                       |
| `Phalcon\Mvc\Model\Exceptions\MissingModelClassName`           | `Phalcon\Mvc\Model\Exception` | A relation or query references a model without a class name.                          |
| `Phalcon\Mvc\Model\Exceptions\ModelCouldNotLoad`               | `Phalcon\Mvc\Model\Exception` | A model class cannot be autoloaded.                                                   |
| `Phalcon\Mvc\Model\Exceptions\ModelOrmServicesUnavailable`     | `Phalcon\Mvc\Model\Exception` | The DI container has no ORM services registered when one is required.                 |
| `Phalcon\Mvc\Model\Exceptions\PrimaryKeyAttributeNotSet`       | `Phalcon\Mvc\Model\Exception` | A primary-key attribute is not initialized when required.                             |
| `Phalcon\Mvc\Model\Exceptions\PrimaryKeyRequired`              | `Phalcon\Mvc\Model\Exception` | An update or delete operation is attempted on a model without a primary key.          |
| `Phalcon\Mvc\Model\Exceptions\PropertyNotAccessible`           | `Phalcon\Mvc\Model\Exception` | A magic property access targets a property that does not exist or is not readable.    |
| `Phalcon\Mvc\Model\Exceptions\RecordCannotRefresh`             | `Phalcon\Mvc\Model\Exception` | `refresh()` is called on a record that has no primary-key value to look up.           |
| `Phalcon\Mvc\Model\Exceptions\RecordNotPersisted`              | `Phalcon\Mvc\Model\Exception` | An operation requiring a persisted record is called on a new instance.                |
| `Phalcon\Mvc\Model\Exceptions\ReferencedFieldsMismatch`        | `Phalcon\Mvc\Model\Exception` | A relation's referenced and local field counts do not match.                          |
| `Phalcon\Mvc\Model\Exceptions\RelationAliasMustBeString`       | `Phalcon\Mvc\Model\Exception` | A relation alias supplied at registration time is not a string.                       |
| `Phalcon\Mvc\Model\Exceptions\RelationNotDefined`              | `Phalcon\Mvc\Model\Exception` | A relation is referenced but has not been declared on the model.                      |
| `Phalcon\Mvc\Model\Exceptions\RelationRequiresObjectOrArray`   | `Phalcon\Mvc\Model\Exception` | A relation value provided for save/update is not an object or array.                  |
| `Phalcon\Mvc\Model\Exceptions\ResultsetColumnNotInMap`         | `Phalcon\Mvc\Model\Exception` | A resultset column cannot be mapped back to a model attribute.                        |
| `Phalcon\Mvc\Model\Exceptions\RowIsImmutable`                  | `Phalcon\Mvc\Model\Exception` | A simple resultset row is written to but is read-only.                                |
| `Phalcon\Mvc\Model\Exceptions\SnapshotsDisabled`               | `Phalcon\Mvc\Model\Exception` | Snapshot APIs are called when snapshotting is disabled.                               |
| `Phalcon\Mvc\Model\Exceptions\StaticMethodRequiresOneArgument` | `Phalcon\Mvc\Model\Exception` | A static-method magic call has the wrong argument count.                              |
| `Phalcon\Mvc\Model\Exceptions\UnknownRelationType`             | `Phalcon\Mvc\Model\Exception` | A relation declares a type the manager does not recognize.                            |
| `Phalcon\Mvc\Model\Exceptions\UpdateSnapshotDisabled`          | `Phalcon\Mvc\Model\Exception` | `updateSnapshot()` is invoked while snapshots are disabled.                           |

[binding-parameters]: #binding-parameters
[db-models-relationships]: /5.19/db-models-relationships/
[models-relationships-eager]: /5.19/db-models-relationships/#eager-loading
[cache]: /5.19/cache/
[creating-models]: #creating-models
[db]: /5.19/db-layer/
[db-models-transactions]: /5.19/db-models-transactions/
[db-phql]: /5.19/db-phql/
[db-rawvalue]: /5.19/api/phalcon_db/#dbrawvalue
[DRY]: https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[events]: /5.19/events/
[finding-records]: #finding-records
[flash]: /5.19/flash/
[mvc-model]: /5.19/api/phalcon_mvc/#mvcmodel
[mvc-model-binder]: /5.19/api/phalcon_mvc/#mvcmodelbinder
[mvc-model-binder-bindableinterface]: /5.19/api/phalcon_mvc/#mvcmodelbinderbindableinterface
[mvc-model-binderinterface]: /5.19/api/phalcon_mvc/#mvcmodelbinderinterface
[mvc-model-criteria]: /5.19/api/phalcon_mvc/#mvcmodelcriteria
[mvc-model-criteriainterface]: /5.19/api/phalcon_mvc/#mvcmodelcriteriainterface
[mvc-model-exception]: /5.19/api/phalcon_mvc/#mvcmodelexception
[mvc-model-manager]: /5.19/api/phalcon_mvc/#mvcmodelmanager
[mvc-model-managerinterface]: /5.19/api/phalcon_mvc/#mvcmodelmanagerinterface
[mvc-model-query]: /5.19/api/phalcon_mvc/#mvcmodelquery
[mvc-model-query-builder]: /5.19/api/phalcon_mvc/#mvcmodelquerybuilder
[mvc-model-query-builderinterface]: /5.19/api/phalcon_mvc/#mvcmodelquerybuilderinterface
[mvc-model-query-lang]: /5.19/api/phalcon_mvc/#mvcmodelquerylang
[mvc-model-query-status]: /5.19/api/phalcon_mvc/#mvcmodelquerystatus
[mvc-model-query-statusinterface]: /5.19/api/phalcon_mvc/#mvcmodelquerystatusinterface
[mvc-model-queryinterface]: /5.19/api/phalcon_mvc/#mvcmodelqueryinterface
[mvc-model-relation]: /5.19/api/phalcon_mvc/#mvcmodelrelation
[mvc-model-relationinterface]: /5.19/api/phalcon_mvc/#mvcmodelrelationinterface
[mvc-model-resultinterface]: /5.19/api/phalcon_mvc/#mvcmodelresultinterface
[mvc-model-resultset]: /5.19/api/phalcon_mvc/#mvcmodelresultset
[mvc-model-resultset-complex]: /5.19/api/phalcon_mvc/#mvcmodelresultsetcomplex
[mvc-model-resultset-simple]: /5.19/api/phalcon_mvc/#mvcmodelresultsetsimple
[mvc-model-resultsetinterface]: /5.19/api/phalcon_mvc/#mvcmodelresultsetinterface
[mvc-model-row]: /5.19/api/phalcon_mvc/#mvcmodelrow
[mvc-model-validationfailed]: /5.19/api/phalcon_mvc/#mvcmodelvalidationfailed
[mvc-modelinterface]: /5.19/api/phalcon_mvc/#mvcmodelinterface
[pdo-statements]: https://php.net/manual/en/pdo.prepared-statements.php
[transactions]: #transactions

Source: https://docs.phalcon.io/5.19/db-models/index.mdx
