1

I'll give a basic example, let's say I have 3 classes - Config, Database and User. User needs Database and Database needs Config, but User might also need Config. All of the classes are going to need just one instance because I need to load the config file just once, connect to the database just once and check if the user is logged in and get their data just once. Now, what would be the best way to tell from within each class which classes it will need and have them load automatically? Or is this bad practice and I'm just being lazy? I tried making all classes singletons and accessing them like this from within each class:

User.php

$this->db = Module::get('Database');

But then I had to create a custom __construct() method and have that run whenever I made a new instance of a class for the first time, which felt off-putting. I did some research and read that singletons aren't exactly good practice and dependency-injection would be a better solution, however I have no idea how to achieve this with dependency-injection. So I'm wondering, is this even a good idea or should I re-think how I want everything to work?

user3117244
  • 143
  • 1
  • 8

2 Answers2

1

User needs Database and Database needs Config, but User might also need Config

Sounds like you need dependency injection. There are existing packages out there that can do this, but if you're making it yourself, you could create a class called DIContainer that has a single purpose of setting and getting object instances, then pass this class to other classes that might need what's in it.

class DIContainer {

    protected $_objects = array();

    public function __set( $name='', $instance=null )
    {
        if( !empty( $name ) && is_object( $instance ) )
        {
            $this->_objects[ $name ] = $instance;
        }
    }
    public function __get( $name='' )
    {
        if( !empty( $name ) && array_key_exists( $name, $this->_objects ) )
        {
            return $this->_objects[ $name ];
        }
        throw new Exception( 'Object not found ('.$name.').' );
    }
}

Then just instantiate and setup your common classes and add them to DIContainer:

// setup config 
$config = new Config(); 
$config->loadFile( './conf.php' ); 

// setup db 
$database = new Database( $config ); 
$database->connect(); 

// setup session
$session = new Session( $config );
$session->start();  

// save dependencies 
$container = new DIContainer;
$container->config = $config;
$container->database = $database;
$container->session = $session;

// later when you start a new User class 
$user = new User( $container ); 

Now the user class can get what it needs, just setup the constructor method to handle the DIContainer:

class User {

    protected $container = null;

    public function __construct( DIContainer $container )
    {
        $this->container = $container;
    }

    public function getRows()
    {
        $rows = $this->container->database->getRows( '...' );
    }
}
Rainner
  • 589
  • 3
  • 7
0

You're looking for autoloading in php. This topic has been discussed on SO and there is some good explanation here.

In brief, at the beginning of your file (after declaring the namespace), you tell php what does your class depend on.

namespace Entity;
use ORM\Database;
use Config;

//...

$d = new Database();
Config::set('a', 'b');

You might also want to go for Composer for DI when using external libraries and managing autoloading in your code without reinventing the wheel.

Here's what Composer docs say about autload:

For libraries that specify autoload information, Composer generates a vendor/autoload.php file. You can simply include this file and you will get autoloading for free.

require __DIR__ . '/vendor/autoload.php';

This makes it really easy to use third party code. For example: If your project depends on Monolog, you can just start using classes from it, and they will be autoloaded.

$log = new Monolog\Logger('name');
$log->pushHandler(new Monolog\Handler\StreamHandler('app.log', Monolog\Logger::WARNING));
$log->addWarning('Foo');

You can even add your own code to the autoloader by adding an autoload field to composer.json.

{
    "autoload": {
        "psr-4": {"Acme\\": "src/"}
    }
}
Community
  • 1
  • 1
Alexander Mikhalchenko
  • 4,525
  • 3
  • 32
  • 56
  • 1
    I think OP needs a dependency-injection solution. An autoloader without dependency injection will not solve the problem: "All of the classes are going to need just one instance" – gontrollez Jan 12 '16 at 12:12