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

Еще несколько мыслей о решении 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.

Ответ 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() и возвращают разные объекты. Вы можете зарегистрироваться внутри него, который хочет получить какие-то объекты, чтобы получить представление о том, что происходит в вашей программе.