DataModeler: Simple ORM – Part 2 Saving Models

September 21, 2010

Uncategorized

Saving Models to the Datastore

In DataModeler: Simple ORM – Part 1 Models, I introduced the basics of DataModeler, it’s classes and how to create a basic Model.

After you have created your Models, it’s time to save them to a datastore. DataModeler requires you to use PDO as the abstraction layer as it makes use of prepared statements and database specific extensions.

To start, you’ll want to create a PDO object. DataModeler is not responsible for creating the PDO object; you must create it and inject it into the \DataModeler\Sql class. By injecting the PDO object, you’ll have greater control over what attributes you want to set. Furthermore, it simplifies testing the \DataModeler\Sql class as the PDO object attached can be mocked during test runs.

namespace myApp

use \DataModeler\Model,
  \DataModeler\Sql;

require_once 'DataModeler/Framework.php';

$pdo = new \PDO('mysql:dbname=datamodeler;host=localhost', 'username', 'password');
$sql = new Sql;
$sql->attachPdo($sql);

The attachPdo() method sets two PDO attributes: PDO::ATTR_ERRMODE = PDO::ERRMODE_SILENT and PDO::ATTR_EMULATE_PREPARES = false. DataModeler will handle PDO errors for you, and if the datastore supports prepared queries, DataModeler wants them to be used. With the default value of PDO::ATTR_EMULATE_PREPARES as true, MySQL will allow invalid queries to be prepared, forfeiting the entire purpose of prepared queries.

The $sql object is now ready to be used. A \DataModeler\Exception exception is thrown if you attempt to use any of the methods in \DataModeler\Sql without first attaching a valid PDO object.

Saving Models

Saving your Models is the simplest task for the Sql class. DataModeler is intelligent enough to determine if you are inserting a Model for the first time, or updating a pre-existing one.

DataModeler uses an aptly named method save() to save Models. It accepts exactly one \DataModeler\Model parameter, which it also returns on successful saves, for a final signature of: \DataModeler\Model \DataModeler\Sql::save(\DataModeler\Model $model).

namespace myApp;

use \DataModeler\Model,
  \DataModeler\Sql;

require_once 'DataModeler/Framework.php';
require_once 'myApp/model/user.php';

$pdo = new \PDO('mysql:dbname=datamodeler;host=localhost', 'username', 'password');
$sql = new Sql;
$sql->attachPdo($sql);

// Assuming the User has id, name, age, height, and job_title fields,
// this will insert a new User into the database. 
// ($user->exists() === false) at this stage
$user = new User;
$user->setName('Vic Cherubini')
  ->setAge(26)
  ->setHeight(74)
  ->setJobTitle('Software Mechanic');

$user = $sql->save($user);

DataModeler goes through several steps to save a Model. First, it determines if the Model exists or not. If it exists, a prepared UPDATE query is built. If it does not exist, a prepared INSERT query is built. save() makes use of named parameters rather than anonymous ones so their correct datatype can be bound to the correct parameter easier.

Note that DataModeler makes extensive use of prepared statements. However, prepared statements are useless if they’re prepared each time the save() method is called on a similar Model. To get around this, after the query is constructed, it’s SHA1 hash is found. If that is not the same as the previous query’s SHA1 hash, the query needs to be prepared. If the hashes are equal, the queries are identical, and thus don’t need to be re-prepared.

// This code prepares the query only once
$user = new User;
for ( $i=0; $i<10; $i++ ) {
  $user->setAge($i+10);
  $sql->save($user);
}

// Assuming Thread is a Model as well
$thread = new Thread;
$thread->setTitle('My New Post About DataModeler')
  ->setUserId(10);

// Queries won't be the same, so the new query for Thread is prepared.
$sql->save($thread);

Preparing queries (particularly SELECT queries) for databases that support them speeds up multiple executions considerably.

After the query is prepared, the named parameters are bound to values. Because each Model has the datatype defined, DataModeler does it’s best to determine the PDO::PARAM_* attribute they are aligned with. When the type can not be determined, PDO::PARAM_STR is used by default.

At this point, the query and parameters are executed. If the execution is not successful, a \DataModeler\Exception is thrown.

The save() method always returns the Model object passed into it. If the Model was inserted into the datastore, and contains a primary auto-incremented key, its value is found and attached to the Model. Thus, $user->exists(); would return true if the Model was successfully inserted.

Dumb Loads

DataModeler also supports “dumb loads”, or pretending to load an object to save it to a datastore. This would be useful if you want to update a select few fields in the Model without requiring you to load the Model from the datastore first. In this example, we’ll use the previously defined User Model and update only the job_title field.

// Assuming the PDO connection has been established and attached to a new Sql object

// Imagine this code was the code in a Controller and you had access to the session
// and request variables.

if ( array_key_exists('user_id', $_SESSION) ) {
  $user = new User;
  $user->id($_SESSION['user_id']);

  if ( array_key_exists('job_title', $_POST) ) {
    $user->setJobTitle($_POST['job_title']);

    $sql->save($user);
  }
}

With this abbreviated example, you can pass the primary key value to the object’s id() method to trick the object into thinking it was loaded from a datastore. The call to $sql->save($user); will now update the User object rather than insert it.

Conclusion

DataModeler makes it very easy to save your Models. Future improvements will include cascading saves, being able to save a single Model into multiple locations at once, and tests on multiple datastores. Currently, DataModeler has been tested on Sqlite and MySQL. After DataModeler: Simple ORM – Part 1 Models was published, I received a lot of great feedback. In Part 3, I’ll discuss loading Models from a datastore.

You can download DataModeler from Github at http://github.com/leftnode/DataModeler/downloads or visit the primary DataModeler Github page.

My name is Vic Cherubini. You can find me @leftnode or at http://leftnode.com.

Comments are closed.