PHP: Comparing Object Structures

apples-and-oranges1
apples-and-oranges1

I'm working on a project where I am converting an established REST API over to a rabbitMQ service.  Because, you know, dinosaur, I'm continuing to use PHP as my language of choice for both the rabbitMQ producers and consumers.  (Sorry - just not there with Python.  Yet.)

Anyway, as part of the architecture, I'm building a rabbitMQ service to handle all incoming requests that would normally be handled as POST requests thought the REST API.

Our data flow necessitates the rapid store of several different types of objects loosely associated with real-world events and objects:  users, registration, browser events, etc.  You get the idea:  the data is different but kind-of the same...different enough to require different class objects (because of the data retrieval or storage requirements) yet close enough that, as the programmer, you're constantly plagued by deja-vu when it comes to coding the class methods.

The development team collectively decided to make the move to a queue-messaging system so I evaluated and chose RabbitMQ as the framework to use to convert my existing REST API.

Without going too deep into details (a future article and I want to keep this post somewhat easy), I have the option of pooling the collection of incoming data that, under REST (segregated based on object functionality: members, subscriptions, payments, etc.), into a single collection point.  In other words, using REST, each real-world object has a dedicated service which is represented in the REST/data stack as a REST service attached to a PHP class object which is extended from a base data class dependent on mongo which, in turn, is extended from a base data handling class (auditing, memcache, etc.).

What that means is that if I have 20 or so services offered via REST, I probably have 20 different REST map files that process a POST request that can instantiate up to 20 different object classes unique to the service being offered.  No problem - but under RabbitMQ, this can be done better.

The problem/challenge I encountered (I'm intentionally skipping ahead here because I don't want this article to be about either RabbitMQ or REST) is that I wanted establish a single consumer service for accepting and storing the input data for all of my object classes.  In other words, for any incoming data to be stored, I want to be able to peek at the structure being used to store the data and then invoke the proper class to handle data validation and storage.

So ... the point is, I want my RabbitMQ queue consumer to evaluate the incoming data structures and determine which data classes I'll need to work with and to dispatch the work accordingly to the class handlers.

Comparing objects is the point of this article -- if I get an object for a user, I want to invoke the user class.  If I get a registration data-packet, the registration class and so-on...

This, in-turn, led me down the rathole of PHP object comparison and I picked-up a few things to share with you...so, at least, we get to the point of the article.

When comparing PHP objects, you usually have one of two goals in-mind:  I want to see if the object contents are the same, or I want to see if the object structures are the same.

For the purposes of my program, I want to evaluate the structure itself and dispatch the data to the appropriate class handler; I don't care about the contents -- that's the designated responsibility of said class handler.

But, fear not, I will talk about comparing PHP objects as it relates to the contents.  For this, we'll use the '==' and '===' operators.

Conceptually, I am going to create two base classes, named cat and dog.  From these classes we'll instantiate variable structures and compare these structures against each other.  The code to do all this is at the bottom of this article so here's the walk through:

The Plan

I am going to instantiate two variables:  $siamese and $persian of members of the cat class.

I am going to instantiate one variable: $husky as a member of the dog class.

I am going to clone the variable $malamute from $husky.

Both dog and cat have identical class variables and methods.  (For simplicity, one of each.)

After I create my variables, I am going to compare them (within their classes) for equality  ('==') and then compare them for identity ('===').

Next, I will increment $siamese and $husky and repeat the above checks.

Next, I will declare a copy of $husky and name him $einstein (my dog!) and repeat the checks.

Then I will determine who's a cat and who's a dog -- looking at the structure of the class and not the contents.

And, finally, I will var_dump the relative structures so you can see the contents.

The Results

In the initial comparisons of $siamese to $persian, the comparison operators evaluate true but the identity operators evaluate false.  This makes logical sense:  Siamese and Persians are both cats, but this Siamese has a different identify from this Persian.

When I increment the member variable ($a) within $siamese and re-run the comparison, both checks now evaluate false.  The Siamese no longer has the same contents as the Persian and they still don't have the same identity.

For sanity, I check to ensure that a $husky is not a $persian in both comparison and identity checks and, thankfully, it's not.

Next, I clone $malamute from $husky and run the basic checks:

The Malamute has the same contents as the Husky, but they do not have the same identity.

Next, let's create $einstein by making him a copy of $husky with the assignment ('=') operator and run the comparisons -- no surprise:  Einstein has the same contents as Husky and has the same identity.  The identity check evaluated true because both objects share the same memory space and this is shown by:

I call the method in $einstein to increment the member variable by one and re-run the comparison:  both checks again evaluate to true because we're manipulating shared memory space objects.  In other words, although both $einstein and $husky have the same member variable ($a), both variables are pointing to the same physical storage location in memory.  So, if I change the contents of $a in $einstein, the contents of $a in $husky changes as well.

By var_dump'ing (dispaying the contents) of these variables, I can see that the $husky's member variable ($a) has been incremented even though the increment operation was done by the $einstein object.

Finally, let's use the PHP function instanceof to evaluate the type of class we have.  This is the dispatch mechanism I am using in RabbitMQ to invoke the correct object class for processing my incoming data.

I use instanceof to ask if $siamese is a dog or $husky is a cat.  Pretty straightforward -- and I get the expected results in the output.

Wrap-Up

So, hopefully, you know when to use '==' and '===' when comparing PHP objects and what an object identity is, and the difference between comparing the contents of a class versus evaluating the structure of a class.

Source code and sample output below...hope this helps!

 

class cat 
{
public $a = 1;

public function addone()
{
$this->a = ++$this->a;
}
}

class dog
{
public $a = 1;

public function addone()
{
$this->a = ++$this->a;
}
}

$siamese = new cat();
$persian = new cat();
$husky = new dog();
$malamute = clone $husky;
$einstein = $husky;

if ($siamese == $persian) {
echo 'siamese == persian' . PHP_EOL;
} else {
echo 'siamese != persian' . PHP_EOL;
}

if ($siamese === $persian) {
echo 'siamese === perisan' . PHP_EOL;
} else {
echo 'siamese !== persian' . PHP_EOL;
}

echo 'calling: siamese->addone()' . PHP_EOL;
$siamese->addone();

if ($siamese == $persian) {
echo 'siamese == persian' . PHP_EOL;
} else {
echo 'siamese != persian' . PHP_EOL;
}

if ($siamese === $persian) {
echo 'siamese === persian' . PHP_EOL;
else {
echo 'siamese !== persian' . PHP_EOL;
}

if ($husky == $persian) {
echo 'husky == persian' . PHP_EOL;
} else {
echo 'husky != persian' . PHP_EOL;
}

if ($husky === $persian) {
echo 'husky === persian' . PHP_EOL;
} else {
echo 'husky !== persian' . PHP_EOL;
}

echo 'clone wars...' . PHP_EOL;

if ($husky == $malamute) {
echo 'husky == malamute' . PHP_EOL;
} else {
echo 'husky != malamute' . PHP_EOL;
}

if ($husky === $malamute) {
echo 'husky === malamute' . PHP_EOL;
} else {
echo 'husky !== malamute' . PHP_EOL;
}

echo 'playing with Einstein: ' . PHP_EOL;

if ($einstein == $husky) {
echo 'einstein == husky' . PHP_EOL;
} else {
echo 'einstein != husky' . PHP_EOL;
}

if ($einstein === $husky) {
echo 'einstein === husky' . PHP_EOL;
} else {
echo 'einstein !== husky' . PHP_EOL;
}

echo 'calling einstein->addone()' . PHP_EOL;

$einstein->addone();

if ($einstein == $husky) {
echo 'einstein == husky' . PHP_EOL;
} else {
echo 'einstein != husky' . PHP_EOL;
}

if ($einstein === $husky) {
echo 'einstein === husky' . PHP_EOL;
} else {
echo 'einstein !== husky' . PHP_EOL;
}

if ($siamese instanceof cat) echo 'siamese is a cat' . PHP_EOL; if ($siamese instanceof dog) echo 'siamese is a dog' . PHP_EOL; if ($husky instanceof cat) echo 'husky is a cat' . PHP_EOL;
if ($husky instanceof dog) echo 'husky is a dog' . PHP_EOL;

echo 'var-dumps:' . PHP_EOL . 'husky: ' . PHP_EOL; var_dump($husky); echo PHP_EOL . 'malamute: ' . PHP_EOL; var_dump($malamute); echo PHP_EOL;

Output:

 

siamese == persian 
siamese !== persian
calling siamese->addone()
siamese != persian
siamese !== persian
husky != persian
husky !== persian
clone wars...
husky == malamute
husky !== malamute
playing with Einstein:
einstein == husky
einstein === husky
calling einstein->addone()
einstein == husky
einstein === husky
siamese is a cat
husky is a dog
var-dumps:
husky:
object(dog)#3 (1)
{
["a"]=> int(2)
}

malamute:
object(dog)#4 (1)
{
["a"]=> int(1)
}