在一个PHP项目中,存在哪些模式来存储,访问和组织助手对象?

在基于PHP的面向对象项目中,如何组织和pipe理像数据库引擎,用户通知,error handling等助手对象?

说我有一个大的PHP CMS。 CMS以各种类别组织。 几个例子:

  • 数据库对象
  • 用户pipe理
  • 创build/修改/删除项目的API
  • 一个消息对象向最终用户显示消息
  • 一个上下文处理程序,将您带到正确的页面
  • 显示button的导航栏类
  • 一个日志对象
  • 可能是自定义error handling

等等

我正在处理永恒的问题,如何最好地使这些对象可以访问需要它的系统的每个部分。

我的第一个apporach,很多年前是有一个$应用程序的全球包含这些类的初始化的实例。

global $application; $application->messageHandler->addMessage("Item successfully inserted"); 

然后我转换到Singleton模式和一个工厂函数:

 $mh =&factory("messageHandler"); $mh->addMessage("Item successfully inserted"); 

但是我也不满意 unit testing和封装对我来说变得越来越重要,并且在我看来,全局/单例背后的逻辑破坏了OOP的基本思想。

那么当然有可能给每个对象一些指向它所需的帮助对象的指针,可能是最清洁的,资源节约和testing友好的方式,但是我从长远来看对这种可维护性有怀疑。

我查看过的大多数PHP框架都使用单例模式,或者访问初始化对象的函数。 两个好的方法,但正如我所说,我既不高兴。

我想扩大对这里存在的常见模式的看法。 我正在寻找实例,从长远的 现实世界angular度来讨论这个问题的额外的想法和指向资源。

另外,我有兴趣听到关于这个问题的专业化,小众化或简单的方法。

我会避免Flavius提出的Singleton方法。 有许多理由避免这种做法。 它违反了良好的面向对象的原则。 谷歌testing博客有一些关于单身人士的好文章,以及如何避免它:

http://googletesting.blogspot.com/2008/08/by-miko-hevery-so-you-join-new-project.html http://googletesting.blogspot.com/2008/05/tott-using-dependancy -injection-to.html http://googletesting.blogspot.com/2008/08/where-have-all-singletons-gone.html

备择scheme

  1. 一个服务提供者

    http://java.sun.com/blueprints/corej2eepatterns/Patterns/ServiceLocator.html

  2. dependency injection

    http://en.wikipedia.org/wiki/Dependency_injection

    和一个php的解释:

    http://components.symfony-project.org/dependency-injection/trunk/book/01-Dependency-Injection

这是一个关于这些替代品的好文章:

http://martinfowler.com/articles/injection.html

实现dependency injection(DI):

  • 我相信你应该问在构造函数中需要什么对象的function : new YourObject($dependencyA, $dependencyB);

  • 您可以手动提供所需的对象(依赖关系)( $application = new Application(new MessageHandler() )。但是您也可以使用DI框架 (wikipedia页面提供了PHP DI框架的链接 )。

    重要的是,你只传递你实际使用的内容(调用一个动作),而不是你传递给其他对象的东西,因为他们需要它。 以下是“叔叔鲍伯”(Robert Martin)最近发表的一篇文章,讨论手动DI与使用框架 。

对Flavius的解决scheme有更多的想法。 我不希望这篇文章成为一个反职位,但我认为重要的是要看到为什么dependency injection,至less对我来说,比全局更好。

尽pipe这不是一个“真正的” Singleton实现,但我仍然认为Flavius错了。 全球状态不好 。 请注意,这样的解决scheme也难以testing静态方法 。

我知道很多人这样做,批准和使用它。 但是阅读Misko Heverys的博客文章( 一位谷歌可testing性专家 ),重读并慢慢消化他所做的改变了我对devise的看法。

如果你想能够testing你的应用程序,你需要采用不同的方法来devise你的应用程序。 当你进行testing优先编程时,你会遇到这样的困难:'接下来我想在这段代码中实现日志logging; 让我们先写一个testinglogging一个基本信息“,然后提出一个testing,迫使你编写和使用一个无法替代的全局logging器。

我仍然在为从博客获得的所有信息而苦苦挣扎 ,这并不总是很容易实现,而且我有很多问题。 但是我没有办法回到我之前做的事情上(是的,全球状态和Singletons(大S)),我抓住了Misko Hevery说的话:-)

 class Application { protected static $_singletonFoo=NULL; public static function foo() { if(NULL === self::$_singletonFoo) { self::$_singletonFoo = new Foo; } return self::$_singletonFoo; } } 

我就是这么做的。 它按需创build对象:

 Application::foo()->bar(); 

这是我做这件事的方式,它尊重面向对象的原则,它比现在的代码更less代码,只有当代码第一次需要时才创build对象。

注意 :我所提供的甚至不是一个真正的单身模式。 通过将构造函数(Foo :: __ constructor())定义为私有的,单例将只允许自己的一个实例。 它只是一个“全局”variables,可用于所有“应用程序”实例。 这就是为什么我认为它的使用是有效的,因为它不会忽视良好的面向对象的原则。 当然,就像世界上的任何东西一样,这个“模式”也不应该被过度使用!

我已经看到这个在许多PHP框架中使用,其中包括Zend Framework和Yii。 你应该使用一个框架。 我不会告诉你哪一个。

附录对于那些担心TDD的人 ,仍然可以通过一些布线来dependency injection。 它可能是这样的:

 class Application { protected static $_singletonFoo=NULL; protected static $_helperName = 'Foo'; public static function setDefaultHelperName($helperName='Foo') { if(is_string($helperName)) { self::$_helperName = $helperName; } elseif(is_object($helperName)) { self::$_singletonFoo = $helperName; } else { return FALSE; } return TRUE; } public static function foo() { if(NULL === self::$_singletonFoo) { self::$_singletonFoo = new self::$_helperName; } return self::$_singletonFoo; } } 

有足够的改善空间。 这只是一个PoC,用你的想象力。

为什么这样? 那么,大多数情况下,应用程序不会进行unit testing,实际上它将在生产环境中运行 。 PHP的优势在于速度。 PHP不是,永远也不会像Java那样是一个“干净的OOP语言”。

在应用程序中,最多只有一个Application类和每个助手的一个实例(按照上面的延迟加载)。 当然, 单身是不好的,但是再一次,只有当他们不坚持现实世界。 在我的例子中,他们这样做。

像“单身不好”这样的刻板的“规则”是邪恶的源头,他们是懒惰的人,不愿为自己思考。

是的,我知道,从技术上来说,PHP宣言是坏的。 然而,这是一个成功的语言,以一种诡异的方式。

附录

一种function风格:

 function app($class) { static $refs = array(); //> Dependency injection in case of unit test if (is_object($class)) { $refs[get_class($class)] = $class; $class = get_class($class); } if (!isset($refs[$class])) $refs[$class] = new $class(); return $refs[$class]; } //> usage: app('Logger')->doWhatever(); 

我喜欢dependency injection的概念:

“dependency injection是组件通过构造函数,方法或直接放入字段的依赖关系(从Pico Container网站 )”

Fabien Potencier写了一系列有关dependency injection的文章,以及使用它们的必要性。 他还提供了一个名为Pimple的漂亮而小巧的dependency injection容器,我非常喜欢使用它(在github上有更多的信息)。

如上所述,我不喜欢单身人士的使用。 在Steve Yegge的博客中可以find关于为什么Singleton不是很好的devise的一个很好的总结。

最好的方法是为这些资源提供某种容器 。 一些实现这个容器的最常用的方法是:

独生子

不推荐,因为它是很难testing,并暗示一个全球性的国家。 (Singletonitis)

注册处

消除单纯性扁桃体炎,臭虫我也不推荐registry,因为它也是一种单身。 (很难unit testing)

遗产

可惜,在PHP中没有多重inheritance,所以这就限制了所有的链。

dependency injection

这是一个更好的方法,但是一个更大的话题。

传统

这样做的最简单方法是使用构造函数或setter注入(传递依赖对象使用setter或类构造函数)。

构架

你可以推出你自己的dependency injection器,或者使用一些dependency injection框架,例如。 Yadif

应用程序资源

您可以初始化应用程序引导程序(作为容器)中的每个资源,并在访问引导程序对象的应用程序中的任何位置访问它们。

这是在Zend Framework 1.x中实现的方法

资源加载器

一种仅在需要时加载(创build)所需资源的静态对象。 这是一个非常聪明的方法。 您可能会看到它在实施Symfony的dependency injection组件

注射到特定的层

资源并不总是在应用程序的任何地方需要。 有时你只需要他们,例如在控制器(MV C )中。 那么你可能只注入资源。

通用的方法是使用docblock注释来添加注入元数据。

看到我在这里的方法:

如何在Zend Framework中使用dependency injection? – 堆栈溢出

最后,我想在这里添加一个关于非常重要的事情的提示 – caching。
一般来说,尽pipe你select的技术,你应该考虑如何caching资源。 caching将是资源本身。

应用程序可能非常大,并且在每个请求上加载所有资源非常昂贵。 有许多方法,包括这个appserver-in-php – Google代码上的Project Hosting 。

如果你想让全局可用的对象, registry模式可能会让你感兴趣。 为了获得灵感,看看Zend Registry 。

所以也是registry与单身的问题。

PHP中的对象占用了大量的内存,就像你可能从unit testing中看到的那样。 因此,尽快销毁不需要的对象以节省其他进程的内存是理想的。 考虑到这一点,我发现每个物体都适合两种模具中的一种。

1)该对象可能有许多有用的方法或需要被多次调用,在这种情况下,我实现了一个单身/registry:

 $object = load::singleton('classname'); //or $object = classname::instance(); // which sets self::$instance = $this 

2)该对象只存在于调用它的方法/函数的生命中,在这种情况下,简单的创build有利于防止延迟的对象引用使对象保持活动时间过长。

 $object = new Class(); 

在任何地方存储临时对象都可能导致内存泄漏,因为引用它们可能会忘记将对象保留在内存中以用于脚本的其余部分。

我会去函数返回初始化对象:

 A('Users')->getCurrentUser(); 

在testing环境中,您可以将其定义为返回模型。 你甚至可以使用debug_backtrace()来检测谁在调用函数,并返回不同的对象。 你可以在里面注册谁想要得到什么对象来获得你的程序内实际发生的一些事情。

为什么不阅读精美的手册?

http://php.net/manual/en/language.oop5.autoload.php