Partial Objects - Holy Premature Optimisation Batman


In this video we look at how we can retrieve Partial Objects from Doctrine, and also why this may very well fall into the category of "premature optimisation".

A Partial Object is an object that we would expect, and that would be recognised as being an object of the expected type, but some of its properties would be null.

Now, these nulls can cause issues, as we will see shortly. That's a big reason why this is likely to fall into premature optimisation. But we'll get to that.

Let's imagine we have a database table full of Topics.

There's all kinds of data in this table - id, title, short description, created at, updated at, and a myriad of other stuff that we seem to accumulate over time.

Our users don't care one jot about most of this. All they care about is the topic title.

So, here we have a situation where in the olden days we would have done something SQLy like:

SELECT id, title FROM topics LIMIT 10

Or something.

That way, we aren't getting back a ton of data we don't need. The query is lighter, the result comes back a little faster, and everyone's a happy camper.

But then we switched our Forum over to Symfony2 and Doctrine.

Suddenly we're using an ORM and we don't seem to give as much attention to our queries.

As a result, our returned data is a big collection of full fat objects, each one containing all the properties we don't need, as well as the ones that we do care about.

What's The Solution?

As with almost every Symfony / Doctrine / modern PHP problem, there are, of course, multiple solutions.

We can start easily enough by changing up our Query Builder from the following:

$topics = $qb->select('t')
  ->from('AppBundle:Topic', 't')
  ->getQuery()
  ->getResult()
;

(which would pull out fully populated Topic objects)

to this:

$topics = $qb->select('t.title')
  ->from('AppBundle:Topic', 't')
  ->getQuery()
  ->getResult()
;

Which instead, would return an array of arrays, with each sub-array containing one item - "title".

Now, this is fine, but we just lost the concept of a Topic being an Object.

That might be ok, or it might cause some unexpected issues. What if you needed the id field as well?

Well, that's easy enough:

$topics = $qb->select('t.title', 't.id')
  ->from('AppBundle:Topic', 't')
  ->getQuery()
  ->getResult()
;

Congratulations, your sub-arrays now each contain two items, title and id.

But what about your unfortunate downstream colleague who was kinda hoping you would be sending along some Topic objects?

To placate your colleague's murderous rage, we can look into using Partial Objects.

The syntax is - overall - very similar. The only thing we change up is the contents of our select:

$topics = $qb->select('partial t.{id,title,created_at}')
  ->from('AppBundle:Topic', 't')
  ->getQuery()
  ->getResult()
;

This way, we can return to the SQL-style, more specific result, but still take advantage of getting back ready-to-use Objects.

Gotcha

As you'll see in the video (around the 2:50 mark), things start to get a little funky when we do this.

Any property we don't explicitly request will return a null.

In our video example, that results in a pretty trivial issue - the Topic title is no longer displayed.

But what if later code expected to have access to those now null properties, and that whilst null is still an acceptable value to receive, the outcome of that later code is determined against null, not the real value in the database?

Lord knows how bad that could be.

As such, Partial Objects need to be used with absolute care.

In fact, I would go as far as to say, unless you have a very, very good reason to be using them, then don't bother with them at all.

But it's good to know it's possible, an extra trick in your arsenal, and it's a real conversation starter at cocktail parties*.

* - lies.

Code For This Course

Get the code for this course.

Episodes