Инжектиране на променлива в конструктора

teroristd

Registered
Здравейте, за да разберете какво точно питам, ще дам примерен код.

Да кажем че, имаме един клас, ще го наречем примерно Model, който от своя стана взима някакви данни. За да е прост примера слагаме едно echo с някакви имена.

Имаме още един клас който ще наречем Controller. В конструктора му инжектираме Model-a, и казваме че свойството _users = на променливата $model, която представлява обекта. После просто си викаме функцията getUsers().

Код:
class Model {
    public function getUsers() {
        echo "Ivan, Dragan, Petkan.";
    }
}
Код:
class Controller {

    public $_users;

    public function __construct(Model $model) {
        $this->_users = $model;
    }

    public function viewUsers() {
        $this->_users->getUsers();
    }
}


Всичко изглежда прекрасно, но всъщност променливата $model не говори нищо на класът Controller, и естествено трещи грешка, че тази променлива е неиндетифицирана.

Естествено аз мога да напиша в конструктора на Controller един допълнителен ред $model = new Model(); и ще работи, но тогава се обезмисля цялото инжектиране.

Въпросът е как да се справя с този проблем?
 
Трябва ти di container, в една от миналите ти теми ти предлагахме доста готови библиотеки
 
Мислех си да напиша в началото, че не желая готови решения. Защо? Защото са по-сложни от колкото е необходимо. Защото са насочени предимно към Linux потребители. Защото използват Composer. Защото ако всички ползваха готови решения, щеше да има само Codeigniter първата версия.

Ако знаеш как тези контейнери се справят с този проблем, ще се радвам да споделиш.
 
teroristd каза:
Мислех си да напиша в началото, че не желая готови решения. Защо? Защото са по-сложни от колкото е необходимо. Защото са насочени предимно към Linux потребители. Защото използват Composer. Защото ако всички ползваха готови решения, щеше да има само Codeigniter първата версия.

Ако знаеш как тези контейнери се справят с този проблем, ще се радвам да споделиш.

Как разбра, че са насочени предимно към linux потребители? PHP може да работи, независимо от OS. Всеки използва composer в момента, така най-добре се организират пакетите. Погледни примерно laravel - изграден е от съвкупност от composer packages, не са си играли да откриват топлата вода и да пишат всичко наново. Основно имаш един array, в който пазиш инстанции на дадените класове, а това, което ти искаш да направиш, се прави със reflection
 
Виж сега няма какво да спорим тук. Ако разбираш дай код да видя. Иначе ако ти се спами пусни една тема в дъра бъра и ще си спорим. Капиш :) .
 
Искаш да ти имплементирам DI container, а ти не искаш да използваш чужд код? Насочих те как се прави, погледни какво е reflection и spl_autoload_register

Ето ти най-простото нещо, което може да се направи <140 символа:
http://twittee.org/

Ето ти няколко "по-сложни" имплементации:
https://github.com/auraphp/Aura.Di
http://php-di.org/
https://r.je/dice.html
http://pimple.sensiolabs.org/

Ето и уроци:
http://krasimirtsonev.com/blog/article/Dependency-Injection-in-PHP-example-how-to-DI-create-your-own-dependency-injection-container
http://fabien.potencier.org/what-is-dependency-injection.html
 
Нещо не си ме разбрал правилно. Не искам да ми инплементираш контейнер. Нека приемем че имам написан някакъв бейсик контейнер. Със или без рефлекшън нали трябва да го извикам някъде. В примера от първия пост съм казал $model = new Model(); за да е по просто, но реално мога да си извикам контейнера така $model = $this -> _container -> make('models\Model');.

В този случай пак работи, но тука ключовото е че трябва да подам на променливата $model нещо в същия клас за да работи. Иначе променливата остава неицианализирана.

Разбираш ли къде е проблема? Не е в това дали контейнер създава нова инстанция на класа или го подаваш директно.

Прочети внимателно първия пост.
 
Ами точно това прави reflection-а, можеш да вземеш класа и с него директно да му подадеш в конструктора инстанция на модела, точно както си написал кода, иначе сега това, което правиш се нарича type hinting

Ето и урок точно за това, което искаш: https://www.ltconsulting.co.uk/automatic-dependency-injection-with-phps-reflection-api/

Като допълнение към урока, предполагам щом питаш за mvc, ще имаш и router и тогава, примерно имаш Router::get('/test','Controller@method')

тогава resolve-ваш подадения контролер със $controller = (new Resolver)->resolve($controller); и вече си викаш $controller->$method

Като упражнение можеш да преправиш resolve-ера от урока да ти инжектира необходимите неща и в методите, не само в конструктора
 
Не знам как да обясня за да ме разбереш. Ето контролера в чист вид. Така не работи, защото променливата $model не е инициализирана.

Код:
class Controller {

    public $_users;

    public function __construct(Model $model) {
        $this->_users = $model;
    }

    public function viewUsers() {
        $this->_users->getUsers();
    }
}

Ето го като викам контейнера. Така работи, но кода се обезмисля, защото това е същото все едно съм казал направо $model = new model() само където кода минава през рефлекшъни и масиви. Фактически тука нямам dependency injection, защото зависимостта си остава в класа. За да е правилно кода трябва да е като горе. Това е проблема.


Код:
class Controller {

    public $_users;

    public function __construct(Model $model) {
        $model = $this -> _container -> make('models\Model');.
        $this->_users = $model;
    }

    public function viewUsers() {
        $this->_users->getUsers();
    }
}
 
Виждам че си едитнал поста си, така пиша без да съм видял цялата информация.

Може да стане дълго но ще дам кода който е необходим за да може да помогнете. Нека почнем с контейнера, рутера, който сигурно не прилича на тези в ларавел и фронт контролера. Фронт контролера взима урл-а от рутера и се грижи за инициализирането на контолерите.

Контейнера прилича на този който си пуснал в линка.

Ако имате питанки питайте.

Код:
<?php

namespace application;

class Container {

    private static $_instance = NULL;
    private $_registry = array();
    private $_service = array();
    private $_reflection;
    private $_constructor;
    private $_params = array();

    private function __construct() {
        
    }





    /** @return \application\Container */
    public static function getInstance() {

        if (self::$_instance === NULL) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }





    public function set($key, callable $obj) {
        $this -> _registry[$key] = $obj;
    }





    public function make($key) {

        if (!isset($this -> _service[$key])) {

            if (isset($this -> _registry[$key])) {

                $this -> _service[$key] = $this -> _registry[$key]();
            }
            else {

                $this -> _reflection = new \ReflectionClass($key);

                if ($this -> _reflection -> isInstantiable()) {

                    $this -> _constructor = $this -> _reflection -> getConstructor();

                    if ($this -> _constructor) {

                        foreach ($this -> _constructor -> getParameters() as $arr) {

                            if ($arr -> getClass()) {

                                $this -> _params[] = $this -> make($arr -> getClass() -> getName());
                            }
                            else {
                                $this -> _params[] = $arr -> getDefaultValue();
                            }
                        }

                        $this -> _service[$key] = $this -> _reflection -> newInstanceArgs($this -> _params);
                    }
                    else {
                        $this -> _service[$key] = $this -> _reflection -> newInstance();
                    }
                }
                else {

                    throw new Exception($key . ' Is not instanciable class!');
                }
            }
        }

        return $this -> _service[$key];
    }





}



Рутера

Код:
<?php

namespace application\routers;

use application\interfaces\IRouter;

class DefaultRouter implements IRouter {

    private static $_instance = NULL;

    private function __construct() {
        
    }





    /** @return \application\routers\DefaultRouter */
    public static function getInstance() {

        if (self::$_instance === NULL) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }





    public function getFullUrl() {

        return 'http://' . filter_input(INPUT_SERVER, 'HTTP_HOST') . filter_input(INPUT_SERVER, 'REQUEST_URI');
    }





    public function getPost() {

        return $_POST;
    }





    public function getUrl() {

        return substr(filter_input(INPUT_SERVER, 'PHP_SELF'), strlen(filter_input(INPUT_SERVER, 'SCRIPT_NAME')) + 1);
    }





}



И фронт контролера

Код:
<?php

namespace application;

use application\Config,
    application\routers\DefaultRouter,
    application\Normalize;

class FrontController {

    private static $_instance = NULL;
    private $_config;
    private $_defaultRouter;
    private $_routerArr;
    private $_URL;
    private $_namespace;
    private $_params;
    private $_controller;
    private $_method;
    private $_defaultParam;
    private $_file;
    private $_load;
    private $_input;
    private $_service;

    private function __construct() {

        $this -> _config = Config::getInstance();
        $this -> _defaultRouter = DefaultRouter::getInstance();
        $this -> _input = Normalize::getInstance();


        $this -> _routerArr = $this -> _config -> getConfigFile('routerArr');
        $this -> _defaultParam = $this -> _config -> getConfigFile('appArr');
        $this -> _URL = $this -> _defaultRouter -> getUrl();
        $this -> _service = new \application\ServiceProvider();
        $this -> _service -> spLog();
    }





    /** @return \application\FrontController */
    public static function getInstance() {

        if (self::$_instance === NULL) {
            self::$_instance = new self();
        }

        return self::$_instance;
    }





    private function getNamespace() {

        if (is_array($this -> _routerArr) && $this -> _routerArr > 0) {

            foreach ($this -> _routerArr as $key => $val) {

                if (stripos($this -> _URL, $key) === 0 && ($this -> _URL == $key || stripos($this -> _URL, $key . '/') === 0) && $val) {

                    $this -> _namespace = $val;

                    break;
                }
            }
        }
        else {

            throw new \Exception('Router Array Do Not Exist In: ' . __METHOD__, 500);
        }
    }





    private function isExistNamespace() {

        if ($this -> _namespace === NULL && $this -> _routerArr['*']) {

            $this -> _namespace = $this -> _routerArr['*'];
        }
        elseif ($this -> _namespace === NULL && !$this -> _routerArr['*']) {

            throw new \Exception('Default Root Missing In: ' . __METHOD__, 500);
        }
    }





    private function isControllerExist() {

        if (!class_exists($this -> _namespace . DIRECTORY_SEPARATOR . $this -> _controller)) {

            throw new \Exception('There No Such Controller In: ' . __METHOD__, 404);
        }
    }





    private function isMethodExist() {

        if (!method_exists($this -> _namespace . DIRECTORY_SEPARATOR . $this -> _controller, $this -> _method)) {

            throw new \Exception('There No Such Method In: ' . __METHOD__, 404);
        }
    }





    private function params() {

        $this -> _params = explode('/', $this -> _URL);

        if (!empty($this -> _params[0])) {

            $this -> _controller = strtolower($this -> _params[0]);
            $this -> isControllerExist();

            if (!empty($this -> _params[1])) {

                $this -> _method = strtolower($this -> _params[1]);
                $this -> isMethodExist();
                unset($this -> _params[0], $this -> _params[1]);

                $this -> _input -> setGet(array_values($this -> _params));
            }
            else {

                $this -> _method = strtolower($this -> _defaultParam[1]);
            }
        }
        else {

            $this -> _controller = strtolower($this -> _defaultParam[0]);
            $this -> _method = strtolower($this -> _defaultParam[1]);
        }

        $this -> _input -> setPost($this -> _defaultRouter -> getPost());

        $this -> _file = $this -> _namespace . DIRECTORY_SEPARATOR . ucfirst($this -> _controller);
        $this -> _load = new $this -> _file();
        $this -> _load -> { $this -> _method }();
    }





    public function output() {

        $this -> getNamespace();
        $this -> isExistNamespace();
        $this -> params();
    }





}
 
Какво трябва да помагаме? Накрая на рутера правиш нова инстанция на контролера и викаш make на контейнера
Код:
$instance = new $container->make($this->_namespace.'\'.$this->_controller.'\'.$this->_method);
$instance->$this->_method($params);

Обаче този контейнер, който си направил не resolve-ва nested dependencies, т.е ако в примера ти в конструктора на модела искаш да инжектираш нещо друго, няма да сработи
 
lam3r4370 каза:
Какво трябва да помагаме? Накрая на рутера правиш нова инстанция на контролера и викаш make на контейнера
Код:
$instance = new $container->make($this->_namespace.'\'.$this->_controller.'\'.$this->_method);
$instance->$this->_method($params);

Обаче този контейнер, който си направил не resolve-ва nested dependencies, т.е ако в примера ти в конструктора на модела искаш да инжектираш нещо друго, няма да сработи

Не можах да разбера къде отива това. Би ли го сложил в моя код за да пробвам дали ще сработи. А за nested dependencies ще го мисля после.
 
Това отива в рутера, където викаш метода на контролера, просто трябва да го замениш с това
 
Направих го така и донякъде сработи, но се появиха някои бъгове. Първо ме задължава да имам конструктор в модела иначе трещи грешка. И втория бъг е че ме задължава да имам дефаултовата функция която съм задал и друга не приема.

Код:
$this -> _file = $this -> _namespace . DIRECTORY_SEPARATOR . ucfirst($this -> _controller);
        $this -> _load = $this -> _container -> make($this -> _file);
        $this -> _load -> { $this -> _method }();

Но това че сработи все пак ме радва много :) .
Това имах предвид още в началото, ама като не знам как да задам въпроса така става.

Още няма да ти давам точка за да можем да коментираме да оправим бъговете. Все пак благодаря за търпението.
 
Как те задължава да имаш "дефалтова функция"? Иначе можеш да провериш дали има конструктор преди да продължиш нататък с инжектирането -
Код:
if(is_null($this->_reflection->getConstructor())) return new $this->_namespace.'\'.$this->_controller.'\'.$this->_method
И не ти трябва DIRECTORY_SEPARATOR, не работиш с директории, а namespaces
 
За конструктора не е ли по добре да погледна в самия контейнер. А иначе имам дефаултов метод, при положение че е подаден само контролера в урл-а. Но той по принцип няма общо с моделите. Трябва да прегледам кода по-обстойно защото може и да приказвам глупости в момента :) . Но като прекръстих метода в модела както е дефаултовия и сработи.
 
Няма как да погледнеш в контейнера, а това да имаш default метод е тъпо, по-добре изпиши някаква грешка.
 
Така подмених моя контейнер с този от линка, който ми пусна, и поизчистих разни проби които съм правил със стария, и сега работи добре. Не ми дава никакви грешки, обаче мога да инжектирам само в конструктора. Какво трябва да се добави за да инжектирам и в обикновени методи?
 

Back
Горе