Sections

Object Document Mapper


Overview

Please note that if you are using the Mongo driver provided by PHP 7, the ODM will not work for you. There is an incubator adapter but all the Mongo code must be rewritten (new Bson type instead of arrays, no MongoId, no MongoDate, etc…). Please ensure that you test your code before upgrading to PHP 7 and/or Phalcon 3+

In addition to its ability to map tables in relational databases, Phalcon can map documents from NoSQL databases. The ODM offers a CRUD functionality, events, validations among other services.

Due to the absence of SQL queries and planners, NoSQL databases can see real improvements in performance using the Phalcon approach. Additionally, there are no SQL building reducing the possibility of SQL injections.

The following NoSQL databases are supported:

Nombre Descripción
[MongoDB][mongodb] MongoDB es una base de datos NoSQL de código fuente abierto, escalable y de alto desempeño.

Creating Models

A model is a class that extends from Phalcon\Mvc\Collection. It must be placed in the models directory. A model file must contain a single class; its class name should be in camel case notation:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{

}

By default model Robots will refer to the collection robots. If you want to manually specify another name for the mapping collection, you can use the setSource() method:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    public function initialize()
    {
        $this->setSource('the_robots');
    }
}

Understanding Documents To Objects

Every instance of a model represents a document in the collection. You can easily access collection data by reading object properties. For example, for a collection robots with the documents:

$ mongo test
MongoDB shell version: 1.8.2
connecting to: test
> db.robots.find()
{ '_id' : ObjectId('508735512d42b8c3d15ec4e1'), 'name' : 'Astro Boy', 'year' : 1952,
    'type' : 'mechanical' }
{ '_id' : ObjectId('5087358f2d42b8c3d15ec4e2'), 'name' : 'Bender', 'year' : 1999,
    'type' : 'mechanical' }
{ '_id' : ObjectId('508735d32d42b8c3d15ec4e3'), 'name' : 'Wall-E', 'year' : 2008 }
>

Modelos en Espacios de Nombres

Namespaces can be used to avoid class name collision. In this case it is necessary to indicate the name of the related collection using the setSource() method:

<?php

namespace Store\Toys;

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    public function initialize()
    {
        $this->setSource('robots');
    }
}

You could find a certain document by its ID and then print its name:

<?php

// Encontrar el registro con _id = '5087358f2d42b8c3d15ec4e2'
$robot = Robots::findById('5087358f2d42b8c3d15ec4e2');

// Imprime 'Bender'
echo $robot->name;

Una vez que el registro está en la memoria, puede hacer modificaciones a sus datos y guardar los cambios:

<?php

$robot = Robots::findFirst(
    [
        [
            'name' => 'Astro Boy',
        ]
    ]
);

$robot->name = 'Voltron';

$robot->save();

Setting a Connection

Connections are retrieved from the services container. By default, Phalcon tries to find the connection in a service called mongo:

<?php

// Conexión simple de base de datos a localhost
$di->set(
    'mongo',
    function () {
        $mongo = new MongoClient();

        return $mongo->selectDB('store');
    },
    true
);

// Conectando a un socket de dominio, volviendo a la conexión localhost
$di->set(
    'mongo',
    function () {
        $mongo = new MongoClient(
            'mongodb:///tmp/mongodb-27017.sock,localhost:27017'
        );

        return $mongo->selectDB('store');
    },
    true
);

Finding Documents

As Phalcon\Mvc\Collection relies on the Mongo PHP extension you have the same facilities to query documents and convert them transparently to model instances:

<?php

// Cuantos robots hay?
$robots = Robots::find();
echo 'Hay ', count($robots), "\n";

// ¿Cuántos robots mecánicos hay?
$robots = Robots::find(
    [
        [
            'type' => 'mechanical',
        ]
    ]
);
echo 'Hay ', count($robots), "\n";

// Obtener e imprimir los robots mecánicos ordenados por nombre ascendentemente
$robots = Robots::find(
    [
        [
            'type' => 'mechanical',
        ],
        'sort' => [
            'name' => 1,
        ],
    ]
);

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

// Obtener los primeros 100 robots mecánicos ordenados por nombre
$robots = Robots::find(
    [
        [
            'type' => 'mechanical',
        ],
        'sort'  => [
            'name' => 1,
        ],
        'limit' => 100,
    ]
);

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

También puede utilizar el método findFirst() para obtener sólo el primer registro que coincida con el criterio dado:

<?php

// Cual es el primer robot en la colección robots?
$robot = Robots::findFirst();
echo 'El nombre del robot es ', $robot->name, "\n";

// Cual es el primer robot mecánico en la colección robots?
$robot = Robots::findFirst(
    [
        [
            'type' => 'mechanical',
        ]
    ]
);

echo 'El nombre del primer robot mecánico es ', $robot->name, "\n";

Ambos métodos find() y findFirst() aceptan un array asociativo, especificando los criterios de búsqueda:

<?php

// Primer robot donde tipo 'mechanical' y año '1999'
$robot = Robots::findFirst(
    [
        'conditions' => [
            'type' => 'mechanical',
            'year' => '1999',
        ],
    ]
);

// Todos los robots virtuales ordenados por nombre descendentemente
$robots = Robots::find(
    [
        'conditions' => [
            'type' => 'virtual',
        ],
        'sort' => [
            'name' => -1,
        ],
    ]
);

// Encontrar todos los robots que tengan más de 4 amigos usando la condición where
$robots = Robots::find(
    [
        'conditions' => [
            '$where' => 'this.friends.length > 4',
        ]
    ]
);

Las opciones disponibles de consulta son:

Parameter Descripción Ejemplo
conditions Condiciones de búsqueda para la operación de búsqueda. Se utiliza para extraer sólo los registros que cumplan con un criterio especificado. By default Phalcon_model assumes the first parameter are the conditions. 'conditions' => array('$gt' => 1990)
fields Returns specific columns instead of the full fields in the collection. When using this option an incomplete object is returned 'fields' => array('name' => true)
sort It’s used to sort the resultset. Use one or more fields as each element in the array, 1 means ordering upwards, -1 downward 'sort' => array('name' => -1, 'status' => 1)
limit Limitar los resultados de la consulta a cierto rango 'limit' => 10
skip Omite un número específico de resultados 'skip' => 50

If you have experience with SQL databases, you may want to check the SQL to Mongo Mapping Chart.

Querying specific fields

To query specific fields specific fields from a MongoDB database using the Phalcon ODM, all you need to do is:

$myRobots = Robots:find(
    [
        'fields' => ['name' => 1]
    ]
];

The find() above only returns a name. It can also be combined with a condition:

$myRobots = Robots:find(
    [
        ['type' => 'maid'],
        'fields' => ['name' => 1]
    ]
];

The example above returns the name of the robot with the type = 'maid'.

Aggregations

A model can return calculations using aggregation framework provided by Mongo. The aggregated values are calculate without having to use MapReduce. With this option is easy perform tasks such as totaling or averaging field values:

<?php

$data = Article::aggregate(
    [
        [
            '\$project' => [
                'category' => 1,
            ],
        ],
        [
            '\$group' => [
                '_id' => [
                    'category' => '\$category'
                ],
                'id'  => [
                    '\$max' => '\$_id',
                ],
            ],
        ],
    ]
);

Creating Updating/Records

The Phalcon\Mvc\Collection::save() method allows you to create/update documents according to whether they already exist in the collection associated with a model. The save() method is called internally by the create and update methods of Phalcon\Mvc\Collection.

Also the method executes associated validators and events that are defined in the model:

<?php

$robot = new Robots();

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

if ($robot->save() === false) {
    echo "Ups, no podemos almacenar robots en este momento: \n";

    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo $message, "\n";
    }
} else {
    echo 'Grandioso, un nuevo robot ha sido agregado con éxito!';
}

The _id property is automatically updated with the [MongoId][mongoid] object created by the driver:

<?php

$robot->save();

echo 'La id generada es: ', $robot->getId();

Mensajes de validación

Phalcon\Mvc\Collection has a messaging subsystem that provides a flexible way to output or store the validation messages generated during the insert/update processes.

Each message consists of an instance of the class Phalcon\Mvc\Model\Message. The set of messages generated can be retrieved with the method getMessages(). Cada mensaje proporciona información ampliada como el nombre del campo que genera el mensaje o el tipo de mensaje:

<?php

if ($robot->save() === false) {
    $messages = $robot->getMessages();

    foreach ($messages as $message) {
        echo 'Mensaje: ', $message->getMessage();
        echo 'Campo: ', $message->getField();
        echo 'Tipo: ', $message->getType();
    }
}

Validation Events and Events Manager

Models allow you to implement events that will be thrown when performing an insert or update. They help define business rules for a certain model. The following are the events supported by Phalcon\Mvc\Collection and their order of execution:

Operación Nombre ¿Detiene la operación? Explicación
Insertar o actualizar beforeValidation SI Es ejecutado antes del proceso de validación y la inserción o actualización final en la base de datos
Insertar beforeValidationOnCreate SI Es ejecutado antes del proceso de validación solo cuando es una operación de inserción
Actualizar beforeValidationOnUpdate SI Es ejecutado antes de validar los campos no nulos o claves foraneas cuando es una operación de actualización
Insertar o actualizar onValidationFails SI (detenido) Es ejecutado antes del proceso de validación solo cuando es una operación de inserción
Insertar afterValidationOnCreate SI Es ejecutado después del proceso de validación cuando es una operación de inserción
Actualizar afterValidationOnUpdate SI Es ejecutado después del proceso de validación cuando es una operación de actualización
Insertar o actualizar afterValidation SI Se ejecuta tras el proceso de validación
Insertar o actualizar beforeSave SI Se ejecuta antes de la operación sobre el sistema de base de datos, para operaciones de inserción o actualización
Actualizar beforeUpdate SI Se ejecuta antes de la operación sobre el sistema de base de datos, sólo cuando se realiza una operación de actualización
Insertar beforeCreate SI Se ejecuta antes de la operación sobre el sistema de base de datos, sólo cuando se realiza una operación de inserción
Actualizar afterUpdate NO Se ejecuta después de la operación sobre el sistema de base de datos pero sólo cuando se realiza una operación de actualización
Insertar afterCreate NO Se ejecuta después de la operación sobre el sistema de base de datos pero sólo cuando se realiza una operación de inserción
Insertar o actualizar afterSave NO Después que la operación se ejecuta sobre el sistema de base de datos solo para inserción o actualización

To make a model to react to an event, we must to implement a method with the same name of the event:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    public function beforeValidationOnCreate()
    {
        echo 'Esto es ejecutado antes de crear un Robot!';
    }
}

Events can be useful to assign values before performing an operation, for example:

<?php

use Phalcon\Mvc\Collection;

class Products extends Collection
{
    public function beforeCreate()
    {
        // Establecer la fecha de creación
        $this->created_at = date('Y-m-d H:i:s');
    }

    public function beforeUpdate()
    {
        // Establecer la fecha de modificación
        $this->modified_in = date('Y-m-d H:i:s');
    }
}

Additionally, this component is integrated with the Phalcon Events Manager (Phalcon\Events\Manager), this means we can create listeners that run when an event is triggered.

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;

$eventsManager = new EventsManager();

// Ajuntar una función anónima como oyente de los eventos de 'model'
$eventsManager->attach(
    'collection:beforeSave',
    function (Event $event, $robot) {
        if ($robot->name === 'Scooby Doo') {
            echo "Scooby Doo no es un robot!";

            return false;
        }

        return true;
    }
);

$robot = new Robots();

$robot->setEventsManager($eventsManager);

$robot->name = 'Scooby Doo';
$robot->year = 1969;

$robot->save();

In the example given above the EventsManager only acted as a bridge between an object and a listener (the anonymous function). If we want all objects created in our application use the same EventsManager, then we need to assign this to the Models Manager:

<?php

use Phalcon\Events\Event;
use Phalcon\Events\Manager as EventsManager;
use Phalcon\Mvc\Collection\Manager as CollectionManager;

// Registrando el servicio collectionManager
$di->set(
    'collectionManager',
    function () {
        $eventsManager = new EventsManager();

        // Adjuntar una función anónima como oyente de los eventos del 'model'
        $eventsManager->attach(
            'collection:beforeSave',
            function (Event $event, $model) {
                if (get_class($model) === 'Robots') {
                    if ($model->name === 'Scooby Doo') {
                        echo "Scooby Doo no es un robot!";

                        return false;
                    }
                }

                return true;
            }
        );

        // Establecer un EventsManager por defecto
        $modelsManager = new CollectionManager();

        $modelsManager->setEventsManager($eventsManager);

        return $modelsManager;
    },
    true
);

Implementing a Business Rule

When an insert, update or delete is executed, the model verifies if there are any methods with the names of the events listed in the table above. We recommend that validation methods are declared protected to prevent that business logic implementation from being exposed publicly. The following example implements an event that validates the year cannot be smaller than 0 on update or insert:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    protected function beforeSave()
    {
        if ($this->year < 0) {
            echo 'El año no puede ser menor a cero!';

            return false;
        }
    }
}

Some events return false as an indication to stop the current operation. If an event doesn’t return anything, Phalcon\Mvc\Collection will assume a true value.

Validar la integridad de los datos

Phalcon\Mvc\Collection provides several events to validate data and implement business rules. El evento especial validation nos permite llamar a validadores incorporados en el registro. Phalcon expone algunos validadores incorporados que pueden utilizarse en esta etapa de validación.

En el ejemplo siguiente se muestra cómo se utiliza:

<?php

use Phalcon\Mvc\Collection;
use Phalcon\Validation;
use Phalcon\Validation\Validator\InclusionIn;
use Phalcon\Validation\Validator\Numericality;

class Robots extends Collection
{
    public function validation()
    {
        $validation = new Validation();

        $validation->add(
            'type',
            new InclusionIn(
                [
                    'message' => 'El tipo debe ser: mechanical o virtual',
                    'domain' => [
                        'Mechanical',
                        'Virtual',
                    ],
                ]
            )
        );

        $validation->add(
            'price',
            new Numericality(
                [
                    'message' => 'El precio debe ser numérico'
                ]
            )
        );

        return $this->validate($validation);
    }
}

The example above performs a validation using the built-in validator InclusionIn. It checks that the value of the field type is in a domain list. If the value is not included in the list, then the validator will fail and return false.

For more information on validators, see the Validation documentation

Deleting Records

The Phalcon\Mvc\Collection::delete() method allows you to delete a document. You can use it as follows:

<?php

$robot = Robots::findFirst();

if ($robot !== false) {
    if ($robot->delete() === false) {
        echo "Lo sentimos, no pudimos borrar el robot: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'El robot fue borrado correctamente!';
    }
}

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

<?php

$robots = Robots::find(
    [
        [
            'type' => 'mechanical',
        ]
    ]
);

foreach ($robots as $robot) {
    if ($robot->delete() === false) {
        echo "Lo sentimos, no pudimos borrar el robot: \n";

        $messages = $robot->getMessages();

        foreach ($messages as $message) {
            echo $message, "\n";
        }
    } else {
        echo 'El robot fue borrado correctamente!';
    }
}

Los siguientes eventos están disponibles para definir reglas de negocios personalizadas que se pueden ejecutar cuando se realiza una operación de eliminación:

Operación Nombre ¿Detiene la operación? Explicación
Deleting beforeDelete SI Se ejecuta antes de la operación de eliminación
Deleting afterDelete NO Se ejecuta después de la operación de eliminación

Eventos de validación fallidos

Another type of events is available when the data validation process finds any inconsistency:

Operación Nombre Explicación
Insertar o actualizar notSave Se dispara cuando la operación de inserción o actualización falla por alguna razón
Insertar, borrar o actualizar onValidationFails Se dispara cuando cualquier operación de manipulación de datos falla

Implicit Ids vs. User Primary Keys

By default Phalcon\Mvc\Collection assumes that the _id attribute is automatically generated using MongoIds[mongoid].

If a model uses custom primary keys this behavior can be overridden:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    public function initialize()
    {
        $this->useImplicitObjectIds(false);
    }
}

Setting multiple databases

In Phalcon, all models can share the same database connection or specify a connection per model. Actually, when Phalcon\Mvc\Collection needs to connect to the database it requests the mongo service in the application’s services container. You can overwrite this service by setting it in the initialize() method:

<?php

// El servicio retorna una base de datos monto en 192.168.1.100
$di->set(
    'mongo1',
    function () {
        $mongo = new MongoClient(
            'mongodb://scott:[email protected]'
        );

        return $mongo->selectDB('management');
    },
    true
);

// Este servicio retorna una base de datos mongo en localhost
$di->set(
    'mongo2',
    function () {
        $mongo = new MongoClient(
            'mongodb://localhost'
        );

        return $mongo->selectDB('invoicing');
    },
    true
);

Luego, en el método initialize(), definimos el servicio de conexión para el modelo:

<?php

use Phalcon\Mvc\Collection;

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

Injecting services into Models

Si requiere acceder a los servicios de la aplicación dentro de un modelo, en el siguiente ejemplo se explica cómo hacerlo:

<?php

use Phalcon\Mvc\Collection;

class Robots extends Collection
{
    public function notSave()
    {
        // Obtener el servicio flash desde el contenedor DI
        $flash = $this->getDI()->getShared('flash');

        $messages = $this->getMessages();

        // Mostramos los mensajes de validación
        foreach ($messages as $message) {
            $flash->error(
                (string) $message
            );
        }
    }
}

The notSave event is triggered whenever a creating or updating action fails. We’re flashing the validation messages obtaining the flash service from the DI container. By doing this, we don’t have to print messages after each saving.