Persistence mechanism: use-case

In a previous post, I described working on a persistence mechanism not like ActiveRecord. It's more like a gateway. I indicated that I wish it to:

  1. return domain objects instead of returning an array of values which have then to be set as properties of domain objects;
  2. allow the selection of database fields rather than merely performing a SELECT * FROM tablename;
  3. allow the expression of relations between tables and map/create related domain objects accordingly; and
  4. allow some customization of queries

Examples of queries from a blogging system will serve well to further explain my intentions, as features of such systems can be implemented with the relations I'm thinking of:

  • A blog consists of posts or entries (I'll call them posts).
  • Each post has one author. Conversely, each author has many posts.
  • Each post may attract many comments. Conversely, each comment belongs to one post.
  • Each post may be assigned to many categories or tags (I'll call them categories). Conversely, each category has many posts.

The gateway I have chosen to call a "manager". I'll demonstrate selects only at the moment, but I should probably implement separate objects for SELECTs, INSERTs and UPDATEs, or at least maintain a division between SELECT and INSERT/UPDATE.

So here are some use cases. Each requires a PDO or PDO subclass instance, and a "manager" object. So imagine that something like the following is being done for each case:

$pdo = new PDO();
$manager = new Persistence_Manager($pdo);

I've used a "PDO" object here but in fact I'm using my own subclass of the connection object from the excellent pdoext library.

Use Case 1: Find all posts

//Make a prototype
$prototype = new Post();
//Decide which fields to fetch
$prototype->with('Post', array('id', 'title', 'post');
foreach($manager->find($prototype) as $post) {
    //display post
}

Use Case 2: Find a specific post

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post');
//Find using primary key
$posts = $manager->findByPk($prototype, 2);
//Or find using some other condition, such as title = 'My First Post'
$posts = $manager->find($prototype, array(array('title', 'My First Post', '=')); //WHERE title='My First Post'

foreach($posts and $post) {
    //display post or posts
}

My plan here is to allow multiple conditions. The reason there's a nested array is because pdoext allows expressing conditions as arrays.

Use Case 3: Find a post and its author

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post'));
$prototype->with('Author', array('id', 'name'));
foreach($manager->findByPk($post, 1) as $post) {
    echo "$post->title, posted by $post->author->name"; //Author object is automatically created
}

The Author object is created automatically and populated with the 'Author' fields specified in the second call to $prototype->with().

The relationship between a Post and its Author is defined in an 'Association' object which is added to either or both domain objects. One could add them manually at run-time or make them part of the domain classes:

class Post extends Persistence_Object
{
    public function __construct()
    {
        parent::__construct();
        $this->addAssociation( new PostToAuthorAssociation() );
    }
}

Use Case 4: Custom associations

It's not uncommon for pages displaying single blog entries to display links to the previous and the next post. I've seen code where these are done with separate queries. But since each post has only one previous and one next, and since fetching a couple post ids and titles is not much more overhead than fetching one complete post and its possibly long text, one could do it in a custom query. Something like:

SELECT Post.id, Post.title, Post.post, prev.id, prev.title, next.id, next.title FROM posts AS Post
LEFT JOIN posts AS prev ON (prev.id = Posts.id - 1)
LEFT JOIN post AS next ON (next.id = Posts.id + 1)
WHERE Post.id = '2'

So, assuming that such relationships are defined in an Association, the calling code for this might look like:

$prototype = new Post();
$prototype->with('Post', array('id', 'title', 'post'));
$prototype->with('prev', array('id', 'title'));
$prototype->with('next', array('id', 'title'));
foreach($manager->findByPk($post, 2) as $post) {
    echo "$post->prev->title"; //Display previous post's title
    echo "$post->next->title"; //Display next post's title
}

Use Case 5: Nested queries

One might wish to find display along with the current post a list of recent posts by the author. For now I'm going to ignore any LIMITs in the underlying query, but the syntax might be something like:

$postProto = new Post();
$postProto->with('Post', array('id', 'title', 'post'));
$authorProto = new Author();
$authorProto->with('Author', array('id', 'name'));
$authorProto->with('Post', array('id', 'title'));    //one-to-many
$postProto->with('Author', $authorProto);
foreach($manager->findByPk($postProto, 1) as $post) {
//A list of posts by this post's author
    print_r($post->author->posts);    
}

This time, we're passing in a prototype of the Author instead of a list of fields. Whatever fields are defined for the Author prototype will be used for the subsidiary query to find posts by the Author.

This post is intended to show how this persistence mechanism might be used, not the exact syntax. In other words, things might change.

Palinflow

Now, this is funny.

Active Record, persistence and all that

The so-called Active Record pattern has gained a lot of currency thanks to Ruby on Rails. According to Martin Fowler:

An object carries both data and behavior. Much of this data is persistent and needs to be stored in a database. Active Record uses the most obvious approach, putting data access logic in the domain object. This way all people know how to read and write their data to and from the database.

The purpose of this post is not to discuss the merits or otherwise of this approach. One supposes that Ruby on Rails does something like what Fowler describes. It should be noted that some would suggest that this violates separation of concerns: a domain object—an object representing either a single database record or a complex of data which together models something like a real-world entity—should not be concerned with retrieving its data from, or writing its data to, a database.

I sort of agree with this, not least because every domain object has to carry around the baggage concerned with storage. One could simply have another object which performs the database transactions and reads data from the domain object for insertion to or updating the database or retrieves data from the database and sets the domain object's properties with its values. This is the approach that eZ Components takes.

Anyway, in thinking about this, I thought that I might learn something about database relations by "rolling my own". For my first attempt, I wrote a fairly simple MySQL database connection object and a statement object based on PDO. (I like PDO a lot.) Then I discovered a side project of Troels Knak-Nielsen, author of Konstrukt, called pdoext. It's a library for composing SQL. It still requires that you have a knowledge of SQL, but it does do some of the grunt work, and is especially useful when certain parts of an SQL query are composed at runtime.

I thought I'd implement composition of common relations such as:

  • OneToOne
  • ManyToOne
  • OneToMany
  • ManyToMany (what, in Rails parlance, is called "has and belongs to many")

However, there are some things I want my library to do which I've not seen elsewhere.

  1. It should spit out domain objects. This cuts out the "middle man" approach of using a gateway of some sort to create and then populate each domain object. PDO facilitates this behaviour with its PDO::FETCH_CLASS constant or PdoStatement::fetchObject() function. (Actually, recently I have seen this done. I just can't remember where!)
  2. It should allow selection of database fields in the query. Many Active Record implementations do something like "SELECT * FROM tablename". Many programmers and database administrators say that one should fetch only what one needs to reduce the amount of traffic between the application and the database server.
  3. It should facilitate some customization of queries (more on this in a later post). I'm sure there are some libraries that do this, so please don't flame me for this point.

Now, I've been working on this for some time, so rather that writing a series of posts about how I'm going (with all the trails and tribulations and changes of mind and so on), I'll write a series of posts that plot a brief history of my decisions. Code examples will be included, of course.

Timecode voodoo

Last week, my institution held a music festival. There were 6 concerts in 5 days. Students and teachers from the TV course brought their portable hi-def studio and cameras. It was our job to record multitrack sessions but also provide them with a stereo mix.

The stereo mix was generated in Pro Tools in real time. We took a timecode feed from TV (sent over an analog cable run of 80 metres (amazing!), set the session start time, put Pro Tools "on-line" (receiving sync from timecode), and hit record. During one act, Pro Tools threw an error message. I didn't see it but my students said an error dialog flashed very quickly and recording stopped. They took Pro Tools off-line and tried to put it on-line again but the timecode reference jumped about 3 hours ahead. It was over a minute before they could get it all working again.

TV's timecode feed didn't miss a beat. The timecode display on the SYNC I/O box was apparently maintained.

I suppose for 6 concerts, each generating 11-12 GB of data, one drop-out is not too bad.

Calrec PQ2647s for sale

If you're reading this between August 16 and 26, 2008, then this entry is valid. If not, then forget it!

I now have one pair of Calrec PQ2647 modules for sale. You can see the listing on ebay or read my previous post.

Update!

They didn't sell, so I've relisted them for a little bit less. Here's hoping....

← Previous  1 … 13 14 15 16 17 Next →

About

A vanity publishing venture of David Rodger, sound production teacher and wannabe PHP developer

User