【问题标题】:PHP Class Dependency Injection FailurePHP 类依赖注入失败
【发布时间】:2017-02-02 17:15:12
【问题描述】:

好的,在访问一个类中的几个其他类时,我经常遇到一些未解决的错误。 根据首先需要哪个类,它不会使其在其他文件的 __construct 函数中可访问。

Undefined property: Container::$Core in /usr/share/nginx/html/app/classes/class.users.php on line 9


class.container.php

public function __construct() {
    if(isset(self::$class)) {
        foreach(self::$class as $key => $value) {
            if(!isset($this->{$key})) {
                $this->{$key} = $value;
            }
        }
    }   
} 

public function setClassHandlers($class = []) {
            if(!isset(self::$class)) {
                foreach($class as $name => $file) {
                    $file = DIR . "app/classes/" . $file;

                    if(!isset(self::$library[$name])) {
                        // Get interface file
                        $interface = DIR . "app/classes/interfaces/interface.".strtolower($name).".php";
                        // Check if interface file exists
                        if(!file_exists($interface)) {
                            // Throw exception
                            $this->throwException("Unable to load interface file: {$interface}");
                        }

                        // Require interface
                        require_once $interface;
                        //Check if interface is set
                        if(!interface_exists("i".ucfirst($name))) {
                            // Throw exception
                            $this->throwException("Unable to find interface: {$interface}");
                        }

                        // Check if class file exists
                        if(!file_exists($file)) {
                            // Throw exception
                            $this->throwException("Unable to load class file: {$file}");
                        }
                        // Require class
                        require_once $file;
                        // Check if class file exists
                        if(class_exists($name)) {
                            // Set Library
                            self::$library[$name] = $file;
                            // Set interface
                            self::$interface[$name] = $interface;
                            // Set class        // class.container.php
                            self::$class[$name] = new $name(new self);
                            $this->{$name} = self::$class[$name];
                        } else {
                            // Thror error
                            $this->throwException("Unable to load class: {$name}", self::$library[$name]);
                        }

                    }
                }
            } 
        }

index.php上的函数中插入参数:

require_once DIR . 'app/management/dependency.php';

$container = new Container();

$container->setClassHandlers([
    // Name         // Path
    'Database'  => 'class.database.php',
    'Config'    => 'class.config.php',
    'Users'     => 'class.users.php',
    'Core'      => 'class.core.php',
    //'Admin'       => 'class.admin.php',
    //'Forum'       => 'class.forum.php',
    //'Template'    => 'class.template.php'
]);

class.users.php

public function __construct(Container $class) {
        $this->db   = $class->Database;
        $this->core = $class->Core;
        $this->ip   = $this->core->userIP();
        $this->container = $class;
    }

例如 UsersCore 在彼此的同一个文件中使用,但如上所述,如果首先需要 CoreUsers 不是该类中的可用依赖项。
我不太确定如何解决此问题,因此感谢您的每一个帮助。

【问题讨论】:

  • 你知道这是错的吗?还是你的问题是错字?$file = DIR . "app/classes/" . $file;应该是__DIR__
  • @yivi 在“DIR”被定义为dirname(__FILE__) . DIRECTORY_SEPARATOR 时并不是一个错字,但它和__DIR__ 有什么区别?
  • __DIR__ 是魔术常数,类似于__FILE__。始终指向当前文件所在的目录。为清楚起见,不建议使用DIR。使用WEBROOT 或更明确的东西,IMO。原生 __DIR__ 不包含斜杠。
  • 你是善意的重新发明轮子,但我很害怕。有什么理由不想使用现有的容器,比如Pimple,既简单又轻量级?
  • @yivi 我宁愿自己学习也不愿使用别人的工作。

标签: php oop dependency-injection


【解决方案1】:

当运行 setClassHandlers 方法时,您的“容器”正在尝试实例化包含的对象,到那时可能不会设置其所有属性。

另外,你的容器不够“懒惰”。正在尝试立即实例化所有内容,即使可能不需要。

试试下面的

首先,删除这些行:

    // Set class        // class.container.php
    self::$class[$name] = new $name(new self);
    $this->{$name} = self::$class[$name];

然后,将私有数组服务添加到您的 Container 类中:

private $services = [];

最后,为你的容器添加一个魔法吸气剂:

function __get($service)
    {
        if ( ! isset($this->services[$service]) ) {
            $this->services[$service] = new $service();
        }

        return $this->services[$service];
    }

同样,了解这一点很棒,但我建议您看看其他一些实现以向它们学习。 Pimple's 很棒,非常简单,易于理解。

在这里,我们将容器注入到所有对象上,而不是对象的具体依赖项,这通常(正确地)不受欢迎。但是我不想对您的设计进行更多修改,最好您自己学习。

除此之外,您的容器还处理由autoloader 更好地处理的内容,并混合职责。

另外,正如我在 cmets 中所说,您正在重新发明 PHP 中已经存在的 __DIR__ 常量。你正在使用

define( 'DIR', dirname(__FILE__) . '/' );

这几乎等同于__DIR__(或者,更准确地说,等同于__DIR__ . '/')。


最后,你的大部分麻烦都是由循环依赖引起的,你的容器(也不是任何容器)都无法修复。 A 依赖于 B,而 B 又依赖于 A。解决此问题的唯一方法是使用 setter 注入依赖项。

一个更简单的实现,或多或少遵循您的代码:

对于自动加载:

function autoload_so($class)
{
    $file = __DIR__ . "/app/classes/class." . strtolower($class) . '.php';
    if (file_exists($file)) {
        include_once($file);

        return true;
    }

    return false;
}

spl_autoload_register('autoload_so');

我没有使用命名空间,并且忽略了您的界面逻辑。这也有点奇怪。您需要为接口实现自己的加载,您应该可以这样做。

对于容器:

class MyContainer
{
    public $services = [];

    function __get($service)
    {
        if ( ! isset($this->services[$service]) ) {
            $this->services[$service] = call_user_func([$this, "get_$service"]);
        }

        return $this->services[$service];
    }

    /**
     * @return Users
     */
    private function get_Users()
    {
        return new Users($this->Database, $this->Core);
    }

    /**
     * @return Core
     */
    private function get_Core()
    {
        return new Core();
    }

    /**
     * @return Database
     */
    private function get_Database()
    {
        return new Database($this->Core);
    }

}

注意

  • 您需要为每个要添加的新“服务”使用新方法。
  • 我在构造函数中直接注入依赖项

最后你的课程会是这样的:

数据库

<?php

class Database
{
    public function __construct(Core $core)
    {
        $this->core  = $core;
    }
}

用户

class Users
{
    public $ip;

    public function __construct(Database $db, Core $core)
    {
        $this->db   = $db;
        $this->core = $core;
        $this->ip   = $this->core->userIP();
    }

}

等等。

再一次,这一切都非常粗糙。我会使用命名空间和更通用的自动加载器和目录结构。

所有这些都足以作为您构建自己的起点。

但是没有容器会神奇地为你修复循环依赖

【讨论】:

  • 非常感谢,我会看看这是否可行,否则请检查 Pimple,因为它看起来很有用
  • 酷。让我知道这是怎么回事,好吗?如果它不起作用,我们可以重新访问代码。 :)
  • 在我的索引页上尝试从 class.core.php 调用 userIP() 函数时收到此错误:Call to undefined Closure::userIP()跨度>
  • 好吧,它起初工作,但现在我仍然收到未定义的属性错误:/
  • 具体在哪里?
猜你喜欢
  • 2017-09-16
  • 2012-08-13
  • 2017-09-24
  • 1970-01-01
  • 2019-08-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多