我一直在阅读/观看很多推荐材料,最近一次是这个 - 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()
轻松调试我的类?如何避免将所有这些实例作为变量存储在我的类中?
答案 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输出,您可以看到,您调用的服务只在输出中。 所以这个小,快,性感;)