Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dependancy Injection where virtualy every class is dependant on several others

I'm trying to implement best practices while learning PHP OOP. I understand the concept, but kind of doubt with proper implementation. As I'm trying to figure out the basic implementation principle, I'm not implementing DI container in this piece of code.

Structure

  • Db class for database connection.

  • Settings class, retrieve settings from db.

  • Languages class, retrieving information for a specific language.

  • Page class, Product class, Customer class, and many more.

Idea

Settings class needs Db class to retrieve settings.

Languages class needs both Db and Settings to retreive information based on settings from a database.

Page class needs Db, Settings and Languages. It may also need some other classes in the future.

Simplified code

Db.php extends PDO

Settings.php

class Settings
{
    /* Database instance */
    protected $db;

    /* Cached settings */
    private $settings   = array();

    public function __construct(Db $db)
    {
        $this->db = $db;
    }

    public function load ()
    {
        $selq = $this->db->query('SELECT setting, value FROM settings');
        $this->settings = $selq->fetchAll();
    }
}

Languages.php

class Languages
{

    public $language;

    protected $db;
    protected $settings;

    private $languages = array();

    public function __construct(Db $db, Settings $settings)
    {
        $this->db = $db;
        $this->settings = $settings;
        // set value for $this->language based on user choice or default settings
        ...
    }

    public function load() 
    {
        $this->languages = array();
        $selq = $this->db->query('SELECT * FROM languages');
        $this->languages = $selq->fetchAll();
    }

}

Page.php

class Page
{
    protected $db;
    protected $settings;
    protected $language;

    public function __construct(Db $db, Settings $settings, Languages $languages)
    {
        $this->db = $db;
        $this->settings = $settings;
        $this->languages = $languages;
    }

    public function load() 
    {
        // load page info from db with certain settings and in proper language
        ...
    }

}

Config.php

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($db, $settings);
$languages->load();

/* Instantiate page */
$page   = new Page($db, $settings, $languages);

I don't like the idea of injection same classes over and over again. This way, I'll get to the point, where I'll need to inject 10 classes. So, my code is wrong from the beginning.

Maybe, a better way is to do the following:

Config.php

$db = new Db;

/* Load all settings */
$settings   = new Settings($db);
$settings->load();

/* Load all languages */
$languages  = new Languages($settings);
$languages->load();

/* Instantiate page */
$page   = new Page($languages);

as Settings already have access to $db, and $languages to both $db and $settings. However in this manner, I'll have to make calls like $this->languages->settings->db->...

All my code architecture seems to be completely wrong :) How it should be done?

like image 870
Alex Lomakin Avatar asked Feb 05 '15 12:02

Alex Lomakin


1 Answers

I'll try to answer my own question, as I see it after studying lots and lots of materials.

1. Best practice is to create objects like:

$db = new Db();

$settings = new Settings ($db);

$languages = new Languages ($db, $settings);

// ...

2. Use DI container.

If you can't write one, use existing one. There are some, which call themselves DI containers not being them, like Pimple (there are several post about that on this site). Some tend to be much slower and much more complicated (Zend, Symfony), then the others, but also provide greater functionality. If you are reading this, then you should probably choose more simple one, like Aura, Auryn, Dice, PHP-DI (in alphabetical order). It's also important to know, that proper DI containers (as I see it) should have the ability to recursively traverse dependencies, meaning find dependencies needed for a certain object. They should also provide an ability to share same object (like $db instance).

3. Injecting dependencies manually will cause you lots of problems when trying to create objects dynamically (in case you use front controller and routing). That's why see point 2.

See great example here:

https://github.com/rdlowrey/Auryn#user-content-recursive-dependency-instantiation

https://github.com/rdlowrey/Auryn#instance-sharing

Video to watch:

https://www.youtube.com/watch?v=RlfLCWKxHJ0 (it's not PHP, but try to get the idea)

like image 105
Alex Lomakin Avatar answered Sep 30 '22 16:09

Alex Lomakin