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+

ODM (Object-Document Mapper)

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:

NameDescription
MongoDBMongoDB is a scalable, high-performance, open source NoSQL database.

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:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. }

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:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function initialize()
  6. {
  7. $this->setSource('the_robots');
  8. }
  9. }

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:

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

Models in Namespaces

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:

  1. <?php
  2. namespace Store\Toys;
  3. use Phalcon\Mvc\Collection;
  4. class Robots extends Collection
  5. {
  6. public function initialize()
  7. {
  8. $this->setSource('robots');
  9. }
  10. }

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

  1. <?php
  2. // Find record with _id = '5087358f2d42b8c3d15ec4e2'
  3. $robot = Robots::findById('5087358f2d42b8c3d15ec4e2');
  4. // Prints 'Bender'
  5. echo $robot->name;

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

  1. <?php
  2. $robot = Robots::findFirst(
  3. [
  4. [
  5. 'name' => 'Astro Boy',
  6. ]
  7. ]
  8. );
  9. $robot->name = 'Voltron';
  10. $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:

  1. <?php
  2. // Simple database connection to localhost
  3. $di->set(
  4. 'mongo',
  5. function () {
  6. $mongo = new MongoClient();
  7. return $mongo->selectDB('store');
  8. },
  9. true
  10. );
  11. // Connecting to a domain socket, falling back to localhost connection
  12. $di->set(
  13. 'mongo',
  14. function () {
  15. $mongo = new MongoClient(
  16. 'mongodb:///tmp/mongodb-27017.sock,localhost:27017'
  17. );
  18. return $mongo->selectDB('store');
  19. },
  20. true
  21. );

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:

  1. <?php
  2. // How many robots are there?
  3. $robots = Robots::find();
  4. echo 'There are ', count($robots), "\n";
  5. // How many mechanical robots are there?
  6. $robots = Robots::find(
  7. [
  8. [
  9. 'type' => 'mechanical',
  10. ]
  11. ]
  12. );
  13. echo 'There are ', count($robots), "\n";
  14. // Get and print mechanical robots ordered by name upward
  15. $robots = Robots::find(
  16. [
  17. [
  18. 'type' => 'mechanical',
  19. ],
  20. 'sort' => [
  21. 'name' => 1,
  22. ],
  23. ]
  24. );
  25. foreach ($robots as $robot) {
  26. echo $robot->name, "\n";
  27. }
  28. // Get first 100 mechanical robots ordered by name
  29. $robots = Robots::find(
  30. [
  31. [
  32. 'type' => 'mechanical',
  33. ],
  34. 'sort' => [
  35. 'name' => 1,
  36. ],
  37. 'limit' => 100,
  38. ]
  39. );
  40. foreach ($robots as $robot) {
  41. echo $robot->name, "\n";
  42. }

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

  1. <?php
  2. // What's the first robot in robots collection?
  3. $robot = Robots::findFirst();
  4. echo 'The robot name is ', $robot->name, "\n";
  5. // What's the first mechanical robot in robots collection?
  6. $robot = Robots::findFirst(
  7. [
  8. [
  9. 'type' => 'mechanical',
  10. ]
  11. ]
  12. );
  13. echo 'The first mechanical robot name is ', $robot->name, "\n";

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

  1. <?php
  2. // First robot where type = 'mechanical' and year = '1999'
  3. $robot = Robots::findFirst(
  4. [
  5. 'conditions' => [
  6. 'type' => 'mechanical',
  7. 'year' => '1999',
  8. ],
  9. ]
  10. );
  11. // All virtual robots ordered by name downward
  12. $robots = Robots::find(
  13. [
  14. 'conditions' => [
  15. 'type' => 'virtual',
  16. ],
  17. 'sort' => [
  18. 'name' => -1,
  19. ],
  20. ]
  21. );
  22. // Find all robots that have more than 4 friends using the where condition
  23. $robots = Robots::find(
  24. [
  25. 'conditions' => [
  26. '$where' => 'this.friends.length > 4',
  27. ]
  28. ]
  29. );

The available query options are:

ParameterDescriptionExample
conditionsSearch conditions for the find operation. Is used to extract only those records that fulfill a specified criterion. By default Phalcon_model assumes the first parameter are the conditions.'conditions' => array('$gt' => 1990)
fieldsReturns specific columns instead of the full fields in the collection. When using this option an incomplete object is returned'fields' => array('name' => true)
sortIt’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)
limitLimit the results of the query to results to certain range'limit' => 10
skipSkips a number of results'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:

  1. $myRobots = Robots:find(
  2. [
  3. 'fields' => ['name' => 1]
  4. ]
  5. ];

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

  1. $myRobots = Robots:find(
  2. [
  3. ['type' => 'maid'],
  4. 'fields' => ['name' => 1]
  5. ]
  6. ];

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:

  1. <?php
  2. $data = Article::aggregate(
  3. [
  4. [
  5. '\$project' => [
  6. 'category' => 1,
  7. ],
  8. ],
  9. [
  10. '\$group' => [
  11. '_id' => [
  12. 'category' => '\$category'
  13. ],
  14. 'id' => [
  15. '\$max' => '\$_id',
  16. ],
  17. ],
  18. ],
  19. ]
  20. );

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:

  1. <?php
  2. $robot = new Robots();
  3. $robot->type = 'mechanical';
  4. $robot->name = 'Astro Boy';
  5. $robot->year = 1952;
  6. if ($robot->save() === false) {
  7. echo "Umh, We can't store robots right now: \n";
  8. $messages = $robot->getMessages();
  9. foreach ($messages as $message) {
  10. echo $message, "\n";
  11. }
  12. } else {
  13. echo 'Great, a new robot was saved successfully!';
  14. }

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

  1. <?php
  2. $robot->save();
  3. echo 'The generated id is: ', $robot->getId();

Validation Messages

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(). Each message provides extended information like the field name that generated the message or the message type:

  1. <?php
  2. if ($robot->save() === false) {
  3. $messages = $robot->getMessages();
  4. foreach ($messages as $message) {
  5. echo 'Message: ', $message->getMessage();
  6. echo 'Field: ', $message->getField();
  7. echo 'Type: ', $message->getType();
  8. }
  9. }

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:

OperationNameCan stop operation?Explanation
Inserting/UpdatingbeforeValidationYESIs executed before the validation process and the final insert/update to the database
InsertingbeforeValidationOnCreateYESIs executed before the validation process only when an insertion operation is being made
UpdatingbeforeValidationOnUpdateYESIs executed before the fields are validated for not nulls or foreign keys when an updating operation is being made
Inserting/UpdatingonValidationFailsYES (already stopped)Is executed before the validation process only when an insertion operation is being made
InsertingafterValidationOnCreateYESIs executed after the validation process when an insertion operation is being made
UpdatingafterValidationOnUpdateYESIs executed after the validation process when an updating operation is being made
Inserting/UpdatingafterValidationYESIs executed after the validation process
Inserting/UpdatingbeforeSaveYESRuns before the required operation over the database system
UpdatingbeforeUpdateYESRuns before the required operation over the database system only when an updating operation is being made
InsertingbeforeCreateYESRuns before the required operation over the database system only when an inserting operation is being made
UpdatingafterUpdateNORuns after the required operation over the database system only when an updating operation is being made
InsertingafterCreateNORuns after the required operation over the database system only when an inserting operation is being made
Inserting/UpdatingafterSaveNORuns after the required operation over the database system

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

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function beforeValidationOnCreate()
  6. {
  7. echo 'This is executed before creating a Robot!';
  8. }
  9. }

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

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Products extends Collection
  4. {
  5. public function beforeCreate()
  6. {
  7. // Set the creation date
  8. $this->created_at = date('Y-m-d H:i:s');
  9. }
  10. public function beforeUpdate()
  11. {
  12. // Set the modification date
  13. $this->modified_in = date('Y-m-d H:i:s');
  14. }
  15. }

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.

  1. <?php
  2. use Phalcon\Events\Event;
  3. use Phalcon\Events\Manager as EventsManager;
  4. $eventsManager = new EventsManager();
  5. // Attach an anonymous function as a listener for 'model' events
  6. $eventsManager->attach(
  7. 'collection:beforeSave',
  8. function (Event $event, $robot) {
  9. if ($robot->name === 'Scooby Doo') {
  10. echo "Scooby Doo isn't a robot!";
  11. return false;
  12. }
  13. return true;
  14. }
  15. );
  16. $robot = new Robots();
  17. $robot->setEventsManager($eventsManager);
  18. $robot->name = 'Scooby Doo';
  19. $robot->year = 1969;
  20. $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:

  1. <?php
  2. use Phalcon\Events\Event;
  3. use Phalcon\Events\Manager as EventsManager;
  4. use Phalcon\Mvc\Collection\Manager as CollectionManager;
  5. // Registering the collectionManager service
  6. $di->set(
  7. 'collectionManager',
  8. function () {
  9. $eventsManager = new EventsManager();
  10. // Attach an anonymous function as a listener for 'model' events
  11. $eventsManager->attach(
  12. 'collection:beforeSave',
  13. function (Event $event, $model) {
  14. if (get_class($model) === 'Robots') {
  15. if ($model->name === 'Scooby Doo') {
  16. echo "Scooby Doo isn't a robot!";
  17. return false;
  18. }
  19. }
  20. return true;
  21. }
  22. );
  23. // Setting a default EventsManager
  24. $modelsManager = new CollectionManager();
  25. $modelsManager->setEventsManager($eventsManager);
  26. return $modelsManager;
  27. },
  28. true
  29. );

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:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. protected function beforeSave()
  6. {
  7. if ($this->year < 0) {
  8. echo 'Year cannot be smaller than zero!';
  9. return false;
  10. }
  11. }
  12. }

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.

Validating Data Integrity

Phalcon\Mvc\Collection provides several events to validate data and implement business rules. The special validation event allows us to call built-in validators over the record. Phalcon exposes a few built-in validators that can be used at this stage of validation.

The following example shows how to use it:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. use Phalcon\Validation;
  4. use Phalcon\Validation\Validator\InclusionIn;
  5. use Phalcon\Validation\Validator\Numericality;
  6. class Robots extends Collection
  7. {
  8. public function validation()
  9. {
  10. $validation = new Validation();
  11. $validation->add(
  12. 'type',
  13. new InclusionIn(
  14. [
  15. 'message' => 'Type must be: mechanical or virtual',
  16. 'domain' => [
  17. 'Mechanical',
  18. 'Virtual',
  19. ],
  20. ]
  21. )
  22. );
  23. $validation->add(
  24. 'price',
  25. new Numericality(
  26. [
  27. 'message' => 'Price must be numeric'
  28. ]
  29. )
  30. );
  31. return $this->validate($validation);
  32. }
  33. }

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:

  1. <?php
  2. $robot = Robots::findFirst();
  3. if ($robot !== false) {
  4. if ($robot->delete() === false) {
  5. echo "Sorry, we can't delete the robot right now: \n";
  6. $messages = $robot->getMessages();
  7. foreach ($messages as $message) {
  8. echo $message, "\n";
  9. }
  10. } else {
  11. echo 'The robot was deleted successfully!';
  12. }
  13. }

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

  1. <?php
  2. $robots = Robots::find(
  3. [
  4. [
  5. 'type' => 'mechanical',
  6. ]
  7. ]
  8. );
  9. foreach ($robots as $robot) {
  10. if ($robot->delete() === false) {
  11. echo "Sorry, we can't delete the robot right now: \n";
  12. $messages = $robot->getMessages();
  13. foreach ($messages as $message) {
  14. echo $message, "\n";
  15. }
  16. } else {
  17. echo 'The robot was deleted successfully!';
  18. }
  19. }

The following events are available to define custom business rules that can be executed when a delete operation is performed:

OperationNameCan stop operation?Explanation
DeletingbeforeDeleteYESRuns before the delete operation is made
DeletingafterDeleteNORuns after the delete operation was made

Validation Failed Events

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

OperationNameExplanation
Insert or UpdatenotSaveTriggered when the insert/update operation fails for any reason
Insert, Delete or UpdateonValidationFailsTriggered when any data manipulation operation fails

Implicit Ids vs. User Primary Keys

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

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

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function initialize()
  6. {
  7. $this->useImplicitObjectIds(false);
  8. }
  9. }

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:

  1. <?php
  2. // This service returns a mongo database at 192.168.1.100
  3. $di->set(
  4. 'mongo1',
  5. function () {
  6. $mongo = new MongoClient(
  7. 'mongodb://scott:[email protected]'
  8. );
  9. return $mongo->selectDB('management');
  10. },
  11. true
  12. );
  13. // This service returns a mongo database at localhost
  14. $di->set(
  15. 'mongo2',
  16. function () {
  17. $mongo = new MongoClient(
  18. 'mongodb://localhost'
  19. );
  20. return $mongo->selectDB('invoicing');
  21. },
  22. true
  23. );

Then, in the initialize() method, we define the connection service for the model:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function initialize()
  6. {
  7. $this->setConnectionService('mongo1');
  8. }
  9. }

Injecting services into Models

You may be required to access the application services within a model, the following example explains how to do that:

  1. <?php
  2. use Phalcon\Mvc\Collection;
  3. class Robots extends Collection
  4. {
  5. public function notSave()
  6. {
  7. // Obtain the flash service from the DI container
  8. $flash = $this->getDI()->getShared('flash');
  9. $messages = $this->getMessages();
  10. // Show validation messages
  11. foreach ($messages as $message) {
  12. $flash->error(
  13. (string) $message
  14. );
  15. }
  16. }
  17. }

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.