In a PHP project, what patterns exist to store, access and organize helper objects?

How do you organize and manage your helper objects like the database engine, user notification, error handling and so on in a PHP based, object oriented project?

Say I have a large PHP CMS. The CMS is organized in various classes. A few examples:

  • the database object
  • user management
  • an API to create/modify/delete items
  • a messaging object to display messages to the end user
  • a context handler that takes you to the right page
  • a navigation bar class that shows buttons
  • a logging object
  • possibly, custom error handling

etc.

I am dealing with the eternal question, how to best make these objects accessible to each part of the system that needs it.

my first apporach, many years ago was to have a $application global that contained initialized instances of these classes.

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

I then changed over to the Singleton pattern and a factory function:

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

but I'm not happy with that either. Unit tests and encapsulation become more and more important to me, and in my understanding the logic behind globals/singletons destroys the basic idea of OOP.

Then there is of course the possibility of giving each object a number of pointers to the helper objects it needs, probably the very cleanest, resource-saving and testing-friendly way but I have doubts about the maintainability of this in the long run.

Most PHP frameworks I have looked into use either the singleton pattern, or functions that access the initialized objects. Both fine approaches, but as I said I'm happy with neither.

I would like to broaden my horizon on what common patterns exist here. I am looking for examples, additional ideas and pointers towards resources that discuss this from a long-term, real-world perspective.

Also, I'm interested to hear about specialized, niche or plain weird approaches to the issue.

13095 次浏览
class Application {
protected static $_singletonFoo=NULL;


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


}

我就是这么做的,它根据需要创建对象:

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

这是我正在做的事情,它尊重 OOP 原则,它比你现在正在做的事情更少的代码,并且只有当代码第一次需要它的时候才创建对象。

注意 : 我所展示的甚至不是一个真正的单例模式。通过将构造函数(Foo: : _ _ structor ())定义为 private,单例模式只允许自身的一个实例。它只是一个可用于所有“应用程序”实例的“全局”变量。这就是为什么我认为它的使用是有效的,因为它没有忽视好的 OOP 原则。当然,作为世界上的任何东西,这种“模式”也不应该被过度使用!

我在许多 PHP 框架中看到过这种方法,Zend Framework 和 Yii 就是其中之一。你应该使用一个框架。我不会告诉你是哪个。

For the ones among you worrying about TDD, you can still make up some wiring to dependency-inject it. It could look like this:

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,发挥你的想象力。

Why do it like that? Well, most of the time the application won't be unit-tested, it will actually be run, 希望在生产环境中. The strength of PHP is its speed. PHP is NOT and never will be a "clean OOP language", like Java.

在一个应用程序中,最多只有一个 Application 类及其每个助手的一个实例(如上所述的延迟加载)。当然,单身是不好的,但是话又说回来,只有当他们不遵守现实世界。在我的例子中,他们是这样做的。

定型的“规则”,如“单身是坏的”是邪恶的来源,他们是懒惰的人不愿意为自己考虑。

是的,我知道,从技术上来说,PHP 宣言是糟糕的,但是它是一种成功的语言,从它的黑客风格来看。

附录

One function style:

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();

我会避免弗拉维乌斯建议的辛格尔顿方法。有很多理由可以避免这种方法。它违反了良好的 OOP 原则。谷歌测试博客上有一些关于 Singleton 的好文章,以及如何避免它:

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

替代品

  1. 服务供应商

    Http://java.sun.com/blueprints/corej2eepatterns/patterns/servicelocator.html

  2. 依赖注入

    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

实施依赖注入:

  • 我相信你应该 询问构造函数中需要什么对象才能起作用: new YourObject($dependencyA, $dependencyB);

  • 您可以提供所需的对象(依赖项) 手动操作($application = new Application(new MessageHandler())。但是你也可以使用 DI 框架(维基百科页面提供了 到 PHP DI 框架的链接)。

    重要的是,您只传递您实际使用的内容(调用动作) ,而不是仅仅传递给其他对象的内容,因为它们需要这些内容。下面是“鲍勃叔叔”(罗伯特 · 马丁)最近发表的一篇讨论 手动依赖注入与使用框架的文章。

Some more thoughts on Flavius's solution. I don't want this post to be an anti-post but I think it's important to see why dependency injection is, at least for me, better than globals.

即使它不是一个“真正的”辛格尔顿实现,我仍然认为 Flavius 弄错了。全球化很糟糕.注意,这样的解决方案也使用 难以测试静态方法

我知道很多人这样做,批准和使用它。但是阅读米斯科 · 赫弗里斯的博客文章(谷歌可测试性专家) ,重新阅读并慢慢消化他所说的确改变了我看待设计的方式。

如果希望能够测试应用程序,则需要采用不同的方法来设计应用程序。当你进行测试优先的编程时,你会遇到这样的困难: “接下来我想在这段代码中实现日志记录; 让我们先编写一个测试来记录一条基本消息”,然后编写一个测试来强制你编写并使用一个不能被替换的全局日志记录器。

我仍然是 挣扎的所有信息,我从那个博客,它并不总是容易实现,我有很多问题。但是,当我理解了 Misko Hevery 所说的话之后,我再也没有办法回到我以前所做的事情(是的,全球状态和单身族(大 S))

我喜欢依赖注入的概念:

依赖注入是指组件通过构造函数、方法或直接进入字段而获得其依赖关系的地方

Fabien Potencier 写了一个非常好的 series of articles about Dependency Injection并且需要使用它们。他还提供了一个很好的和小的依赖注入容器命名为 青春痘,我真的很喜欢使用(更多信息关于 Github)。

如上所述,我不喜欢使用单件。一个很好的总结为什么单例不是好的设计可以找到 在 Steve Yegge 的博客里

If you want to make objects globally available, the 注册表模式注册表模式 could be interesting for you. For inspiration, have a look at Zend 注册表.

也是 注册处诉 Singleton的问题。

Objects in PHP take up a good amount of memory, as you have probably seen from your unit tests. Therefore it is ideal to destroy un-needed objects as 越快越好 to save memory for other processes. With that in mind I find that every object fits one of two molds.

1)对象可能有很多有用的方法,或者需要调用不止一次,在这种情况下,我实现一个单例/注册表:

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

2)对象只存在于调用它的方法/函数的生命周期中,在这种情况下,一个简单的创建有利于防止延迟的对象引用使对象保持太长的生命周期。

$object = new Class();

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

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

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

我选择返回初始化对象的函数:

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

在测试环境中,您可以定义它来返回模型。您甚至可以使用 debug _ backtrace ()检测调用函数的内部人员,并返回不同的对象。您可以在其中注册谁想要得到什么对象,以获得一些见解,您的程序中实际发生了什么。

最好的方法是对这些资源使用某种类型的 集装箱。一些最常见的实现 集装箱的方法:

辛格尔顿

不推荐使用,因为它很难测试,并且暗示了一种全局状态

注册处

消除单一性,bug 我也不推荐注册表,因为它也是一种单一性。(难以单元测试)

Inheritance

遗憾的是,PHP 没有多重继承,所以这限制了所有的链接。

Dependency injection

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

传统的

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

框架

你可以使用你自己的依赖注入器,或者使用一些依赖注入框架,例如 亚迪夫

应用资源

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

这是在 ZendFramework1.x 中实现的方法

资源载入器

一种静态对象,它只在需要时加载(创建)所需的资源。这是一个非常聪明的方法。您可能会看到它在运行,例如实现 Symfony 的依赖注入组件

注射到特定的层

资源并不总是需要在应用程序的任何地方。有时候你只是需要它们,例如在控制器中(MV C)。然后您可以只在那里注入资源。

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

看看我的方法:

如何在 Zend Framework 使用依赖注入?-堆栈溢出

最后,我想补充一点关于这里非常重要的事情——缓存。
通常,不管您选择什么技术,您都应该考虑如何缓存资源。缓存将是资源本身。

应用程序可能非常庞大,并且在每个请求上加载所有资源的成本非常高。有许多方法,包括这个 Appserver-in-php-Google Code 上的项目托管