在 PHP 项目中,存在哪些存储、访问和组织辅助对象的模式? [关闭]

Posted

技术标签:

【中文标题】在 PHP 项目中,存在哪些存储、访问和组织辅助对象的模式? [关闭]【英文标题】:In a PHP project, what patterns exist to store, access and organize helper objects? [closed] 【发布时间】:2010-12-21 05:06:55 【问题描述】:

在一个基于 php 的面向对象的项目中,您如何组织和管理您的帮助对象,例如数据库引擎、用户通知、错误处理等?

假设我有一个大型 PHP CMS。 CMS 分为不同的类别。几个例子:

数据库对象 用户管理 创建/修改/删除项目的 API 向最终用户显示消息的消息传递对象 上下文处理程序将您带到正确的页面 显示按钮的导航栏类 一个日志对象 可能是自定义错误处理

等等

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

多年前我的第一个方法是创建一个包含这些类的初始化实例的 $application 全局变量。

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

然后我切换到单例模式和工厂函数:

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

但我也不满意。单元测试和封装对我来说变得越来越重要,在我的理解中,全局/单例背后的逻辑破坏了 OOP 的基本思想。

那么当然有可能给每个对象一些指向它需要的辅助对象的指针,这可能是最干净、节省资源和测试友好的方式,但我对这种方式的可维护性长期存在疑问跑步。

我研究过的大多数 PHP 框架要么使用单例模式,要么使用访问初始化对象的函数。两种方法都很好,但正如我所说,我对这两种方法都不满意。

我想拓宽视野,了解这里存在哪些常见模式。我正在寻找从长期现实世界角度讨论此问题的示例、其他想法和资源指针。

另外,我很想听听有关该问题的专业、利基或普通怪异方法。

【问题讨论】:

我刚刚问了一个非常相似的问题,也有赏金。您可能会在那里欣赏一些答案:***.com/questions/1967548/… 请注意,通过引用返回一个新对象——比如$mh=&factory("messageHandler"); 是没有意义的,并且不会产生任何性能优势。此外,这在 5.3 中已弃用。 【参考方案1】:

我会避免 Flavius 建议的 Singleton 方法。避免这种方法的原因有很多。它违反了良好的 OOP 原则。 google 测试博客有一些关于 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

替代方案

    服务提供商

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

    依赖注入

    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

实现依赖注入(DI):

我相信你应该ask what is needed in the constructor for the object to function:new YourObject($dependencyA, $dependencyB);

您可以手动提供所需的对象(依赖项) ($application = new Application(new MessageHandler())。但您也可以使用DI 框架(***页面提供links to PHP DI frameworks)。

重要的是你只传递你实际使用的东西(调用一个动作),而不是你简单地传递给其他对象的东西,因为他们需要它。这是“鲍勃叔叔”(罗伯特·马丁)最近发表的一篇讨论 manual DI vs using framework 的帖子。

关于 Flavius 解决方案的更多想法。我不希望这篇文章成为反帖,但我认为重要的是要了解为什么依赖注入至少对我来说比全局更好。

即使它不是“真正的”Singleton 实现,我仍然认为 Flavius 弄错了。 Global state is bad。请注意,此类解决方案也使用difficult to test static methods。

我知道很多人都这样做,批准并使用它。但是阅读 Misko Heverys 的博客文章 (a google testability expert)、重读并慢慢消化他所说的确实改变了我对设计的看法。

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

我仍然struggling 拥有从该博客获得的所有信息,但实施起来并不总是那么容易,我有很多问题。但是在我掌握了 Misko Hevery 所说的话之后,我无法回到我之前所做的事情(是的,全局状态和单例(大 S)):-)

【讨论】:

+1 表示 DI。虽然我没有尽可能多地使用它,但无论我使用多少它,它都非常有帮助。 @koen:愿意举一个 PHP 中的 DI/SP 实现的 PHP 示例吗?也许@Flavius 代码使用您建议的替代模式实现? 在我的回答中添加了指向 DI 实现和容器的链接。 我现在正在阅读所有这些但我还没有阅读所有内容,我想问一下,依赖注入框架基本上是一个注册表吗? 不,不是。但是依赖注入容器也可以作为注册中心。只需阅读我在答案中发布的链接。 DI的概念解释得很实际。【参考方案2】:
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::__constructor()) 定义为私有,单例将只允许其自身的一个实例。它只是一个可用于所有“应用程序”实例的“全局”变量。这就是为什么我认为它的使用是有效的,因为它不会忽视良好的 OOP 原则。当然,就像世界上任何事物一样,这种“模式”也不应该被过度使用!

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

附录 对于那些担心TDD 的人,您仍然可以编造一些线路来依赖注入它。它可能看起来像这样:

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

为什么会这样?嗯,大多数时候应用程序不会进行单元测试,它会实际运行,希望在生产环境中。 PHP 的优势在于它的速度。 PHP 不是,也永远不会像 Java 那样成为“干净的 OOP 语言”。

在一个应用程序中,最多只有一个应用程序类和每个助手的一个实例(根据上面的延迟加载)。当然,单身人士很糟糕,但话又说回来,前提是他们不遵守现实世界。在我的示例中,他们确实如此。

诸如“单身不好”之类的刻板“规则”是邪恶的根源,它们适用于不愿独立思考的懒人。

是的,我知道,从技术上讲,PHP 宣言很糟糕。然而,它是一种成功的语言,以它的骇人听闻的方式。

附录

一种功能风格:

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 原则。 @koen:你说的是真的,一般来说,但据我了解他的问题,他在谈论应用程序的助手,并且在应用程序中只有一个......嗯, 应用程序。 注意:我所展示的甚至不是真正的单例模式。通过将构造函数定义为私有,单例将只允许类的一个实例。它只是一个可用于所有“应用程序”实例的“全局”变量。这就是为什么我认为它的有效性不会忽视良好的 OOP 原则。当然,就像世界上的任何事物一样,这种“模式”也不应该被过度使用。 -1 也来自我。它可能只是 Singleton DP 的一半,但它是丑陋的:“提供对它的全局访问”。 这确实让他现有的方法更加简洁。【参考方案3】:

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

单例

不推荐,因为它很难测试并且意味着全局状态。 (单发炎)

注册表

消除singletonitis,错误我也不推荐registry,因为它也是一种singleton。 (难以进行单元测试)

继承

可惜,PHP中没有多重继承,所以这限制了所有的链。

依赖注入

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

传统

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

框架

您可以使用自己的依赖注入器,或者使用一些依赖注入框架,例如。 Yadif

应用资源

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

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

资源加载器

一种静态对象,仅在需要时加载(创建)所需资源。这是一个非常聪明的方法。你可能会看到它在行动,例如实现Symfony's Dependency Injection component

注入特定层

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

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

在这里查看我的方法:

How to use dependency injection in Zend Framework? - Stack Overflow

最后,我想在这里添加一个关于非常重要的东西的注释 - 缓存。 一般来说,不管您选择何种技术,您都应该考虑如何缓存资源。缓存将是资源本身。

应用程序可能非常大,每次请求都加载所有资源非常昂贵。有很多方法,包括这个appserver-in-php - Project Hosting on Google Code。

【讨论】:

【参考方案4】:

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

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

在测试环境中,您可以将其定义为返回模型。您甚至可以使用 debug_backtrace() 检测内部谁调用了该函数并返回不同的对象。您可以在其中注册想要获取哪些对象的人,以了解您的程序中实际发生的情况。

【讨论】:

【参考方案5】:

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

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

【讨论】:

感谢 gcb,但类的加载不是我关心的问题,我的问题是更多的建筑性质。 虽然理论上可以回答这个问题,it would be preferable 在这里包含答案的基本部分,并提供链接以供参考。【参考方案6】:

PHP 中的对象会占用大量内存,正如您可能从单元测试中看到的那样。因此,理想的做法是尽快销毁不需要的对象,以便为其他进程节省内存。考虑到这一点,我发现每个物体都适合两种模具中的一种。

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

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

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

$object = new Class();

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

【讨论】:

【参考方案7】:

如果您想让对象在全球范围内可用,registry pattern 对您来说可能会很有趣。如需灵感,请查看Zend Registry。

Registry vs. Singleton 问题也是如此。

【讨论】:

如果你不想使用 Zend 框架,这里是 PHP5 注册表模式的一个很好的实现:phpbar.de/w/Registry 我更喜欢类型化的注册表模式,比如 Registry::GetDatabase("master"); Registry::GetSession($user->SessionKey());注册表::GetConfig("local"); [...] 并为每种类型定义一个接口。通过这种方式,您可以确保不会意外覆盖用于不同用途的密钥(即,您可能有一个“主数据库”和一个“主配置”。通过使用接口,您可以确保只使用有效的对象。Ofc 这可以也可以通过使用多个 Registry 类来实现,但恕我直言,单个类更简单易用,但仍然具有优势。 或者当然是 PHP 内置的 - $_GLOBALS【参考方案8】:

我喜欢依赖注入的概念:

“依赖注入是通过构造函数、方法或直接将依赖项赋予组件的地方。(来自Pico Container Website)”

Fabien Potencier 写了一个非常好的series of articles about Dependency Injection 并且需要使用它们。他还提供了一个不错的小型依赖注入容器,名为Pimple,我非常喜欢使用它(更多信息请参见github)。

如上所述,我不喜欢使用单例。可以在here in Steve Yegge's blog 找到关于为什么单例不是好的设计的一个很好的总结。

【讨论】:

我喜欢PHP中通过闭包的实现,读起来很有趣 我也是,他在他的网站上还有一些关于关闭的其他需要的东西:fabien.potencier.org/article/17/… 让我们希望,主流 webhouse 将很快迁移到 PHP 5.3,因为看到功能齐全的 php 5.3 服务器仍然不常见 他们将不得不这样做,当越来越多的项目需要 PHP 5.3 时,如 Zend Framework 2.0 将framework.zend.com/wiki/display/ZFDEV2/… 依赖注入也被接受了关于decupling from GOD object的问题的答案:***.com/questions/1580210/… 有一个很好的例子

以上是关于在 PHP 项目中,存在哪些存储、访问和组织辅助对象的模式? [关闭]的主要内容,如果未能解决你的问题,请参考以下文章

索引的概念

优雅的处理Redis访问超时

存储器中数据常用的存取方式有哪些?

组织 c# 项目助手或实用程序类

计算机中存储的分类

数据库系统实现2.3 加速对辅助存储器的访问