3

I've been doing MVC for several months now, and I store everything in my $registry object. When I create a new class, I only ever pass the registry usually, but I'm having to constantly pass the $this->registry when creating a new class.

e.g.

class something
{
   public function __construct($registry)
   {
      $this->registry = registry;
      $this->db = $registry->db;
      $this->user = $registry->user; // ......
   }

   public function something()
   {
      $class = new something_class($this->registry);
      $class->do();
   }
}

class something_class
{
   public function __construct($registry)
   {
      $this->registry = $registry;
   }

   public function do()
   {
     echo 'Doing something ....';
   }
}

My question is, how can I handle the passing of the registry to the new class behind the scenes (in this case when instantiating something_class) inside the registry class somehow? I'm absolutely convinced there is an easy way to do this, but I can't find anything related anywhere to what I'm looking for.

Here is my registry class:

<?php
class registry
{
    protected $vars = array();
    public function &__set($index, $value)
    {
        $this->vars[$index] = $value;
        return $value;
    }

    public function &__get($index)
    {
        return $this->vars[$index];
    }
}
Dennis
  • 7,907
  • 11
  • 65
  • 115
Chris98
  • 567
  • 1
  • 4
  • 16
  • Are you using any type of framework or is this your own "MVC framework"? Just checking so that me or someone else don't write a long answer which is totally irrelevant ;) – Jite Aug 31 '16 at 10:15
  • Thanks for the quick reply, it's my own framework which is very loosely based on this: http://www.phpro.org/tutorials/Model-View-Controller-MVC.html I've already added numerous improvements, such as using spl_autoload_register ** Edit ** Site appears to be down right now, so I found it in Way Back When: https://web.archive.org/web/20160331021349/http://www.phpro.org/tutorials/Model-View-Controller-MVC.html – Chris98 Aug 31 '16 at 10:20
  • I would kinda recommend taking a look at some other projects implementation of a `Dependency injection container`, for example: the Symfony project (which a whole lot of frameworks build upon) have a nice Dependency injection component which could either be used right away or at least a good resource when it comes to how its implemented: https://symfony.com/doc/current/components/dependency_injection.html – Jite Aug 31 '16 at 10:25
  • What you have is a container. You should not pass a container into an object. Read this: http://www.php-fig.org/psr/psr-11/meta/#4-recommended-usage-container-psr-and-the-service-locator – Dennis Jul 18 '17 at 14:15

3 Answers3

2

This is all wrong. "Registry" is an anti-patter and what you are doing the is not dependency injection. You have found a way to fake global variables .. that's it.

As a start, please watch this lecture.

As for, how to correctly do what you want, there are two ways:

  • use a factory, that creates a class, using dependencies that you provided
  • use a dependency injection container, Auryn

To learn how you use a DI container, you will just have to consult the documentation. But I will explain the basics of factory, which is more of a DIY approach

A factory is an object, that is responsible for initializing other class. For example, you have a large set of classes, which require PDO as a dependency.

class Factory 
{
    private $pdo;

    public function __construct(PDO $pdo) {
        $this->pdo = $pdo;
    }


    public function create($name) {
        return new $name($this->pdo);
    }
}

If you use an instance of this class, it would let you create objects, which have the PDO already passed in as a dependency in a constructor:

$factory = new Factory(PDO($dsn, $user, $pass));
$user = $factory->create('User');
$document = $factory->create('Doc');

And as an added benefit, this setup would let bot the User class instance and the Doc class instance to share the same PDO object.

tereško
  • 58,060
  • 25
  • 98
  • 150
  • Very nice explanation of DI teresko. One simple comment, because your topics (specifically about MVC) are reference for a lot new developers, I think that it would be better if you had a reference to `composition root` at your answer so the concept of DI would be more clear. – dios231 Sep 12 '16 at 14:24
0

It's best practice to have your classes or components not be dependent on your container. Let your container do the injection of dependencies.

Here's an example that uses Container from The League of Extraordinary Packages and shows class B being dependent on A:

<?php

$container = new League\Container\Container();

$container->add('b', function () {
    $a = new A('foo');
    $b = new B($a);

    return $b;
});

var_dump($container->get('b')->getValueFromA()); // Outputs "foo"

class A
{
    private $value;

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

    public function getValue()
    {
        return $this->value;
    }
}

class B
{
    public function __construct(A $a)
    {
        $this->a = $a;
    }

    public function getValueFromA()
    {
        return $this->a->getValue();
    }
}

Each class you write should only be coupled to its direct dependencies. If possible, the use of interfaces is highly recommended for further decoupling.

Please see this Stackoverflow question for more information around the differences between a registry and a DI container. Using a registry is considered an anti-pattern and should be avoided.

Community
  • 1
  • 1
0

Your $registry class is what's called a Service Locator pattern. It is useful in some contexts, but it has a high potential for being abused, especially when you inject the service locator into your class.

Dependency Injection is supposed to expose (show) all the objects that your class uses (or depends on). Injecting $registry in your class instead hides the dependencies, and this is why it is an anti-pattern. Also, it creates a burden for you as you have to pass it everywhere, you might as well make it a global to make it easier (this will also answer your question). But better tools are available.

Consider your class like so:

  • Note the use of dependency injection of $db and $user into constructor of something class. Now it is more clear what your class needs to work.
  • Also note the use of dependency injection container in this case Auryn to create a class for you.
//in your bootstrap or part of your framework
$injector = new Auryn\Injector();
$class = $injector->make('something_class');

//Your own code:
class something
{
   public function __construct(Database $db, User $user)
   {
      $this->db = $db;
      $this->user = $user;
   }

   public function something(something_class $class)
   {
      $class->do();
   }
}

You can also use Auryn to do what you want to do to call the Registry class. But you will soon find that Auryn is a better more robust version of your Registry class and one that forces you to use better dependency-injection techniques for Auryn to work as intended.

Basic point is that your own application (all of your application's classes) best not be aware of any container. Because what containers do is they hide dependencies from your classes. It remains however as acceptable to use containers as part of a framework. Let your framework handle containers, but you focus on your own classes, without calling containers unless those containers are used as part of your framework to wire up your application.

Dennis
  • 7,907
  • 11
  • 65
  • 115