В PHP-проекте, какие шаблоны существуют для хранения, доступа и организации вспомогательных объектов?
=====================================================================================================

Как вы организуете и управляете своими вспомогательными объектами, такими как механизм базы данных, уведомления пользователей, обработка ошибок и т.д. в объектно-ориентированном проекте на основе PHP?

Скажем, у меня есть большая PHP CMS.
CMS организован в различных классах. Несколько примеров:

* объект базы данных
* управление пользователями
* API для создания/изменения/удаления элементов
* объект обмена сообщениями для отображения сообщений конечному пользователю
* обработчик контекста, который приведет вас к правильной странице
* класс панели навигации, отображающий кнопки
* объект регистрации
* возможно, пользовательская обработка ошибок

и др.

Я имею дело с вечным вопросом, как лучше сделать эти объекты доступными для каждой части системы, которая в ней нуждается.

мой первый опыт, много лет назад, состоял в том, чтобы иметь глобальное приложение $, содержащее инициализированные экземпляры этих классов.

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

Затем я перешел к шаблону Singleton и функции factory:

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

 но я тоже этого не доволен. Модульные тесты и инкапсуляция становятся все более важными для меня, и в моем понимании логика глобалов/синглетов разрушает основную идею ООП.

Тогда есть, конечно, возможность дать каждому объекту несколько указателей на вспомогательные объекты, которые ему нужны, возможно, самый чистый, ресурсосберегающий и удобный для тестирования способ, но я сомневаюсь в его поддерживаемости в длинных запустить.

В большинстве фреймворков PHP я использовал либо синтаксический шаблон, либо функции, которые обращаются к инициализированным объектам. Оба прекрасных подхода, но, как я уже сказал, я не доволен ни тем, ни другим.

Я хотел бы расширить свой горизонт тем, какие общие шаблоны существуют здесь. Я ищу примеры, дополнительные идеи и указатели на ресурсы, которые обсуждают это с точки зрения  долгосрочного,  реального мира.

Кроме того, мне интересно узнать о специализированных, нишевых или  простых странных подходах к проблеме.

 

Я бы избегал подхода Singleton, предложенного Флавиусом. Существует множество причин, чтобы избежать такого подхода. Это нарушает хорошие принципы ООП. В блоге тестирования Google есть хорошие статьи о Синглтоне и как его избежать:

 <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):
--------------------------------------

 .i1a { width: 336px; height: 280px; }
 @media(min-width: 768px) { .i1a { width: 728px; height: 90px; } }
 @media(min-width: 992px) { .i1a { width: 468px; height: 60px; } }
 @media(min-width: 1083px) { .i1a { width: 728px; height: 90px; } }

 (adsbygoogle = window.adsbygoogle || []).push({});

 Я считаю, что вы должны [узнать, что необходимо в конструкторе для работы объекта](http://misko.hevery.com/2009/02/19/constructor-injection-vs-setter-injection/): new YourObject($dependencyA, $dependencyB);
 Вы можете вручную указать необходимые объекты (зависимости) ($application = new Application(new MessageHandler()`). Но вы также можете использовать фреймворк DI (страница wikipedia обеспечивает [ссылки на фреймворки PHP DI](http://en.wikipedia.org/wiki/Dependency_injection)).

Важно то, что вы передаете только то, что вы на самом деле используете (вызывают действие), а не то, что вы просто передаете другим объектам, потому что им это нужно. Вот недавний пост от "дяди Боба" (Robert Martin), в котором обсуждается [руководство DI с использованием фреймворка](http://blog.objectmentor.com/articles/2010/01/17/dependency-injection-inversion).

Еще несколько мыслей о решении Flavius. Я не хочу, чтобы этот пост был анти-пост, но я думаю, что важно понять, почему инъекция зависимостей, по крайней мере для меня, лучше, чем глобальная.

Несмотря на то, что это не реализация "true" [Singleton](http://misko.hevery.com/2008/08/17/singletons-are-pathological-liars/), я все же думаю, что Flavius ошибался. [Глобальное состояние плохое](http://misko.hevery.com/code-reviewers-guide/flaw-brittle-global-state-singletons/). Обратите внимание, что в таких решениях также [трудно проверить статические методы](http://misko.hevery.com/2008/12/15/static-methods-are-death-to-testability/).

Я знаю, что многие люди это делают, одобряют и используют. Но читайте статьи блога Misko Heverys ([эксперт по тестированию google](http://misko.hevery.com/about/)), перечитывая его и медленно переваривая то, что он говорит, меняет способ, которым я вижу дизайн много.

Если вы хотите испытать ваше приложение, вам нужно будет принять другой подход к разработке вашего приложения. Когда вы выполняете тестовое программирование, у вас возникнут трудности с такими вещами: "Затем я хочу реализовать регистрацию в этом фрагменте кода; сначала напишите тест, который регистрирует базовое сообщение", а затем придумайте тест, который заставит вас писать и использовать глобальный регистратор, который не может быть заменен.

Я все еще [борется](http://misko.hevery.com/2008/09/30/to-new-or-not-to-new/) со всей информацией, полученной из этого блога, и это не всегда легко реализовать, и у меня много вопросов, Но я не могу вернуться к тому, что я делал раньше (да, глобальное состояние и синглтоны (большой S)) после того, как я понял, что сказал Мишко Хевери: -)

 

```
class Application {
    protected static $_singletonFoo=NULL;

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

}

```

Так я бы это сделал. Он создает объект по требованию:

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

```

Как я это делаю, он уважает принципы ООП, это меньше кода, чем то, как вы делаете это прямо сейчас, и объект создается только тогда, когда код нуждается в нем в первый раз.

  **Примечание**: то, что я представил, даже не является реальным одноэлементным шаблоном. Одноэлемент допускал бы только один экземпляр себя, определяя конструктор (Foo:: \_\_ constructor()) как закрытый. Это всего лишь "глобальная" переменная, доступная для всех экземпляров "Приложение". Поэтому я считаю, что его использование действительно, поскольку оно не игнорирует принципы ООП. Конечно, как ничто в мире, эту "модель" тоже нельзя использовать слишком!

Я видел, что это используется во многих фреймворках PHP, Zend Framework и Yii среди них. И вы должны использовать фреймворк. Я не скажу вам, какой из них.

  **Добавление**
Для тех из вас, кто беспокоится о [TDD](http://en.wikipedia.org/wiki/Test-driven_development), вы все равно можете создать некоторую проводку для зависимостей - вставьте ее. Это может выглядеть так:

```
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;
        }
}

 (adsbygoogle = window.adsbygoogle || []).push({});

Там достаточно места для улучшения. Это просто PoC, используйте свое воображение.

Почему так? Ну, в большинстве случаев приложение не будет проверено на единицу, оно действительно будет запущено, надеюсь, в производственной среде. Силой PHP является его скорость. PHP не является и никогда не будет "чистым языком ООП", как Java.

В приложении есть только один класс приложения и только один экземпляр каждого из его помощников, максимум (в соответствии с ленивой загрузкой, как указано выше). Конечно,  синглтоны плохие, но опять же, только если они не придерживаются реального мира. В моем примере они делают.

Стереотипные "правила", такие как "синглтоны плохие", являются источником зла, они для ленивых людей, которые не хотят думать сами за себя.

Да, я знаю, что манифест 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();

```
 

Мне нравится концепция Injection Dependency:

>   "Инъекция зависимостей - это то, где компоненты получают свои зависимости через свои конструкторы, методы или непосредственно в поля. (Из  [Веб-сайт Pico Container](http://www.picocontainer.org/injection.html))" 
> 
> 
> 

Fabien Potencier написал действительно приятную  [и необходимость их использования. Он также предлагает приятный и маленький контейнер для инъекций под названием](http://fabien.potencier.org/article/11/what-is-dependency-injection) [Pimple](http://fabien.potencier.org/article/29/pimple-the-small-dependency-injection-container-for-php-5-3), который я очень люблю использовать (подробнее об [github](http://github.com/fabpot/Pimple)).

Как указано выше, мне не нравится использование синглтонов. Хорошее резюме о том, почему Singletons не является хорошим дизайном, можно найти [здесь, в блоге Стив Егге](http://steve.yegge.googlepages.com/singleton-considered-stupid).

 

Лучшим подходом является наличие  **контейнера** для этих ресурсов. Некоторые из наиболее распространенных способов реализации этого контейнера:

 Singleton
----------

Не рекомендуется, потому что это трудно проверить и подразумевает глобальное состояние. (Singletonitis)

 Реестр
-------

Устраняет синглтонит, ошибка, которую я бы тоже не рекомендовал реестр, потому что это своего рода одноэлементный. (Трудно до unit test)

 Наследование
-------------

Жаль, в PHP нет множественного наследования, поэтому это ограничивает всю цепочку.

 Включение зависимостей
-----------------------

Это лучший подход, но большая тема.

###  Традиционный

Самый простой способ сделать это - использовать инсталляцию конструктора или сеттера (передать объект зависимостей с помощью setter или в конструкторе класса).

###  Каркасы

Вы можете свернуть свой собственный инжектор зависимостей или использовать некоторые из фреймворков инъекций зависимостей, например. [Yadif](http://github.com/beberlei/yadif)

###  Ресурс приложения

Вы можете инициализировать каждый из ваших ресурсов в загрузочном буфере приложения (который действует как контейнер) и получить доступ к ним в любом месте приложения, доступ к объекту начальной загрузки.

Это подход, реализованный в Zend Framework 1.x

###  загрузчик ресурсов

Вид статического объекта, который загружает (создает) необходимый ресурс только тогда, когда это необходимо. Это очень умный подход. Вы можете увидеть это в действии, например. Внедрение [Компонент Injection Dependency Dependency](http://components.symfony-project.org/dependency-injection/)

###  Впрыск на определенный слой

Ресурсы не всегда нужны везде в приложении. Иногда вам просто нужны они, например. в контроллерах (MV  **C**). Затем вы можете вводить ресурсы только там.

Общий подход к этому - использование комментариев docblock для добавления метаданных инъекций.

Смотрите мой подход к этому здесь:

 [Как использовать инъекцию зависимостей в Zend Framework? - Переполнение стека](/questions/51104/how-to-use-dependency-injection-in-zend-framework/360859#360859)

В конце я хотел бы добавить примечание о очень важной вещи здесь - кеширование.   

В общем, несмотря на выбранную вами технику, вы должны подумать, как будут кэшироваться ресурсы. Кэш будет самим ресурсом.

Приложения могут быть очень большими, и загрузка всех ресурсов по каждому запросу очень дорога. Существует много подходов, в том числе [appserver-in-php - Хостинг проектов в Google Code](http://code.google.com/p/appserver-in-php/).

 

Если вы хотите сделать объекты доступными по всему миру, вам может быть интересен [шаблон реестра](http://sinnema313.wordpress.com/2009/03/01/the-registry-pattern/). Для вдохновения посмотрите [Zend Registry](http://framework.zend.com/manual/en/zend.registry.using.html).

Итак, также вопрос [Реестр против Синглтона](/questions/51103/registry-or-singleton-pattern-in-php).

 

Объекты в PHP занимают достаточное количество памяти, как вы, вероятно, видели в своих модульных тестах. Поэтому он идеально подходит для уничтожения ненужных объектов как можно скорее, чтобы сохранить память для других процессов. Имея это в виду, я обнаружил, что каждый объект подходит к одной из двух форм.

1) Объект может иметь много полезных методов или должен быть вызван более одного раза, и в этом случае я реализую singleton/registry:

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

```

2) Объект существует только для жизни метода/функции, вызывающего его, и в этом случае простое создание полезно для предотвращения затяжных ссылок на объекты от слишком длительного хранения объектов.

```
$object = new Class();

```

Сохранение временных объектов  **ANYWHERE** может привести к утечке памяти, поскольку ссылки на них могут быть забыты о сохранении объекта в памяти для остальной части script.

 

Я бы пошел для функции, возвращающей инициализированные объекты:

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

```

В тестовой среде вы можете определить ее для возврата макетов. Вы даже можете обнаружить внутри, кто вызывает функцию, используя debug\_backtrace() и возвращают разные объекты. Вы можете зарегистрироваться внутри него, который хочет получить какие-то объекты, чтобы получить представление о том, что происходит в вашей программе.

 

Почему бы не прочитать точное руководство?

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