В 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 я использовал либо синтаксический шаблон, либо функции, которые обращаются к инициализированным объектам. Оба прекрасных подхода, но, как я уже сказал, я не доволен ни тем, ни другим.
Я хотел бы расширить свой горизонт тем, какие общие шаблоны существуют здесь. Я ищу примеры, дополнительные идеи и указатели на ресурсы, которые обсуждают это с точки зрения долгосрочного, реального мира.
Кроме того, мне интересно узнать о специализированных, нишевых или простых странных подходах к проблеме.
Ответы
Ответ 1
Я бы избегал подхода 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://martinfowler.com/articles/injection.html
Внедрение инъекции зависимостей (DI):
-
Я считаю, что вы должны узнать, что необходимо в конструкторе для работы объекта: new YourObject($dependencyA, $dependencyB);
-
Вы можете вручную указать необходимые объекты (зависимости) ($application = new Application(new MessageHandler()
). Но вы также можете использовать фреймворк DI (страница wikipedia обеспечивает ссылки на фреймворки PHP DI).
Важно то, что вы передаете только то, что вы на самом деле используете (вызывают действие), а не то, что вы просто передаете другим объектам, потому что им это нужно. Вот недавний пост от "дяди Боба" (Robert Martin), в котором обсуждается руководство DI с использованием фреймворка.
Еще несколько мыслей о решении Flavius. Я не хочу, чтобы этот пост был анти-пост, но я думаю, что важно понять, почему инъекция зависимостей, по крайней мере для меня, лучше, чем глобальная.
Несмотря на то, что это не реализация "true" Singleton, я все же думаю, что Flavius ошибался. Глобальное состояние плохое. Обратите внимание, что в таких решениях также трудно проверить статические методы.
Я знаю, что многие люди это делают, одобряют и используют. Но читайте статьи блога Misko Heverys (эксперт по тестированию google), перечитывая его и медленно переваривая то, что он говорит, меняет способ, которым я вижу дизайн много.
Если вы хотите испытать ваше приложение, вам нужно будет принять другой подход к разработке вашего приложения. Когда вы выполняете тестовое программирование, у вас возникнут трудности с такими вещами: "Затем я хочу реализовать регистрацию в этом фрагменте кода; сначала напишите тест, который регистрирует базовое сообщение", а затем придумайте тест, который заставит вас писать и использовать глобальный регистратор, который не может быть заменен.
Я все еще борется со всей информацией, полученной из этого блога, и это не всегда легко реализовать, и у меня много вопросов, Но я не могу вернуться к тому, что я делал раньше (да, глобальное состояние и синглтоны (большой S)) после того, как я понял, что сказал Мишко Хевери: -)
Ответ 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();
Как я это делаю, он уважает принципы ООП, это меньше кода, чем то, как вы делаете это прямо сейчас, и объект создается только тогда, когда код нуждается в нем в первый раз.
Примечание: то, что я представил, даже не является реальным одноэлементным шаблоном. Одноэлемент допускал бы только один экземпляр себя, определяя конструктор (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();
Ответ 3
Мне нравится концепция Injection Dependency:
"Инъекция зависимостей - это то, где компоненты получают свои зависимости через свои конструкторы, методы или непосредственно в поля. (Из Веб-сайт Pico Container)"
Fabien Potencier написал действительно приятную и необходимость их использования. Он также предлагает приятный и маленький контейнер для инъекций под названием Pimple, который я очень люблю использовать (подробнее об github).
Как указано выше, мне не нравится использование синглтонов. Хорошее резюме о том, почему Singletons не является хорошим дизайном, можно найти здесь, в блоге Стив Егге.
Ответ 4
Лучшим подходом является наличие контейнера для этих ресурсов. Некоторые из наиболее распространенных способов реализации этого контейнера:
Singleton
Не рекомендуется, потому что это трудно проверить и подразумевает глобальное состояние. (Singletonitis)
Реестр
Устраняет синглтонит, ошибка, которую я бы тоже не рекомендовал реестр, потому что это своего рода одноэлементный. (Трудно до unit test)
Наследование
Жаль, в PHP нет множественного наследования, поэтому это ограничивает всю цепочку.
Включение зависимостей
Это лучший подход, но большая тема.
Традиционный
Самый простой способ сделать это - использовать инсталляцию конструктора или сеттера (передать объект зависимостей с помощью setter или в конструкторе класса).
Каркасы
Вы можете свернуть свой собственный инжектор зависимостей или использовать некоторые из фреймворков инъекций зависимостей, например. Yadif
Ресурс приложения
Вы можете инициализировать каждый из ваших ресурсов в загрузочном буфере приложения (который действует как контейнер) и получить доступ к ним в любом месте приложения, доступ к объекту начальной загрузки.
Это подход, реализованный в Zend Framework 1.x
загрузчик ресурсов
Вид статического объекта, который загружает (создает) необходимый ресурс только тогда, когда это необходимо. Это очень умный подход. Вы можете увидеть это в действии, например. Внедрение Компонент Injection Dependency Dependency
Впрыск на определенный слой
Ресурсы не всегда нужны везде в приложении. Иногда вам просто нужны они, например. в контроллерах (MV C). Затем вы можете вводить ресурсы только там.
Общий подход к этому - использование комментариев docblock для добавления метаданных инъекций.
Смотрите мой подход к этому здесь:
Как использовать инъекцию зависимостей в Zend Framework? - Переполнение стека
В конце я хотел бы добавить примечание о очень важной вещи здесь - кеширование.
В общем, несмотря на выбранную вами технику, вы должны подумать, как будут кэшироваться ресурсы. Кэш будет самим ресурсом.
Приложения могут быть очень большими, и загрузка всех ресурсов по каждому запросу очень дорога. Существует много подходов, в том числе appserver-in-php - Хостинг проектов в Google Code.
Ответ 5
Если вы хотите сделать объекты доступными по всему миру, вам может быть интересен шаблон реестра. Для вдохновения посмотрите Zend Registry.
Итак, также вопрос Реестр против Синглтона.
Ответ 6
Объекты в PHP занимают достаточное количество памяти, как вы, вероятно, видели в своих модульных тестах. Поэтому он идеально подходит для уничтожения ненужных объектов как можно скорее, чтобы сохранить память для других процессов. Имея это в виду, я обнаружил, что каждый объект подходит к одной из двух форм.
1) Объект может иметь много полезных методов или должен быть вызван более одного раза, и в этом случае я реализую singleton/registry:
$object = load::singleton('classname');
//or
$object = classname::instance(); // which sets self::$instance = $this
2) Объект существует только для жизни метода/функции, вызывающего его, и в этом случае простое создание полезно для предотвращения затяжных ссылок на объекты от слишком длительного хранения объектов.
$object = new Class();
Сохранение временных объектов ANYWHERE может привести к утечке памяти, поскольку ссылки на них могут быть забыты о сохранении объекта в памяти для остальной части script.
Ответ 7
Я бы пошел для функции, возвращающей инициализированные объекты:
A('Users')->getCurrentUser();
В тестовой среде вы можете определить ее для возврата макетов. Вы даже можете обнаружить внутри, кто вызывает функцию, используя debug_backtrace() и возвращают разные объекты. Вы можете зарегистрироваться внутри него, который хочет получить какие-то объекты, чтобы получить представление о том, что происходит в вашей программе.
Ответ 8
Почему бы не прочитать точное руководство?
http://php.net/manual/en/language.oop5.autoload.php