Как структурировать собственное приложение MVC PHP с внедрением зависимостей?

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

Прежде всего я добавлю немного кода (не настоящий код, просто для тестирования):

Модель:

class User{
    public $id;
    public $name;
    public $age;
}

Интерфейс:

interface IData{
   public function search(User $user)

}

Репозиторий классов:

class DataRepository{
    protected $db;
    public $conn;
    public $data;


    // Passing the interface DB to the constructor
    public function __construct(DB $db)
    {
        $this->db = $db;
    }


    public function search(User $user){
        $query = " SELECT * FROM test_db WHERE id_user = ". $user->id . " and name = '%" . $user->name . "%'";
        $this->conn = $this->db->connection();
        $this->data = $this->conn->query($query);
        $dataList = [];

        foreach ($this->data as $row)
        {
            $dataList [] = (object)array(
                "name" => $row["name"],
                "age" => $row["age"],
            );
        }

        return $dataList;
    }

}

Контроллер:

class UserController{
    public $Idata;


    // Passing the interface IData to the constructor
    function __construct(IData $Idata) {
        $this->Idata = $Idata;
    }


    // Function to search the user and return an array with the data
    function printData(){
        $Mdata = new User();
        $Mdata->id = 5;
        $Mdata->name = "Carl";
        $Mdata->age = 39;

        $result = $this->Idata->search($Mdata);

        return $result;
    }
}

Вид:

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <meta http-equiv = "X-UA-Compatible" content = "IE=edge">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>

<?php


// We create an instance of UserController

$UserData = new UserController();


// Print the data
echo "<pre>";
    print_r($UserData);
echo "</pre>"

?>
    
</body>
</html>

Итак, я пытался изучить шаблон репозитория с помощью MVC, чтобы отделить мой уровень БД от контроллера и инкапсулировать, все выглядело отлично, пока мне не понадобилось использовать интерфейс IData на главном контроллере (UserController), на мой взгляд, я была ошибка

$UserData = new UserController() -> returns an error: Expected 1 arguments. Found 0.

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

Я попытался:

<?php

$IData = new IData();

$UserData = new UserController($IData);

echo "<pre>";
    print_r($UserData);
echo "</pre>"

?>

он возвращает эту ошибку: Fatal error: Uncaught Error: Cannot instantiate interface IData.

Мой главный вопрос: есть ли способ использовать интерфейс только в конструкторе UserController без необходимости определять его в представлении?

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

function __construct(IData $Idata) - Это означает, что вам нужно отправить объект, реализующий интерфейс. Ваш DataRepository-класс должен реализовать этот интерфейс, а затем вы передаете экземпляр DataRepository своему UserController. Вам также следует избегать создания экземпляров в представлениях. Ваш контроллер должен получать все данные, необходимые представлению, и просто передавать данные представлениям. Представления не должны знать, откуда они берутся или как их получить.
M. Eriksson 02.05.2023 14:57

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

ADyson 02.05.2023 15:00

Не имеет отношения к текущей проблеме, но SELECT * FROM test_db WHERE id_user = ". $user->id . " and name = '%" . $user->name . "%' странно. 1. Значения должны быть связаны 2. Если есть идентификатор пользователя, зачем вам имя 3. Почему в имени есть подстановочные знаки или это опечатка для оператора (например, = должно быть like)?

user3783243 02.05.2023 15:05

@ М.Эрикссон, окей, спасибо! Я попробую. У меня есть вопрос, вы сказали, что мне следует избегать использования экземпляров в представлении, если я не использую экземпляр для вызова контроллера, как я могу получить данные? Должен ли я просто указать, когда я хочу получить окончательный результат?

haruk1515 02.05.2023 15:37

@ADyson Механизм маршрутизации, вы имеете в виду, что я должен создать экземпляр контроллера из маршрута? (получить, опубликовать ..)

haruk1515 02.05.2023 15:37

@user3783243 user3783243 не особо задумывался над запросом, поэтому просто игнорируйте его :)

haruk1515 02.05.2023 15:38

большое спасибо за комментарий, я ценю всех вас за то, что вы показываете мне мои ошибки, видите их и учитесь

haruk1515 02.05.2023 15:40
i should instantiate a controller from a route...по сути да. Обычно в архитектурах MVC механизм маршрутизации обрабатывает все запросы, поступающие в приложение, проверяет URL-адрес и, исходя из этого, использует свои правила маршрутизации для определения того, какой контроллер и метод действия необходимо вызвать. например в относительно часто используемой структуре, если URL-адрес https://yoursite.com/user/printData, тогда должно сработать, что ему нужно создать экземпляр пользовательского контроллера и запустить функцию printData. Конечно, большинство систем позволяют разработчику также переопределять значения по умолчанию с помощью настраиваемых URL-адресов маршрутов.
ADyson 02.05.2023 16:35

Вы потратили какое-то время на изучение существующих реализаций MVC, чтобы увидеть, что они делают?

ADyson 02.05.2023 16:35

Я не понимаю, почему использование ключевого слова implement помешает вам использовать внедрение зависимостей. Интерфейсы PHP требуют, чтобы класс явно объявлял, какой интерфейс он реализует.

Koala Yeung 02.05.2023 17:28

@ADyson Okei, теперь я понял. Я использовал Laravel для создания некоторых проектов, и этот тоже использует систему маршрутов, вызывая контроллер и определяя имя маршрута (не знал, что маршруты играют огромную роль в архитектуре MVC).

haruk1515 03.05.2023 13:34

@ADyson Я изучил несколько примеров шаблона репозитория, однако не нашел много примеров в контроллере - часть просмотра.

haruk1515 03.05.2023 13:34

"на стороне просмотра" - сам PHP ничего не знает о "стороне просмотра", и я даже не вижу, как данная ошибка (что вы не можете создать экземпляр интерфейса) связана с MVC в конце концов

Nico Haase 03.05.2023 14:35

@NicoHaase Model-View-Controller -> Я говорю о представлении, где у меня есть весь мой html и прочее

haruk1515 03.05.2023 15:04

@NicoHaase Конструктору UserController требуется параметр (интерфейс), поэтому я попытался создать экземпляр класса UserController в представлении и указать интерфейс в качестве параметра (в соответствии с архитектурой MVC). Я знаю, и я сказал это, я не знал, что создание экземпляра объекта в представлении было неправильным.

haruk1515 03.05.2023 15:04
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
1
15
94
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Это больше вопрос о том, как в PHP выполняется внедрение зависимостей.

Предпосылки

Прежде всего, вы должны исправить свой код, объявив DataRepository реализует интерфейс:

class DataRepository implements IData
{
    // ...
}

Нет никакого способа обойти это. PHP требует явного объявления этого.

Сначала подумай...

Затем подумайте о следующем:

  1. UserController зависит от IData.
  2. DataRepository должен реализовать IData.
  3. Ваш код должен знать, что при создании экземпляра UserController ему нужна реализация IData. Но их может быть больше одного.
  4. Как ваш код узнает, какой IData использовать для UserController?

Для (4) вам понадобится способ объявить это либо:

  • все классы, которые хотят IData, должны иметь DataRepository; или

  • некоторые из классов, которые хотят IData, например UserController, должны иметь DataRepository.

Контейнер внедрения зависимостей

Простой способ — использовать контейнер внедрения зависимостей, такой как PHP-DI. Они гибки в объявлении рецептов того, как должна выполняться зависимость интерфейса.

Предполагая, что у вас установлен «php-di/php-di» с composer, тогда вы должны запустить этот фрагмент кода в каком-то общем php/ядре.

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

$db = getDB(); // some way to create the database object
$container = new DI\Container();
$container->set(IData::class, \DI\create(DataRepository::class));
$container->set(DB::class, \DI\value($db));

// ...

Если вы передадите переменную $container в файл представления, вы можете просто сделать это:

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <meta http-equiv = "X-UA-Compatible" content = "IE=edge">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
<?php

// We create an instance of UserController
$UserData = $container->get(UserController::class);

// Print the data
echo "<pre>";
    print_r($UserData);
echo "</pre>"

?>
</body>
</html>

Обратите внимание, что вам не нужно вручную создавать зависимости UserController. При запросе UserController контейнер здесь будет искать все рецепты, чтобы выполнить все зависимости, требуемые UserController. Таким образом, представлению не нужно знать детали реализации контроллера.

Подробнее об этом

Как я упоминал ранее, вам может понадобиться использовать DataRepository вместо IData только для некоторых контроллеров. В PHP-DI и других контейнерах внедрения зависимостей есть способы указать рецепт для создания DataRepository, чтобы сделать это.

Для PHP-DI вы должны прочитать документацию для этого или для других более сложных способов использования.

Распространенные фреймворки MVC в PHP

Я предполагаю, что более распространенный дизайн MVC, вероятно, будет иметь вызов контроллера для представления. И создайте экземпляр контроллера (с использованием контейнера) в качестве деталей реализации:

Маршрутизация:

// Pseudo-code only
// But all MVC framework has its own router / routing code like this

// some container initialization
$container = dummyGetContainer();

// router will probably need container
$router = new MyRouter($container);

// register the handler of GET request to "/userData" 
$router->get('/userData', 'UserController::viewData');

// actually execute the routing
// internally will do:
// 1. find the handler of the request method and path
// 2. instantiate the controller with dependencies.
// 3. execute the handler against the request.
$router->route(dummyGetRequestFromEnvironment());

Контроллер:

class UserController{
    public $Idata;

    // Passing the interface IData to the constructor
    function __construct(IData $Idata) {
        $this->Idata = $Idata;
    }

    function viewData($request) {
        $userData = $this->getData();
        include __DIR__ . '/../views/userDataView.php';
    }

    // Function to search the user and return an array with the data
    function getData(){
        $Mdata = new User();
        $Mdata->id = 5;
        $Mdata->name = "Carl";
        $Mdata->age = 39;

        $result = $this->Idata->search($Mdata);

        return $result;
    }
}

Вид:

<!DOCTYPE html>
<html lang = "en">
<head>
    <meta charset = "UTF-8">
    <meta http-equiv = "X-UA-Compatible" content = "IE=edge">
    <meta name = "viewport" content = "width=device-width, initial-scale=1.0">
    <title>Test Page</title>
</head>
<body>
<pre>
<?php print_r($userData); ?>
</pre>

Этот дизайн даже лучше, потому что представление не должно знать ничего о контроллере. Он должен просто представлять данные, которые ему даются.

Я никогда не слышал о PHP-DI, я проверю его и начну использовать маршруты. Спасибо за хорошо объясненный пример

haruk1515 03.05.2023 13:35

Код маршрутизатора, который я написал здесь, является только псевдокодом. Вам нужно будет написать его или найти его реализацию. Фреймворки MVC, такие как Laravel и Symfony, имеют свой собственный способ указания маршрутов. И они оба имеют свою собственную реализацию контейнера для инъекций зависимостей.

Koala Yeung 03.05.2023 14:26

Другие вопросы по теме