5

I am creating a Drupal 7 module, and i have a file with a class definition in it. I am able to instantiate this class successfully. Where is the best place to instantiate this class in my .module file and make that object available to my module hooks?

For instance, I don't want to do the following:

function mymod_block_view{
  $obj = new Object()
  ....
}

function mymod_block_info() {
... 
$obj = new Object()
}

Normally I would just use dependency injection, but I can't add arguments to the function definitions (so far as I know)...How can I only instantiate my object once and get the members and methods of the instantiated object available to my hooks?

| improve this question | | | | |
7

"Best" won't get you too far here; there are a bunch of different ways, none of them particular to Drupal, and all with advantages and disadvantages. Go with what you're comfortable maintaining.

That said, a simple singleton pattern would make most sense to me:

class MyClass {

  protected static $instance = NULL;

  protected function __construct() { }

  public static function getInstance() {
    if (!isset(static::$instance)) {
      static::$instance = new static();
    }
    return static::$instance;
  }

  // Remaining class implementation

}

// Use this as many times in a single page request to get access to the same instance of MyClass every time.
$instance = MyClass::getInstance();

I feel like I should add a bit about autoloading in Drupal 7, in case you haven't already come across the options...

Drupal 7 implements a simple autoloader; in the module's .info file you can specify the path to a file containing classes:

files[] = MyClass.php

and any class in that file will be automatically available to you anytime you want it, without having to explicitly include the file.

If you want to go one step further, check out the excellent X Autoload module. It provides a PSR-0 autoloader, which mirrors Drupal 8's autoloading.

I think PSR-4 is in the pipeline too, but that's another story...

| improve this answer | | | | |
  • I like this idea. I'm goin to give it a shot - thank you! – JRizz Nov 22 '13 at 18:23
  • I have the files[] variable pointed at my class definition file in my .info file, so I was able to get that figured out. Thanks again. – JRizz Nov 22 '13 at 19:25
  • I just wanted to follow up with another question. Everything I've read has suggested singletons are unnecessary in PHP, but the reason they're unnecessary in because of Dependency Injection! When Dependency Injection is not an option, is Singleton the way to go? – JRizz Nov 25 '13 at 16:20
  • 2
    Singletons aren't inherently necessary or unnecessary in PHP (or any language for that matter). Whatever you're reading is probably heavily biased towards Dependency Injection, which, don't forget, is nothing to do with PHP - it's another design pattern. It's not likely you'd use DI and singletons together because of the redundancy, so an advocate of DI would certainly say that singletons are unnecessary. As far as I'm concerned both patterns are tools for the toolbox - you decide which one to pick up based on the job you're taking on – Clive Nov 25 '13 at 16:25
  • thanks. I suspect you are right. I'll do more research on Singleton and implement it. Thanks again - – JRizz Nov 25 '13 at 16:29
1

In general, a singleton is just a global variable in a nice dress. We should avoid it as much as possible. But in Drupal there is no way to completely avoid globals. We can only try to reduce their usage.


About the singleton approach: Many existing Drupal modules use a procedural-style singleton:

function household_get_manager() {
  static $manager;
  if (!isset($manager)) {
    $manager = new Drupal\household\HouseholdManager();
  }
  return $manager;
}

This is technically not so different from Clive's answer, but I think it is used more often within Drupal. I personally prefer it, because it separates the class definition from its instance.


This being said, often it is more than one object that you want to treat like a "global". And often one of these objects depends on another.

With the singleton approach, your household manager instance would need to make another singleton call to e.g. the Shovel::getInstance() and Broom::getInstance(), whenever it needs one, etc.

I personally often build a "poor man's DIC" (Dependency injection container), to organize the classes/objects provided by the module, and their dependencie.

There are different ways to do this, and I am not yet sure which variation I should recommend. Heck, I am not even sure if I should recommend the entire thing :)

The Poor man's DIC object itself is still going to be a singleton. But the objects themselves have their dependencies injected by a factory.

This way you can write this in your hooks:

$shovel = household()->shovel;
$manager = household()->manager;

From outside, this still looks and behaves very global-ish. But at least on the inside, you can remove all static and global calls from your worker objects.

class HouseholdManager {
  protected $shovel;
  /**
   * @param Shovel $shovel
   *   Shovel object injected from the factory.
   */
  function __construct(Shovel $shovel) {
    $this->shovel = $shovel;
  }
}

Posting the entire Micro-DIC here would be too much. So maybe you want to look at an example instead. I could not find a really perfect one, but this here is at least simple to look at: http://drupalcode.org/project/krumong.git/blob/refs/heads/7.x-1.x:/lib/Drupal/krumong/ServiceCache/ServiceCache.php http://drupalcode.org/project/krumong.git/blob/refs/heads/7.x-1.x:/lib/Drupal/krumong/ServiceCache/ServiceFactory.php There is some magic going on here with the magic __get(). Some people don't like this. Also, this desperately needs docblock comments.

One trick I learned is you can use @property docblock for type hints on your container's magic properties. I don't have an example for this atm, sorry. Some of this is still waiting on my localhost to be committed.

| improve this answer | | | | |

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.