Php 您是否可以使用依赖项注入,同时仍然避免大量私有变量?
我一直在阅读/观看许多推荐材料,最近一次是在今年-。出现的一件事是,单例是不好的,它们在类之间创建依赖关系,依赖关系注入是好的,因为它允许单元测试和解耦 在我写程序之前,一切都很好。让我们以ESPHOP中的产品页面为例。首先,我有我的页面:Php 您是否可以使用依赖项注入,同时仍然避免大量私有变量?,php,debugging,dependency-injection,singleton,Php,Debugging,Dependency Injection,Singleton,我一直在阅读/观看许多推荐材料,最近一次是在今年-。出现的一件事是,单例是不好的,它们在类之间创建依赖关系,依赖关系注入是好的,因为它允许单元测试和解耦 在我写程序之前,一切都很好。让我们以ESPHOP中的产品页面为例。首先,我有我的页面: class Page { public $html; public function __construct() { } public function createPage() { // do some
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\u dump()
页面实例。它给了我页面中的所有变量,包括product对象,所以我也得到了product中的所有变量
但该产品不仅拥有包含该产品所有细节的所有变量,它还拥有一个数据库连接来获取这些产品细节。因此,现在我的var_dump()
中也包含数据库对象。现在它开始变得更长,更难阅读,即使在
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()
我的页面实例时,我只得到我想要的变量,那些属于页面的变量,而没有其他变量
当然,这在我的类之间创建了依赖关系。在单元测试中,没有办法不调用产品类,这使得单元测试变得困难
如何既能获得依赖注入的所有好处,又能使用var\u dump()
轻松调试类?如何避免将所有这些实例作为变量存储在类中 我将试着在这里写几件事
关于var\u dump()
:
我使用Symfony2作为默认框架,有时,var\u dump()
是快速调试的最佳选项。然而,它可以输出如此多的信息,以至于你不可能阅读所有的信息,对吗?比如,转储Symfony的AppKernel.php
,或者更接近您的情况,转储具有EntityManager
依赖关系的服务。我的意思是,var\u dump()
在调试少量代码时很好,但是大型复杂的产品会使var\u dump()
无效。对我来说,另一种选择是使用一个与IDE集成的“真正的”调试器。使用PhpStorm下的xDebug,我不再真正需要var\u dump()
关于“为什么”和“如何”的有用链接是
关于DI容器:
它的忠实粉丝。它简单,使代码更稳定;这在现代应用中很常见。但我同意你的观点,在这背后有一个真正的问题:嵌套依赖项。这是过度抽象,有时会添加不必要的层,从而增加复杂性
通过使用依赖项注入容器来掩盖痛苦正在产生
您的应用程序更加复杂
如果您想从应用程序中删除DIC,并且您确实可以这样做,那么您根本不需要DIC。如果你想要DIC的替代品,那么单例对于不可测试的代码和应用程序的巨大状态空间来说被认为是不好的做法服务定位器已发送给我。看来只有这样,才能正确地学习使用DI
关于您的示例:
我立即看到一件事——通过construct()
注入。这很酷,但我更喜欢可选的传递依赖项,而不是需要它的方法,例如通过servicesconfig.yml
中的setters
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 );
我认为,当您在执行过程中只需要一些对象时,它提供了所需的灵活性,并且在给定的时刻,您可以在类内部看到所需的属性
结论
请原谅我宽泛而浅薄的回答。我真的认为你的问题没有直接的答案,任何解决方案都是基于意见的。我只是希望您会发现DIC确实是最好的解决方案,缺点有限,而且集成了调试器,而不是转储整个类(构造函数、服务等)。也许这会帮助您:
Using get_object_vars:
Array
(
[skin] => 1
)
Using array cast:
Array
(
[skin] => 1
[ * meat] => 2
[ Potatoe roots] => 3
)
请看这里的其余部分我确切地知道,实现您所希望的结果是可能的,并且不使用极端的解决方案。
我不确定我的示例对您来说是否足够好,但它有:di和它很容易通过单元测试和var_dump来涵盖,它将完全显示您希望的内容,我认为它鼓励SRP
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这样的东西或者它的原理
简单的回答是:
对于该类,您只有一个受保护的变量,您可以通过添加服务向该类添加任何功能
带有ServiceContainer的更复杂的示例可能是这样的
调度员怎么样?Page类充当基类,ProductPage是派生类,页面使用所需的变量分派给ProductPage。如果您需要另一个页面,请创建一个新的类InfoPage并从page和Dispatch派生。您使用的这段代码是快速示例还是系统架构的一部分?如果第二,我想,你没有使用任何框架并构建自己的框架?这只是一个简单的例子,不是我真正的架构。通常我使用Laravel或其他基于symphony的框架。Page类的职责是什么?@yanivel它将负责整理所有页面部分并输出它们。在现实世界中,Page
可能是其他人扩展的父类,例如ProductPage
或CategoryPage
。感谢您花时间访问
<?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
',
))
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();
<?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);