0

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?

sth
  • 222,467
  • 53
  • 283
  • 367
noisebleed
  • 1,299
  • 2
  • 12
  • 26

2 Answers2

3

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.

Kris
  • 14,426
  • 7
  • 55
  • 65
prodigitalson
  • 60,050
  • 10
  • 100
  • 114
  • It is indeed a way to do it but the `UserStatus` class provides several methods, one of them being the `getIcon()` as shown above. Using your suggestion I must call the icon method in a two step procedure: `$user->getStatus(); $user->status->getIcon()`. I was trying to make it _more clever_ but hey, it works. Thanks. – noisebleed Feb 06 '11 at 14:33
  • no you dont you can use the magiv `__get` i assumed you were going to do that since `status` was protected and not public - otherwise you couldnt access it directly from outside your User class... so i didnt include it... see my update. – prodigitalson Feb 06 '11 at 16:00
  • Yep, the idea was to use `__get()` and I left `status` protected on purpose, but I didn't code `__get()` simply because I didn't knew the best approach on how to do it. Your update solves my doubts, it's a really nice approach and I'm happy to take it. Thanks again. About _doing it in consistent way_, it's a good advice, that I'm not respecting right now :( I'm considering a rewrite of my home-made-framework using Symfony components like DependencyInjection and EventDispatcher, but I'm still a bit confused on how to layout them. – noisebleed Feb 06 '11 at 22:05
1

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.

yankee
  • 38,872
  • 15
  • 103
  • 162
  • I have several small classes and putting them in a file each is not something I would like to do. I will try to solve this issue with something in the line of what prodigitalson suggested and then comeback here if it doesn't work. Thanks. – noisebleed Feb 06 '11 at 14:39