Подтвердить что ты не робот

Как создать контейнер для инъекций зависимостей PHP

Недавно я узнал о преимуществах использования Dependency Injection (DI) в моем приложении PHP. Тем не менее, я все еще не уверен, как создать контейнер для зависимостей, или я должен использовать DI вообще для онлайн-форума, который я создаю.

Следующий код - это моя версия контейнера DI, который я сделал на примере, который я узнал из здесь.

class ioc {

   var $db;
   var $session;
   var $user_id;

   static function newUser(static::$db, static::$user_id) {
      $user = new User($db, $user_id);
      return $user;
   }

   static function newLogin(static::$db, static::$session) {
      $login = new Login($db, $session);
      return $login;
   }

}

$user = ioc::newUser();
$login = ioc::newLogin();

У меня есть несколько вопросов:

1) Где я должен создавать экземпляры зависимостей, например $database, $session и т.д.? Будет ли это за пределами класса контейнера или внутри конструктора контейнера.

2) Что делать, если мне нужно создать несколько экземпляров класса User внутри других классов? Я не могу ввести ранее созданный объект $user, потому что этот экземпляр уже используется. Однако создание нескольких экземпляров пользователя внутри другого класса нарушило бы правила DI. Например:

class Users {

    function __construct($db, $user_id) {
        $this->db = $db;
        $this->user_id = $user_id;
    }

    function create_friends_list() {
        $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = $this->user_id");
        $st->execute();
        while($row = $st->fetch()) {
            $friend = ioc::newUser($row['user_id']);
            $friend->get_user_name();
            $friend->get_profile_picture();
        }
    }
}   

3) Мне интересно, должен ли я даже принять DI, зная, что мне нужно переписать весь мой предыдущий код. Я ранее полагался на глобальные переменные, которые я создавал в своем файле initialize.php, который включен во все мои файлы.

Мне кажется, что DI создает много накладных расходов, и есть ситуации, когда они непригодны (как в моем примере # 2). Следующий сайт принадлежит разработчику, который ссылается на многие веские причины не использовать DI. Есть ли у его аргументов какие-то заслуги? Или я просто использую DI неправильно? проверьте эту ссылку.

4b9b3361

Ответ 1

Где я могу создать экземпляр моих зависимых зависимостей, таких как $database, $session и т.д.? Будет ли это за пределами класса контейнера или внутри конструктора контейнера.

В идеале ваше соединение с базой данных и сеанс будут загружены. Правильный DI требует экземпляра базового объекта, для которого все зарегистрировано. Поэтому, принимая ваш класс IOC в качестве примера, вам нужно сделать его экземпляр ($ioc = new IOC();), тогда вам понадобится какой-то класс поставщика услуг:

$ioc->register('database', new DatabaseServiceProvider($host, $user, $pass))

Теперь каждый раз, когда вы хотите подключиться к базе данных, вам просто нужно передать $ioc->get('database'); очень грубый пример, но я думаю, вы можете понять, что идея состоит в том, чтобы хранить все в реестре, и ничто не статически привязано, что означает, что вы можете создайте еще один экземпляр $ioc с совершенно разными настройками, что упростит создание подключений, чтобы сказать другую базу данных для целей тестирования.

Что делать, если мне нужно создать несколько экземпляров класса User внутри других классов? Я не могу ввести ранее созданный объект $user, потому что этот экземпляр уже используется. Однако создание нескольких экземпляров пользователя внутри другого класса нарушило бы правила DI.

Это обычная проблема, и существует несколько различных решений. Во-первых, ваш DI должен показать разницу между зарегистрированным пользователем и просто пользователем. Вероятно, вы захотите зарегистрировать зарегистрированного пользователя, но не только любого пользователя. сделайте свой пользовательский класс просто нормальным и используйте

$ioc->register('login-user', User::fetch($ioc->get('database'), $user_id));

поэтому теперь $ioc->get('login-user') возвращает зарегистрированного пользователя. Затем вы можете использовать User->fetchAll($ioc->get('database'));, чтобы получить всех своих пользователей.

Мне интересно, должен ли я даже принять DI, зная, что мне нужно переписать весь мой предыдущий код. Я ранее полагался на глобальные переменные, которые я создаю в своем файле initialize.php, который включен во все мои файлы.

Если вам нужно переписать весь свой код для использования DI, вы, вероятно, не должны этого делать. Возможно, посмотрите, как создать новый проект и работать в каком-то из вашего старого кода, если у вас есть время. Если ваша кодовая база большая, я бы рекомендовал разбить ее на более мелкие проекты и использовать RESTFUL apis для получения и сохранения данных. Хорошие примеры написания API-интерфейсов были бы для поиска вашего форума в его собственном приложении/поиске/имени? Partial-name = bob вернул бы всех пользователей со словом bob в нем. вы можете создать его и сделать его лучше с течением времени и использовать его на своем основном форуме

Надеюсь, вы понимаете мои ответы, но если вам нужна дополнительная информация, дайте мне знать.

Ответ 2

Вместо globals в вашем init.php определите ваши объекты как:

ioc::register('user', function() {
 return new User();
});

И не используйте метод create_friends_list:

ioc::get('user')->newUser($user_id);

Это действительно простая реализация. Проверять, выписываться: http://net.tutsplus.com/tutorials/php/dependency-injection-huh/?search_index=2

или

http://net.tutsplus.com/tutorials/php/dependency-injection-in-php/?search_index=1

для получения дополнительной информации.

Ответ 3

Я собирался написать этот комментарий, но он слишком долгое время. Я не эксперт, поэтому я просто даю свою точку зрения из того, что я узнал через несколько лет, практикуя и здесь, в SO. Не стесняйтесь использовать или подвергать сомнению любую часть моего ответа (или ничего).

1.- Вне. Что делает контейнер? Ответ должен быть одним. Он не должен нести ответственность за инициализацию классов, подключение к базе данных, обработку сеанса и другие вещи. Каждый класс делает только одну вещь.

class ioc
  {
  public $db;

  // Only pass here the things that the class REALLY needs
  static public function set($var, $val)
    {
    return $this->$var = $val;
    }

  static function newDB($user, $pass)
    {
    return new PDO('mysql:host=localhost;dbname=test', $user, $pass);
    }

  static function newUser($user_id)
    {
    return new User($db, $user_id);
    }

  static function newLogin($session)
    {
    return new Login($this->db, $session);
    }
  }

if (ioc::set('db',ioc::newDB($user, $pass)))
  {
  $user = ioc::newUser($user_id);
  $login = ioc::newLogin($session);
  }

2.- Вы не должны делать $friend = ioc::newUser($row['user_id']); внутри своего класса. Там вы предполагаете, что существует класс под названием ioc с методом под названием newUser(), в то время как каждый класс должен действовать самостоятельно, а не на [возможно] других существующих классах. Это называется плотная связь. В принципе, почему вы также не должны использовать глобальные переменные. Все, что используется в классе, должно быть передано ему, а не предполагается в глобальной области. Даже если вы знаете это там и ваш код работает, он делает класс не повторно используемым для других проектов и гораздо сложнее проверить. Я не буду распространяться (PUN?), Но поместил отличное видео, которое я открыл здесь в SO, чтобы вы могли больше копать: Чистые коды кодов - не смотрите Для вещей.

Я не уверен в том, как ведет себя класс User, но так я сделал бы это (не обязательно правильно):

// Allow me to change the name to Friends to avoid confusions
class Friends
  {
  function __construct($db)
    {
    $this->db = $db;
    }

  function create_friends_list($user_id)
    {
    if (!empty(id))
      {
      // Protect it from injection if your $user_id MIGHT come from a $_POST or whatever
      $st = $this->$db->prepare("SELECT user_id FROM friends WHERE user_id = ?");
      $st->execute(array($user_id));
      $AllData = $st->fetchAll()
      return $AllData;
      }
    else return null;
    }

  // Pass the $friend object
  function get_friend_data($friend)
    {
    $FriendData = array ('Name' => $friend->get_user_name(), 'Picture' => $friend->get_profile_picture());
    return $FriendData;
    }
  }

$User = ioc::newUser($user_id);
$Friends = new Friends($db);

$AllFriendsIDs = array();
if ($AllFriendsIDs = $Friends->create_friends_list($User->get('user_id')))
  foreach ($AllFriendsIDs as $Friend)
    {
    // OPTION 1. Return the name, id and whatever in an array for the user object passed.
    $FriendData = $Friends->get_friend_data(ioc::newUser($Friend['user_id']));
    // Do anything you want with $FriendData

    // OPTION 2. Ditch the get_friend_data and work with it here directly. You're already in a loop.
    // Create the object (User) Friend.
    $Friend = ioc::newUser($Friend['user_id']);
    $Friend->get_user_name();
    $Friend->get_profile_picture();
    }

Я не тестировал его, поэтому у него есть некоторые небольшие ошибки.

3.- Если вы учитесь во время кодирования, вам придется переписывать МНОГИЕ вещи. Попробуйте сделать что-то с самого начала, поэтому вам не нужно переписывать все, а только классы/методы и принимать некоторые условные обозначения для всего вашего кода. Например, никогда не эхо изнутри функции/метода, всегда возвращайте и эхо извне. Я бы сказал, что да, это того стоит. Это плохо, что вам нужно потерять 1 или 2 часа, просто переписывая что-то, но если это нужно сделать, сделайте это.

PS, извините, я повсюду менял свой стиль.


ИЗМЕНИТЬ Чтение других ответов, в то время как вы не должны подключаться к базе данных с вашим объектом ioc, это должно быть совершенным, создавая с ним новый объект. Отредактировано выше, чтобы понять, что я имею в виду.

Ответ 4

Заданный вами вопрос имеет один очень важный улов, о котором я просил в комментарии. Каждый раз, когда вы изучаете возможность приостановки прогресса, чтобы вернуться назад и выполнить менее тривиальный повторный факторинг существующих классов, вам действительно нужно подумать о выигрыше.

Вы ответили:

Это кажется немного сложным (и совсем другим) и большим количеством работы, чем это стоит реализовать на этом этапе. Я просто пытаюсь взломать что-то реальное быстро и посмотреть, если он набирает силу.

Помните, что вам нужно выполнить следующие действия, если вы хотите правильно использовать DI с контейнером IoC:

  • Реализовать контейнер IoC и реестр при загрузке
  • Измените все существующие классы, у которых есть зависимости, чтобы разрешить инъекцию установщика, но не в зависимости от самого контейнера IoC.
  • Повторная запись/реструктуризация ваших тестов по мере необходимости

Заметка о второй пуле, которая отражает мое собственное личное мнение и предпочтение, развиваясь с умением думать в уме, может быть трудной, и вы хотите получить полную награду, которую она может дать. Одно из самых больших вознаграждений - полностью развязанные объекты, вы не получаете полной части этого, если все еще зависит от контейнера IoC.

Это большая работа, особенно если вы не рассматривали образец до сих пор. Конечно, у вас будут четкие преимущества, такие как много повторно используемых и легко проверяемых объектов, когда вы закончите. Но, как и при любом повторном факторинге, ничего нового не будет сделано в контексте добавления или завершения новых функций и функций. Трудно ли испытывать трудности или затрудненное сцепление? Это то, что вам нужно было бы весить.

То, что я предлагаю вам сделать, это сохранить шаблон в виду, когда вы пишете новый код. Предоставлять методы установки для инъекций зависимостей, которые могут быть использованы вручную или через контейнер IoC. Тем не менее, ваши классы продолжают создавать их как раз вовремя, если ни один из них не был введен, и избегать композиции в конструкторе.

В этот момент у вас будет набор классов, которые значительно улучшают обращение к управлению, если это шаблон, который вы хотите продолжить в будущем. Если это хорошо работает для вас, и это то, что вы действительно хотите включить в свои проектные решения, вы можете легко перераспределить, чтобы позже удалить логику JIT.

В заключение, я боюсь, что единственное, что у вас получится, если вы попытаетесь полностью реализовать его, и правильно сейчас - беспорядок. Вы можете изменить способ написания классов в будущем, но я бы не стал возвращаться и попытался реализовать его по всем направлениям.