Legacy app

For one unit I teach, I've been using a home-baked web application. At the time I wrote it, I needed to find a web app framework in PHP that could get me up and running quite quickly.

Well, little did I know that Cake 1.1 was a bit of a dog. There were several WTFs such as having to specify form fields in a certain way, even though the model would not return results in the same way— very inconvenient if you wanted to autofill form fields. Little things like that made what should have been a quick and dirty development turn into a 200 hour pain-in-the-rear-end.

Anyway, that can now be considered a "legacy" app, and I really should have rewritten it by now using something nice and relatively straightforward, like konstrukt. I don't have time, and after this year I won't be teaching that unit, because it's being junked along with the rest of the Music Industry Training Package CUS01.

So I have to make it PHP5.3-compatible, so I don't get messages like this:

Deprecated: Assigning the return value of new by reference is deprecated in /www/audioio.com/include/cake_1.1.13.4450/cake/libs/class_registry.php on line 55

There's a bit of editing by hand to be done, but I found a little script for multi-file search and replace that really helped.

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.

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.

PHP scope and get_object_vars()

I've been trying to use the PHP function get_object_vars(). The description of the function says that...
Gets the accessible non-static properties of the given object according to scope.
Well, sort of. Take this code for example...
class Foo {
    private $priv = 1;
	
    protected $prot = 2;
	
    public $pub = 3;
	
    public function showFromWithin() {
        echo 'From within: ';
        print_r(get_object_vars($this));
        echo "<br />\n";
    }
	
    public function showFromOutside($obj) {
        echo 'From outside: ';
        print_r(get_object_vars($obj));
        echo "<br />\n";
    }
	
    public function showAnother() {
        echo 'Another: ';
        $o = new Bar();
        print_r(get_object_vars($o));
        echo "<br />\n";
    }
}
class Bar {
	public $a;
	protected $b;
}

$foo = new Foo();
$faz = new Foo();
$foo->showFromWithin();
$foo->showFromOutside($foo);
$faz->showFromOutside($faz);
$foo->showAnother();

Here's the result:
From within: Array ( [priv] => 1 [prot] => 2 [pub] => 3 )     //foo
From outside: Array ( [priv] => 1 [prot] => 2 [pub] => 3 )  //foo
From outside: Array ( [priv] => 1 [prot] => 2 [pub] => 3 )  //faz
Another: Array ( [a] => )  //Bar object
I would have thought that calling from outside the object would result in only the public class variables being listed. Maybe in the second case, where an instance of Foo is looking at itself, it works as expected. But when one instance of Foo looks at a different instance of foo ($foo2), shouldn't the private and protected class variables not show? I don't get it.

phpmelb meeting - May 2008

I went to the PHP Melbourne User Group meeting on Thursday evening. There were two presenters:

Avi Miller from Squiz, vendor of MySource Matrix, an open source CMS, talking about PHP Code Sniffer, a tool which checks compliance with coding standards.

Ben Balbo, long-time member of phpMelb and current office-holder (I'm embarrassed that I can't remember which office), giving a demonstration of views and stored procedures in MySQL.

PHPCodeSniffer is certainly cool, not least because it's very clever and I suppose it has its uses where adherence to coding standards is held to be a high priority. However, I enjoyed Ben's talk as it's the simplest and easiest explanation of views and stored procedures that I've ever seen. Must try out those features!

 1

About

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

User