您可以使用依赖注入并仍然避免许多私有变量吗?

时间:2015-02-18 01:17:40

标签: php debugging dependency-injection singleton

我一直在阅读/观看很多推荐材料,最近一次是这个 - MVC for advanced PHP developers。有一件事是Singletons很糟糕,它们在类之间创建依赖关系,而依赖注入很好,因为它允许单元测试和解耦。

在我编写程序之前,这一切都很好。我们以电子商店的产品页面为例。首先,我有我的页面:

class Page {
    public $html;

    public function __construct() {

   } 

    public function createPage() {
        // do something to generate the page
   } 

    public function showPage()  {
        echo $this->html;
   } 
} 

到目前为止一切都很好,但页面需要一个产品,所以让我们传递一个:

class Page {
    public $html;
    private $product;

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

    public function createPage() {
        // do something to generate the page
   } 

    public function showPage()  {
        echo $this->html;
   } 
} 

我使用依赖注入来避免让我的页面类依赖于产品。但是如果页面有几个公共变量并且在调试时我想看看那些是什么。没问题,我只是var_dump()页面实例。它为我提供了页面中的所有变量,包括产品对象,因此我也获得了产品中的所有变量。

但是产品并不只是包含产品实例化的所有细节的所有变量,它还有一个数据库连接来获取这些产品的详细信息。所以现在我的var_dump()也有数据库对象。现在它开始变得更长,更难以阅读,即使在<pre>标签中也是如此。

此外,产品属于一个或多个类别。为了论证,让我们说它属于两个类别。它们在构造函数中加载并存储在包含数组的类变量中。所以现在我不仅拥有产品和数据库连接中的所有变量,还拥有类别类的两个实例。当然,类别信息也必须从数据库加载,因此每个类别实例也有一个数据库私有变量。

所以现在当我var_dump()我的页面时,我拥有所有页面变量,所有产品变量,数组中类别变量的倍数,以及数据库变量的3个副本(一个来自产品实例和一个来自每个类别实例)。我的输出现在很庞大,难以阅读。

现在单身人士怎么样?让我们看一下使用单例的页面类。

class Page {
    public $html;

    public function __construct() {

   } 

    public function createPage() {
        $prodId = Url::getProdId();
        $productInfo = Product::instance($prodId)->info(); 
        // do something to generate the page
   } 

    public function showPage()  {
        echo $this->html;
   } 
} 

我也在Product类中使用类似的单例。现在,当我var_dump()我的Page实例时,我只获得了我想要的变量,那些属于页面的变量,没有别的。

但是当然这会在我的类之间创建依赖关系。在单元测试中,没有办法不调用产品类,使单元测试变得困难。

如何才能获得依赖注入的所有好处,但仍然可以使用var_dump()轻松调试我的类?如何避免将所有这些实例作为变量存储在我的类中?

4 个答案:

答案 0 :(得分:1)

我会尝试在这里写几件事。

关于var_dump()

我使用Symfony2作为默认框架,有时,var_dump()是快速调试的最佳选择。但是,它可以输出如此多的信息,你无法阅读所有信息,对吧?比如,倾销Symfony的AppKernel.php,或者更接近你的情况,一些服务具有EntityManager依赖性。恕我直言,var_dump()在调试少量代码时很不错,但是大而复杂的产品使var_dump()无效。替代方案是使用&#34;真正的&#34;调试器,与IDE集成。使用PhpStorm下的xDebug,我不再需要var_dump()了。

有用的链接&#34;为什么?&#34;和&#34;操作方法?&#34;是here

关于DI容器:

它的忠实粉丝。它很简单,使代码更稳定;它在现代应用中很常见。但我同意你的观点,背后有一个真正的问题:嵌套依赖。这是过度抽象,通过添加有时不必要的层来增加复杂性。

  

通过使用依赖注入容器来掩盖痛苦   你的申请更复杂。

如果您想从应用程序中删除DIC,并且实际上可以执行此操作,那么您根本不需要DIC。如果您想要替代DIC,那么...... Singletons 被认为是不可测试的代码和应用程​​序的巨大状态空间的不良做法。 服务定位器给我no benefits at all。所以看起来有唯一的方法,就是学习使用DI吧。

关于您的示例:

我立即看到一件事 - 通过construct()注入。这很酷,但我更喜欢可选的传递依赖关系到需要它的方法,例如通过服务config.yml中的setter。

class Page
{
    public $html;

    protected $em;
    protected $product;

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

    //I suppose it's not from DB, because in this case EM handles this for you
    protected function setProduct(Product $product)
    {
        $this->product = $product;
    }

    public function createPage()
    {
        //$this->product can be used here ONLY when you really need it

        // do something to generate the page
    } 

    public function showPage()
    {
        echo $this->html;
    } 
}

我认为在执行期间只需要一些对象时,它会提供所需的灵活性,在给定的时刻,您只能在类中看到所需的属性。

<强>结论

请原谅我的答案。我真的认为你的问题没有直接的答案,任何解决方案都是基于意见的。我希望您可能会发现DIC确实是有限缺点的最佳解决方案,以及集成调试器而不是转储整个类(构造函数,服务等等)。

答案 1 :(得分:1)

我完全知道可以达到您想要的结果,并且不使用极端解决方案。
我不确定我的例子对你来说是否足够好,但它有:di并且很容易通过单元测试来覆盖,var_dump将显示你想要的内容,我认为它鼓励SRP。

<?php

class Url
{
    public static function getProdId()
    {
        return 'Category1';
    }
}

class Product
{
    public static $name = 'Car';

    public static function instance($prodId)
    {
        if ($prodId === 'Category1') {
            return new Category1();
        }
    }
}

class Category1 extends Product
{
    public $model = 'DB9';

    public function info()
    {
        return 'Aston Martin DB9 v12';
    }
}

class Page
{
    public $html;

    public function createPage(Product $product)
    {
        // Here you can do something more to generate the page.
        $this->html = $product->info() . PHP_EOL;
    }

    public function showPage()
    {
        echo $this->html;
    }
}

$page = new Page();
$page->createPage(Product::instance(Url::getProdId()));
$page->showPage();
var_export($page);

结果:

Aston Martin DB9 v12
Page::__set_state(array(
   'html' => 'Aston Martin DB9 v12
',
))

答案 2 :(得分:0)

也许这会对你有所帮助:

  class Potatoe {
    public $skin;
    protected $meat;
    private $roots;

    function __construct ( $s, $m, $r ) {
        $this->skin = $s;
        $this->meat = $m;
        $this->roots = $r;
    }
}

$Obj = new Potatoe ( 1, 2, 3 );

echo "<pre>\n";
echo "Using get_object_vars:\n";

$vars = get_object_vars ( $Obj );
print_r ( $vars );

echo "\n\nUsing array cast:\n";

$Arr = (array)$Obj;
print_r ( $Arr );

这将返回:

Using get_object_vars:
Array
(
    [skin] => 1
)

Using array cast:
Array
(
    [skin] => 1
    [ * meat] => 2
    [ Potatoe roots] => 3
)

在此处查看其余内容http://php.net/manual/en/function.get-object-vars.php

答案 3 :(得分:0)

简短的回答是,是的,你可以避免许多私有变量并使用依赖注入。但是(这是一个很大的但你必须使用像ServiceContainer或它的原理。

答案简短:

class A
{

    protected $services = array();

    public function setService($name, $instance)
    {
        $this->services[$name] = $instance;
    }

    public function getService($name)
    {
        if (array_key_exists($name, $this->services)) {
            return $this->services[$name];
        }

        return null;
    }


    private function log($message, $logLevel)
    {
        if (null === $this->getService('logger')) {
            // Default behaviour is to log to php error log if $logLevel is critical
            if ('critical' === $logLevel) {
                error_log($message);
            }

            return;
        }
        $this->getService('logger')->log($message, $logLevel);
    }

    public function actionOne()
    {
        echo 'Action on was called';
        $this->log('Action on was called', 0);
    }

}

$a = new A();

// Logs to error log
$a->actionOne();

$a->setService('logger', new Logger());

// using the logger service
$a->actionOne();

使用该类,您只有一个受保护的变量,只需添加服务即可向该类添加任何功能。

使用ServiceContainer的更复杂的示例可能类似于

 <?php

/**
 * Class ServiceContainer
 * Manage our services
 */
class ServiceContainer
{
    private $serviceDefinition = array();

    private $services = array();

    public function addService($name, $class)
    {
        $this->serviceDefinition[$name] = $class;
    }

    public function getService($name)
    {
        if (!array_key_exists($name, $this->services)) {
            if (!array_key_exists($name, $this->serviceDefinition)) {
                throw new \RuntimeException(
                    sprintf(
                        'Unkown service "%s". Known services are %s.',
                        $name,
                        implode(', ', array_keys($this->serviceDefinition))
                    )
                );
            }
            $this->services[$name] = new $this->serviceDefinition[$name];
        }

        return $this->services[$name];

    }
}

/**
 * Class Product
 * Part of the Model. Nothing too complex
 */
class Product
{
    public $id;
    public $info;

    /**
     * Get info
     *
     * @return mixed
     */
    public function getInfo()
    {
        return $this->info;
    }

}

/**
 * Class ProductManager
 *
 */
class ProductManager
{
    public function find($id)
    {
        $p = new Product();
        $p->id = $id;
        $p->info = 'Product info of product with id ' . $id;

        return $p;
    }
}


class UnusedBadService
{
    public function _construct()
    {
        ThisWillProduceAnErrorOnExecution();
    }
}

/**
 * Class Page
 * Handle this request.
 */
class Page
{
    protected $container;


    /**
     * Set container
     *
     * @param ServiceContainer $container
     *
     * @return ContainerAware
     */
    public function setContainer(ServiceContainer $container)
    {
        $this->container = $container;

        return $this;
    }

    public function get($name)
    {
        return $this->container->getService($name);
    }

    public function createPage($productId)
    {
        $pm = $this->get('product_manager');
        $productInfo = $pm->find($productId)->getInfo();

        // do something to generate the page
        return sprintf('<html><head></head><body><h1>%s</h1></body></html>', $productInfo);
    }

}

$serviceContainer = new ServiceContainer();

// Add some services
$serviceContainer->addService('product_manager', 'ProductManager');
$serviceContainer->addService('unused_bad_service', 'UnusedBadService');

$page = new Page();
$page->setContainer($serviceContainer);


echo $page->createPage(1);


var_dump($page);

如果查看var_dump输出,您可以看到,您调用的服务只在输出中。 所以这个小,快,性感;)