Instantiate object defined as property in a PHP class as needed (lazy loading)

1.1k views Asked by At

For the sake of simplicity, assume I have 2 classes, User and UserStatus, used in a Web application.

<?php

// library code:
class UserStatus {
  protected $_status = NULL;

  private function fetchDataFromDB() {
    // regular DB stuff
    $this->_status = ...
    // result will be something like 'online', 'away', etc. 
  }

  public function getIcon() {
    global $icon_array;

    if (is_null($this->_status)) {
      $this->fetchDataFromDB()
    }
    return $icon_array[$this->_status];
  }
}

class User {
  protected $user_id;
  public $user_name;
  protected $status;

  public function __construct() {}

  public static function getAll() {
    // some DB stuff
    return $users;
  }
}

// and now, in index.php:
$users = User::getAll();

// echoes the icon to use to reflect the current user status

foreach ($users as $user) {
  echo <img src="$user->status->getIcon()"/>;
}

?>

In most of the HTTP request the status object will not be used so I'm looking for a way to only instantiate it as needed (call it lazy loading). How should I intercept the status->method() call and create that object on-the-fly?

An important note is that I need $user_id available in the UserStatus class, otherwise the fetchDataFromDB() method won't know to which user it should fetch the data. How should this be done?

I've looked at some interesting stuff on this matter like Fabien Potencier's What is Dependency Injection? and Pimple - a PHP 5.3 dependency injection container and also some articles about the Proxy Pattern but to implement them it looks like I have to mess a lot with the current code. Is there a simpler way?

2

There are 2 answers

3
prodigitalson On BEST ANSWER

Maybe im missing something but it seems the easiest solution in this instance would be to have your getter for Status simply create the object if it doesnt exist...

public function getStatus()
{
  if(!isset($this->status))
  {
     // or however you creat this object..
     $this->status = new UserStatus($this->user_id);
  }

  return $this->status;
}

public function __get($property)
{
   $method = 'get'.ucfirst($property); // getStatus
   if(method_exists($this, $method)) 
   {
      return $this->$method();
   }
}

By using the __get magic method anytime you do $user->status it will call $user->getStatus(). Ofcourse you could also always just access it like: $user->getStatus()->getIcon() as well.

However you decide to set up accessing your properties i would recommend doing it in a consistent way across your entire model.

1
yankee On

You could put the status class in a different file and then leverage php's autoloading mechnism:

http://php.net/manual/de/language.oop5.autoload.php

to not load that file until you access it.

There are rumors that auto loading (or actually just any kind of conditional loading) is troublesome for byte code caches and optimizers though unfortunately I don't know too much about the impact.

P.S.: The manual does not say rhis explicity at this point: You can also use spl_autoload_register() instead of just defining the magic __autoload function. This is slightly more powerful.