__get(): An Alternative to __autoload()

  • Posted by Mike Naberezny in PHP

    __autoload() is a magic function introduced in PHP 5 that provides a mechanism for on-demand loading of classes. After its inclusion in PHP, many argued that using such a feature is too magical or not a good design practice. Putting the religious debates over the appropriateness of __autoload() aside, its implementation does have one significant drawback: it is a function declared in the global scope. Once a function is declared, it cannot be redeclared. This means __autoload() can’t be used effectively in shared libraries, since any other code could have already declared it.

    Similar lazy-load functionality can be achieved on the class level by using __get() as shown in this example:

    class Test {
      public function __get($offset) {
          switch ($offset) {
              case 'obj':
                  // include() and instantiate $obj here or elsewhere.
                  // Set $this->obj so we never hit __get() again.
                  return $this->obj = $obj;
              default:
                  throw new Exception("Unknown property $offset");
          }
      }
      public function testLazyLoad() {
          $this->obj->test();
      }
    }

    In the code above, $instance->obj is initially unset. When testLazyLoad() first tries to access it, __get() is called and the object is then loaded and instantiated. Before returning, $instance->obj is set, so subsequent accesses won’t have the overhead of the __get() call. This behavior is somewhat analogous to that of __autoload().

    A downside is that $instance->obj will have public visibility, which may or may not be a problem depending on your application of the technique. Perhaps a small advantage is that while exceptions cannot be thrown from __autoload(), they can be thrown from __get().

    Update: A few people wrote in about spl_autoload(). I did not mention it here because it is only available since PHP 5.1 and thus is not widely available yet. However, if this is an option for you, it’s certainly worth considering.

9 comments

  • comment by Johannes 8 Jan 06

    You can also take a look at spl_autoload() which fixes most of the __autload() problems.

  • comment by elias 8 Jan 06

    You can avoid the public visibility, when you put the instances in a private array/object.
    Btw. Test::$obj is not accessible, only $testInstance->obj. You cannot use __get/__set with static access.

  • comment by Mike Naberezny 8 Jan 06

    I corrected the typo, thanks.

    Regarding the container, that’s true but then the usage becomes much uglier. __get() then moves into the container, and the usage becomes $instance->container->obj->test(). That’s not very convenient.

  • comment by TAC One 8 Jan 06

    I don’t see the point for using __get().
    Your example also manages only one instance for every class

    //not tested
    class FrameworkClassFactory {

    function &load ($className) {
    include (ROOT.’/libs/’.$className.’.php’);
    $instance = new $className ();
    return $instance;
    }
    }

    This sadly doesn’t deal with parameters, you have to use eval () or some kind of acceptable trick (let’s say we surely know we won’t ever need more than 10 parameters).
    (warning it won’t handle references parameters)

    class FrameworkClassFactory {

    function &load () {
    include (ROOT.’/libs/’.$className.’.php’);
    $p=func_get_args();
    $className=array_shift($p);

    for ($i=0; $i

  • comment by elias 8 Jan 06

    if you access the property with __get always (and i think that was your intention) you don’t have to access the container directly.

  • comment by Mike Naberezny 8 Jan 06

    Stefano: I’m lost on how you say the example “only manages one instance for every class”. While the example is just to demonstrate the idea, even the __get() function in the example is written around a switch() construct. It can handle as many properties as you’d like; “obj” was just one example for demonstration. You can continue to add them to the switch or use __get() to call whatever fancy object factory you’d like. The idea isn’t to make __get() a general purpose factory for all your needs, it’s just to facilitate some lazy-load behavior on the class level.

    Elias: You need to read the example again. The access to “obj” goes to __get() only once. When that happens, $instance->obj is then set and __get() never gets hit again, so there’s no overhead. This is how the idea is somewhat analogous to __autoload().

    Mike

  • comment by Lukas 9 Jan 06

    Mike I think Stefano is talking about:
    return $this->obj = $obj;

  • comment by Mike Naberezny 9 Jan 06

    It’s only an example and is provided in a simplified form to demonstrate the concept more clearly. You can implement it in all kinds of fancy permutations.

    The point is that an instance variable isn’t set, __get() catches the first access to it, some lazy-load occurs, and then the instance variable is set for future accesses.

  • comment by elias 9 Jan 06

    ah now i got your point. besides that my container idea is worth nothing because __get is always public. there no benefits in encapsulation. :)

    if you can live with write access to properties, this is a very nice/lazy factory.

Post a comment


Thanks for commenting. Please remember to be nice to others and keep your comment relevant to the discussion. I reserve the right to remove comments that don't meet these simple guidelines.