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

114

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

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

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

и т.п.

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

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

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

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

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

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

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

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

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

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

Пекка 웃
источник
1
Я просто задал очень похожий вопрос, за который тоже была награда. Вы можете оценить некоторые ответы там: stackoverflow.com/questions/1967548/…
philfreo
3
Просто голову, возвращение нового объекта по ссылке - вроде $mh=&factory("messageHandler");бессмысленно и не дает никакого преимущества в производительности. Кроме того, это устарело в версии 5.3.
ryeguy

Ответы:

68

Я бы избегал подхода 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

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

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

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

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

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

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

Я все еще борюсь со всей информацией, полученной из этого блога, и ее не всегда легко реализовать, и у меня много вопросов. Но у меня нет возможности вернуться к тому, что я делал раньше (да, глобальное состояние и синглтоны (большая S)) после того, как я понял, что говорил Миско Хевери :-)

коен
источник
+1 для DI. Хотя я не использую его так часто, как хотелось бы, он очень помог в тех небольших количествах, в которых я его использовал.
Anurag
1
@koen: Не хотите ли привести пример реализации DI / SP в PHP? Может быть, код @Flavius ​​реализован с использованием предложенных вами альтернативных шаблонов?
Аликс Аксель,
В свой ответ добавлена ​​ссылка на реализацию и контейнер DI.
Thomas
Я читаю все это сейчас, но я еще не прочитал все это, я хотел бы спросить, будет ли структура внедрения зависимостей в основном реестром?
JasonDavis
Нет, не совсем. Но контейнер внедрения зависимостей также может служить в качестве реестра. Просто прочтите ссылки, которые я разместил в своем ответе. Концепция DI объясняется действительно практически.
Thomas
16
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.

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

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

Да, я знаю, манифест 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();
Flavius
источник
2
Я отклонил ответ, потому что считаю, что использование одноэлементного шаблона для решения проблемы противоречит твердым принципам ООП.
коэн
1
@koen: в общем, то, что вы говорите, правда, НО, насколько я понял его проблему, он говорит о помощниках для приложения, а внутри приложения есть только одно ... гм, приложение.
Flavius
Примечание: то, что я представил, даже не является настоящим одноэлементным шаблоном. Синглтон допускает только один экземпляр класса, определяя конструктор как закрытый. Это только «глобальная» переменная, доступная для всех экземпляров «Приложения». Вот почему я думаю, что его действительность НЕ игнорирует хорошие принципы ООП. Конечно, как и все остальное в мире, этим «шаблоном» тоже не следует злоупотреблять.
Flavius
-1 от меня тоже. Это может быть только половина Singleton DP, но уродливая: «предоставить глобальный доступ к нему».
просто кто-то
2
Это действительно делает его существующий подход намного чище.
Daniel Von Fange
15

Мне нравится концепция внедрения зависимостей:

«Инъекция зависимостей - это когда компонентам передаются зависимости через их конструкторы, методы или непосредственно в поля (с веб-сайта Pico Container )».

Фабьен Потенсье написал действительно хорошую серию статей о внедрении зависимостей и необходимости их использования. Он также предлагает красивый и небольшой контейнер для внедрения зависимостей под названием Pimple, который мне очень нравится использовать (подробнее на github ).

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

Томас
источник
Мне нравится реализация через замыкания в PHP, очень интересное чтение
Джурадж Блахунка
Я тоже, и у него есть кое-какие другие потребности относительно закрытия на его сайте: fabien.potencier.org/article/17/…
Томас
2
будем надеяться, что основные веб-сайты скоро перейдут на PHP 5.3, так как полнофункциональный сервер php 5.3 по-прежнему нечасто
нечасто
Им придется, когда все больше и больше проектов потребуют PHP 5.3, например Zend Framework 2.0, framework.zend.com/wiki/display/ZFDEV2/…
Томас
1
Внедрение зависимости также было принято ответом на вопрос о decupling from GOD object: stackoverflow.com/questions/1580210/… с очень хорошим примером
Джурадж Блахунка,
9

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

одиночка

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

реестр

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

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

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

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

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

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

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

Каркасы

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

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

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

Такой подход реализован в Zend Framework 1.x

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

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

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

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

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

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

Как использовать внедрение зависимостей в Zend Framework? - Переполнение стека

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

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

оборота takehin
источник
6

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

Так же и вопрос о реестре и синглтоне .

Феликс Клинг
источник
Если вы не хотите использовать Zend Framework, вот хорошая реализация шаблона реестра для PHP5: phpbar.de/w/Registry
Thomas
Я предпочитаю типизированный шаблон реестра, например Registry :: GetDatabase ("master"); Registry :: GetSession ($ user-> SessionKey ()); Registry :: GetConfig ( "локальный"); [...] и определение интерфейса для каждого типа. Таким образом вы убедитесь, что случайно не перезаписали ключ, используемый для чего-то другого (например, у вас может быть «основная база данных» и «основная конфигурация». Используя интерфейсы, вы убедитесь, что используются только допустимые объекты. также может быть реализован с использованием нескольких классов реестра, но имхо один проще и проще в использовании, но все же имеет преимущества.
Морфилдур
Или, конечно, тот, который встроен в PHP - $ _GLOBALS
Gnuffo1
4

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

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

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

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

$object = new Class();

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

Xeoncross
источник
3

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

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

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

Камил Сот
источник
-1

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

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

GCB
источник
Спасибо gcb, но загрузка классов меня не касается, мой вопрос больше архитектурного характера.
Pekka
Хотя теоретически это может дать ответ на вопрос, было бы предпочтительнее включить сюда основные части ответа и предоставить ссылку для справки.
jjnguy