Overloading: Create PHP Class Methods on the Fly


overloading an outletWhat is overloading and what would I need it for? Well, how many times have you built an application and needed to retrieve data from the database in a multitude of different ways.  Sometimes you will get a record by its id number, other times you may want to get a list of records based upon searching a certain field, but the field you are searching on may vary from page to page.  If you are using a framework, chances are you are building the same basic database calls in your models or controllers over and over again, slightly modified to meet the needs of the given page.  Or maybe you are building models (classes) that have a specific method or function for each variation of records retrieval.  If this is the case you will have method names like:  getById() ,  getByUsername() ,  getByStatus() , etc.

But wait a minute, aren’t we supposed to be striving for the DRYest (Don’t repeat yourself) code we can get?  It just doesn’t make sense to keep typing the same thing over and over again with the slightest of changes to get the desired result.  If any of the above sounds familiar to you, then your code may be a candidate for overloading.

In most languages, overloading just means you can have multiple methods with the same name, but they just had a different number/type of arguments.  In PHP, it is a little different.  Overloading in PHP means that you can actually create dynamic function names and the behavior will be dependent upon the function name that is used.

The Business Requirement

overloading table exampleOur sample application requires that we manage purchase orders.  Part of the management of these purchase orders involves pages where we will get a list of orders by status. Additionally, we will want to be able to get a list of orders that a particular user has entered.  Finally, we need to be able to retrive a single record for viewing and editing details.  Since all three of these tasks involve basically the same thing, getting row(s) from a table based on a specified criteria, let’s use overloading to make this quick and easy.

Our database table to hold purchase order header records is aptly named  po_headers and looks a lot like what you would typically expect of such a table:

 

The Solution using Overloading

Let’s get to the overloading code.  We will build a simple class called  Database that will be used for record retrieval.  In more complex applications, you would probably want to build separate models for each logical object in your database, but for illustrative purposes, we are going to keep it a little simpler.

First, let’s set up the class and put the database connection into the constructor.  Since we may want to use this for many different database objects, let’s also set it up to pass the table name we want to work with to the constructor.

Now that we have a class to work from, we have to establish some ground rules for how we are going to use overloading within the application.  Let’s set the following rules to live by:

  • Database field names will be lower case, using underscores to separate words
  • Method names will be in camelCase.
  • Dynamic record retrieval method names will always begin with “getBy” and end with the field name in camelCase. (e.g. getByOrderDate()  ) .
  • Dynamic methods will have a single parameter, and that parameter will represent the term being used to narrow the search results (e.g. the Order Date, the Order Id, etc.)

Ok, now the rules are set, let’s build our overloading logic using the  __call() magic method, which provides the vehicle to process dynamic methods.  This magic method will process the dynamic method names and return the desired data accordingly.  So let’s insert the following method into our class:

There is a lot going on in the method above, but you can see through the inline comments that the basic progression is to first, remove the “getBy” from the method, then convert the camelCase reference to the field name from the method into a proper field name format using underscores.  From there we simply query the database, and depending on whether we are looking for a single record or multiple records, we build the values to return.  Voila! we have successfully created a handler for dynamic methods via overloading.

Using Dynamic Methods

Now that we can process dynamic methods with our new handler method, it’s time to put them to good use.  You will recall that we had a few different requirements for data retrieval.  The data we want is in the po_headers  table.  Let’s begin by grabbing a single record by the Purchase Order Number, which is stored in the  id field.

Simple right? Now let’s try again and get some more data.  This time, we want to get a list of all open purchase orders, which are in status ‘O’, which is stored in the po_status  field.

In the same way, we can create any method name & parameter on the fly such as getBySupplierCode('XYZ')  , getByWhCode('034')  , or  getByEnteredBy('andy')  and we can retrieve however we need to without multiple line database calls or creating similar functions over and over again.

Yes, it Works on Static Methods Too!

As of PHP 5.3.0, you can also invoke the __callStatic()  magic method to accomplish overloading for static methods.  As an example, in some of my applications that do not sit on a framework, I use a dynamic set of static methods to cleanse and access data in the get, post, session, and server superglobal arrays.  Rather than having to cleanse the value every time I need it, and rather than create a handler for each superglobal which essentially did the same thing, it made sense to me to use overloading through __callStatic() to take care of this.

The Limit is your Imagination…

Overloading can be used for anything from the very simple to the very complicated, and really you are limited only by your creativity.  The Lithium framework uses overloading in very much the same spirit as this example, though their implementation for dynamic record finders is much more sophisticated than what I have shown.  In any case, I hope that you find this tutorial useful and that if you ever find yourself in a situation like the ones I have described, that overloading will make your code simpler and reduce your time to implement your desired functionality.

For a little deeper example, head over to my Github site, and look at this example of simple MySQL CRUD Class using overloading.  In that example, you can Create, Retrieve, Update and Delete records with simple queries, and it is possible to connect to any table, and pass the table name as part of the method name (e.g. getPoHeadersById($id)  ).

I hope you find a way to use overloading to make your life easier in your future PHP endeavors!

-Andy

, , , ,

  1. #1 by Christian on March 6, 2013 - 3:44 pm

    Wouldn’t really call this overloading, at least in the traditional sense (same method name, but differing signatures/input); if you are going to use dynamic methods, its far more efficient to wrap definition in a lambda and add to a callback table (associative array, ie invoke $this->methods[$name]).

    As for class/static dynamic methods, try invoking one from an instance context (ie within a instance method) and see what happens! As a hint, the static scope resolution operator (::) means absolutely [moderated] nothing in PHP!

    • #2 by Andy (@phpAndy) on March 6, 2013 - 3:48 pm

      Thanks for the feedback, appreciate your insights & I appreciate you stopping by and reading.

  2. #3 by Atli on March 7, 2013 - 7:07 pm

    While the approach used by your example case is interesting in theory, in practice it just unnecessarily complicates what is essentially a pretty simple matter. A “getBy” method that accepts a field name and the value as parameters, and executes the query based on that, would be just as effective, more efficient (marginally), and far less confusing to use.

    $db->getById(5);
    $db->getBy(“id”, 5);

    • #4 by Andy (@phpAndy) on March 7, 2013 - 7:41 pm

      Agreed, I was just trying to illustrate a simple example of how overloading can be used. In practice, the ones I use in production read the table name and the get by field in a single method name. I just didn’t want to get that deep into it in this example. Thanks for stopping by and posting a comment!!

  3. #5 by abide on March 11, 2013 - 6:26 pm

    This is like ORM, isnot it?

    • #6 by Andy (@phpAndy) on March 11, 2013 - 8:07 pm

      The example of using __call() for dynamic finders to access a database is kind of like ORM / Active Record pattern. However, overloading is not necessarily just for database access. You could use it for other types of dynamic method names as well. I only used this example because it seemed like something a lot of people would be able to relate to. thanks for reading and commenting!

  4. #7 by Wes Mason on March 13, 2013 - 1:21 pm

    Great article, I blogged about this back when lambdas and closures were being introduced to 5.3:
    http://1stvamp.org/dynamically-adding-methods-and-static-methods-onto-classes-at-runtime-in-php53

  5. #9 by lancel sacs a main on March 20, 2013 - 1:45 am

    Its just well thought out and really fantastic to see someone who knows how to put these thoughts down so well

Comments are closed.