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

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

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

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

и т.п.

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

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

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

Затем я перешел к шаблону Singleton и заводской функции:

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

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

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

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

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

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

Я бы избегал подхода Синглтона, предложенного Флавием. Существует множество причин, чтобы избежать такого подхода. Это нарушает хорошие принципы ООП. В блоге тестирования 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

альтернативы

  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

Внедрение инъекции зависимостей (DI):

  • Я считаю, что вы должны спросить, что нужно в конструкторе для объекта : new YourObject($dependencyA, $dependencyB);

  • Вы можете вручную предоставить необходимые объекты (зависимости) ( $application = new Application(new MessageHandler() ). Но вы также можете использовать инфраструктуру DI (страница wikipedia содержит ссылки на фреймворки PHP DI ).

    Важно то, что вы передаете только то, что вы на самом деле используете (назовите действие), а не то, что вы просто передаете другим объектам, потому что им это нужно. Вот последнее сообщение от «дяди Боба» (Robert Martin), в котором обсуждается руководство DI с использованием фреймворка .

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

Несмотря на то, что это не «истинная» реализация Синглтона , я все еще думаю, что Флавий ошибся. Глобальное состояние плохое . Обратите внимание, что в таких решениях также сложно тестировать статические методы .

Я знаю, что многие люди это делают, одобряют и используют. Но читая статьи блога Misko Heverys (эксперт по тестированию Google ), перечитывая его и медленно переваривая то, что он говорит, меняют способ, которым я вижу дизайн много.

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

Я все еще борюсь со всей информацией, полученной из этого блога, и ее не всегда легко реализовать, и у меня много вопросов. Но я не могу вернуться к тому, что я делал раньше (да, глобальное состояние и синглтоны (большой 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 , вы все равно можете составить некоторую проводку для зависимостей – вводите ее. Это может выглядеть так:

 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.

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

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

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

Fabien Potencier написал действительно красивую серию статей о Dependency Injection и необходимость их использования. Он также предлагает приятный и маленький контейнер для инъекций под названием Pimple, который я очень люблю использовать (подробнее о github ).

Как указано выше, мне не нравится использование синглтонов. Хорошее резюме о том, почему Singletons не очень хороший дизайн, можно найти здесь, в блоге Steve Yegge .

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

одиночка

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

реестр

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

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

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

Внедрение зависимости

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

традиционный

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

Каркасы

Вы можете катить свой собственный инжектор зависимости или использовать некоторые из фреймворков инъекций зависимостей, например. Yadif

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

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

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

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

Вид статического объекта, который загружает (создает) необходимый ресурс только тогда, когда это необходимо. Это очень умный подход. Вы можете увидеть это в действии, например, внедрение компонента Injection Symfony's Dependency Injection

Инъекция в конкретный слой

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

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

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

Как использовать инъекцию зависимостей в Zend Framework? – Переполнение стека

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

Приложения могут быть очень большими, и загрузка всех ресурсов по каждому запросу очень дорога. Существует много подходов, включая этот appserver-in-php – Project Hosting в Google Code .

Если вы хотите сделать объекты доступными по всему миру, шаблон реестра может быть вам интересен. Для вдохновения ознакомьтесь с Zend Registry .

Так что и вопрос Registry vs. Singleton .

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

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

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

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

 $object = new Class(); 

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

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

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

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

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

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