If I run a query simialr to:
SELECT * FROM user u
LEFT JOIN orders o ON o.user_id=u.id
LEFT JOIN payments p ON p.order_id=o.id
Resulting in data like:
u.id, u.name, o.id, o.item, o.date_time, p.id, p.amount, p.date_time
99 jeff 17 spring 12-12-2012 12 10.99 13-12-2012
99 jeff 18 jam 12-12-2012 13 .99 16-12-2012
99 jeff 19 car 22-12-2012 14 1000 17-12-2012
99 jeff 19 car 22-12-2012 15 1000 18-12-2012
99 jeff 19 car 22-12-2012 16 1000 19-12-2012
So user Jeff has 3 orders, he has made 3 payments towards his car.
Give the PHP objects User, Order and Payment and a single DB query - how do they get hydrated? I would like to see pseudo-code, actual php code or the pattern name I should read up on :)
Because no answers, I'll try to describe how I solved this. This is a toy solution.
Queries to the database are done in a little language I called QQL, this was inspired by Doctrine 2 DQL.
SELECT *
FROM user
JOIN user order
JOIN order payment
WHERE user.name=?
This gets parsed and an SQL statement can be built. The parse tree is also used to map the result set back to an object graph.
Each model has a description of 'relations' to other models. So User has a one-to-many to Order, Order has a one-to-many to Payment. The description is in an array containing the model name, the primary key and foregin key names.
To build the JOIN:
JOIN user order
Inspect the User model, get the table name 'users', then find the relationship 'order', get the Order table name 'orders' and joining keys. Use this to build:
JOIN orders o ON o.user_id=users.id
Once the query has run and results are returned to build the object graph. What I did was get all the distinct models used in the query (in this case User, Order and Payment) then for each row Hydrate each one:
// query DB and get results into an array called $rows
foreach ($rows as $row) {
foreach (array('User', 'Order', 'Payment') as $model) {
$o = new $model;
$o->hydrate($row);
// inspect primary key - have we got this object already? store or throw away
}
}
My hydrate method was extremely thin (quick) because a lot of the objects that are built will be duplicates and get removed. From the result set in my question you can see that User('Jeff') will be built 5 times, 4 of which are duplicates and will be discarded.
Once the results have been read, there are 3 lists of objects. Users, Orders and Payments. These are passed to a Graph Builder along with the parse tree.
The Graph Builder uses the parse tree to look at the relationships. Starting at the 'root' model (determined by the "FROM user"), inspect the parsed QQL to find the JOIN requested (User->id TO Order->user_id) adds the Orders to the User->orders array. It then does the same with (Order->id TO Payment->order_id).
The result is:
$user->name == 'jeff'
$user->orders[0]->item == 'spring'
$user->orders[1]->item == 'jam'
$user->orders[2]->item == 'car'
$user->orders[2]->payments[2]->date_time == '19-12-2012'
I ended up with four main classes, the ORM, a Model_Base that all models extend (this makes sure each model has a 'table name', 'columns' and 'relations'), a QQLParser and a Graph Builder. The ORM class was by far the largest at nearly 200 lines.
Conclusion. It works. It feels similar to Doctrine 2, (so my eventual transition to using Doctrine 2 will be less painful). It could be more efficient. Results of profiling a test page reading in a few thousand objects, by far the slowest parts were the SQL query (3ms) and freeing the mysqli results (1ms) including the model classes (0.7ms). Writing it was fun and only took a day.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With