У проекті PHP, які схеми існують для зберігання, доступу та організації допоміжних об'єктів? [зачинено]


114

Як ви організовуєте та керуєте своїми допоміжними об'єктами, такими як двигун бази даних, сповіщення користувачів, обробка помилок тощо в об'єктно-орієнтованому проекті на основі PHP?

Скажіть, у мене великий PHP CMS. CMS організовано в різні класи. Кілька прикладів:

  • об’єкт бази даних
  • управління користувачами
  • API для створення / зміни / видалення елементів
  • об’єкт обміну повідомленнями для відображення повідомлень кінцевому користувачеві
  • обробник контексту, який переведе вас на потрібну сторінку
  • клас панелі навігації, на якому показані кнопки
  • об’єкт реєстрації
  • Можливо, власна робота з помилками

тощо.

Я маю справу з вічним питанням, як найкраще зробити ці об’єкти доступними для кожної частини системи, яка цього потребує.

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

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

Потім я перейшов на шаблон Singleton і заводську функцію:

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

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

Тоді, звичайно, є можливість надати кожному об'єкту ряд покажчиків на потрібні йому помічники, мабуть, самий чистий, ресурсозберігаючий і тестуючий спосіб, але я маю сумніви в ремонтопридатності цього в перспективі.

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

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

Також мені цікаво почути про спеціалізовані, нішеві або просто дивні підходи до цього питання.


1
Я щойно задав надзвичайно подібне запитання, яке також було щедро. Ви можете оцінити деякі відповіді там: stackoverflow.com/questions/1967548 / ...
philfreo

3
Лише голова вгору, повернення нового об'єкта за посиланням - начебто $mh=&factory("messageHandler");безглуздо і не дає ніякої користі від продуктивності. Крім того, це застаріле в 5.3.
ryeguy

Відповіді:


68

Я б уникнув підходу Сінглтона, запропонованого Флавієм. Існує чимало причин уникати такого підходу. Це порушує хороші принципи ООП. У блозі тестування Google є кілька хороших статей про Singleton і як цього уникнути:

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. Хоча я не використовую його так сильно, як хотів би, він був дуже корисним у будь-яких невеликих кількостях, якими я користувався.
Анураг

1
@koen: Хочете надати PHP приклад реалізації DI / SP в PHP? Можливо, код @Flavius ​​реалізований за допомогою запропонованих альтернативних моделей?
Алікс Аксель

У мою відповідь додано посилання на реалізацію DI та контейнер.
Томас

Я читаю все це зараз, але я ще не все прочитав, хотів би запитати, чи не залежно від того, наскільки надійною є система ін'єкцій, це реєстр?
JasonDavis

Ні, не дуже. Але контейнер для ін'єкцій залежності може також слугувати реєстром. Просто прочитайте посилання, які я розмістив у своїй відповіді. Концепція DI пояснюється дійсно практично.
Томас

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();

Це так, як я це роблю, він дотримується принципів OOP, це менше коду, ніж те, як ви це робите зараз, і об’єкт створюється лише тоді, коли код потребує його вперше.

Примітка : те, що я представив, навіть не є реальним однотонним малюнком. Синглтон дозволив би лише один екземпляр себе, визначивши конструктор (Foo :: __ constructor ()) як приватний. Це лише "глобальна" змінна, доступна для всіх примірників "Application". Ось чому я вважаю, що його використання справедливе, оскільки НЕ нехтує хорошими принципами ООП. Звичайно, як і все на світі, цим «шаблоном» теж не варто перестаратися!

Я бачив, як це використовується у багатьох 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 НЕ і ніколи не буде "чистою мовою OOP", як Java.

У програмі є лише один клас програми та максимум один екземпляр кожного з його помічників (щонайменше (за ледачим завантаженням, як вище). Звичайно, одинаки погані, але знову ж таки, лише якщо вони не дотримуються реального світу. У моєму прикладі вони.

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

Так, я знаю, PHP маніфест BAD, технічно кажучи. І все-таки це вдала мова, по-хакерському.

Додаток

Один стиль функції:

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();

2
Я відповів на підтримку відповіді, тому що я вважаю, що пропонування однотонної схеми вирішення проблеми суперечить твердим принципам ООП.
koen

1
@koen: те, що ви говорите, є правдою, загалом кажучи, АЛЕ, наскільки я зрозумів його проблему, він говорить про помічників у застосуванні, а всередині додатка є лише одне ... uhm, додаток.
Флавій

Зауважте: те, що я представив, навіть не є реальним однотонним малюнком. Синглтон дозволив би лише один екземпляр класу, визначивши конструктор як приватний. Це лише "глобальна" змінна, доступна для всіх примірників "Application". Тому я вважаю, що його дійсність НЕ ігнорує хороших принципів ООП. Звичайно, як і все на світі, цим «шаблоном» також не варто перестаратися.
Флавій

-1 від мене теж. Це може бути лише половина ДП Синглтона, але це некрасиво: "забезпечте глобальний доступ до нього".
просто хтось

2
Це дійсно робить його існуючий підхід набагато чистішим.
Даніель Фон Фендж

15

Мені подобається концепція введення залежності:

"Ін'єкція залежностей - це те, де компонентам надається залежність через їх конструктори, методи або безпосередньо в поля. (З веб-сайту контейнерів Pico )"

Фабієн Потенцьє написав справді приємну серію статей про ін'єкцію залежностей та необхідність їх використання. Він також пропонує симпатичний та маленький контейнер для ін'єкцій залежності на ім’я Pimple, який я дуже люблю використовувати (більше інформації про github ).

Як було сказано вище, мені не подобається використання Singletons. Хороший підсумок того, чому Singletons не є гарним дизайном, можна знайти тут у блозі Стіва Йегге .


Мені подобається реалізація через закриття в PHP, дуже цікаве читання
Юрай Блахунка,

Мені теж у нього є інші необхідні речі щодо закриття на своєму сайті: fabien.potencier.org/article/17/…
Томас

2
сподіваємося, що веб-хаус mainstream перейде до PHP 5.3 незабаром, оскільки досі не звичайно бачити повнофункціональний сервер php 5.3
Юрай Блахунка,

Їм доведеться, коли все більше і більше проектів потребуватимуть PHP 5.3, як Zend Framework 2.0, буде Framework.zend.com/wiki/display/ZFDEV2/…
Thomas

1
Також було прийнято відповідь на ін'єкційну залежність на запитання decupling from GOD object: stackoverflow.com/questions/1580210/… з дуже приємним прикладом
Юрай Блахунка,

9

Найкращий підхід - мати якийсь контейнер для цих ресурсів. Деякі найпоширеніші способи реалізації цього контейнера :

Сінглтон

Не рекомендується, оскільки це важко перевірити, і це означає глобальний стан. (Синглетоніт)

Реєстр

Усуває синглетоніт, помилка я також не рекомендував би реєструвати, оскільки це теж різновид сингтона. (Важкий для випробування блок)

Спадщина

Шкода, що в PHP не існує багаторазового успадкування, тому це обмежує все на ланцюжку.

Ін'єкційна залежність

Це кращий підхід, але більша тема.

Традиційні

Найпростіший спосіб зробити це за допомогою конструктора або інжектора сетеру (передайте залежність об'єкту за допомогою сеттера або в конструкторі класу).

Каркаси

Ви можете прокатати свій власний інжектор залежностей або використовувати деякі рамки введення залежності, наприклад. Ядиф

Ресурс програми

Ви можете ініціалізувати кожен свій ресурс у завантажувальній програмі програми (яка виступає в якості контейнера) та отримати доступ до них будь-де в додатку, що отримує доступ до об'єкта завантаження.

Це підхід, реалізований у Zend Framework 1.x

Завантажувач ресурсів

Різновид статичного об'єкта, який завантажує (створює) потрібний ресурс лише за потреби. Це дуже розумний підхід. Ви можете бачити це в дії, наприклад, реалізуючи компонент залежної ін'єкції Symfony

Ін'єкція на певний шар

Ресурси не завжди потрібні десь у додатку. Іноді вони просто потрібні, наприклад, у контролерах (MV C ). Тоді ви можете вводити ресурси лише туди.

Загальним підходом до цього є використання коментарів docblock для додавання метаданих ін'єкції.

Дивіться мій підхід до цього тут:

Як використовувати ін'єкцію залежності в Zend Framework? - Переповнення стека

Зрештою, я хотів би додати примітку про дуже важливу справу тут - кешування.
Загалом, незважаючи на обрану техніку, слід подумати, як кешуватимуться ресурси. Кеш буде самим ресурсом.

Програми можуть бути дуже великими, а завантаження всіх ресурсів за кожним запитом дуже дороге. Існує багато підходів, включаючи цей appserver-in-php - хостинг проектів в Google Code .


6

Якщо ви хочете зробити об’єкти глобально доступними, шаблон реєстру може бути цікавим для вас. Для натхнення погляньте на реєстр Zend .

Тож також питання " Реєстр проти Сінглтона" .


Якщо ви не хочете використовувати Zend Framework, ось приємна реалізація шаблону реєстру для PHP5: phpbar.de/w/Registry
Thomas

Я віддаю перевагу введеному шаблону реєстру, як Registry :: GetDatabase ("master"); Реєстр :: GetSession ($ user-> SessionKey ()); Реєстр :: GetConfig ("локальний"); [...] та визначення інтерфейсу для кожного типу. Таким чином ви переконайтеся, що ви випадково не перезаписуєте ключ, який використовується для чогось іншого (тобто у вас може бути "головна база даних" і "головна конфігурація". Використовуючи інтерфейси, ви переконайтеся, що використовуються лише дійсні об'єкти. Ofc це може також може бути реалізований за допомогою декількох класів реєстру, але imho один є простішим та простішим у використанні, але все ж має свої переваги.
Morfildur

Або звичайно той, що вбудований у PHP - $ _GLOBALS
Gnuffo1

4

Об'єкти в PHP займають хороший об'єм пам'яті, як ви, напевно, бачили на своїх тестах. Тому ідеально знищувати непотрібні об'єкти якнайшвидше, щоб зберегти пам'ять для інших процесів. Зважаючи на це, я вважаю, що кожен предмет відповідає одній із двох форм.

1) Об'єкт може мати багато корисних методів або потрібно викликати не один раз, і тоді я реалізую сингл / реєстр:

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

2) Об'єкт існує лише протягом методу / функції, що викликає його, і в цьому випадку просте створення є вигідним для запобігання затримці посилань на об'єкт від збереження об'єктів живими занадто довго.

$object = new Class();

Зберігання тимчасових об'єктів БУДЬ-ЯКОГО може призвести до витоку пам'яті, оскільки посилання на них можуть бути забуті про збереження об'єкта в пам'яті для решти сценарію.


3

Я б пішов на функцію повернення ініціалізованих об'єктів:

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

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


-1

Чому б не прочитати прекрасний посібник?

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


Дякую gcb, але завантаження класів не хвилює мене, моє питання має більш архітектурний характер.
Pekka

Хоча це теоретично може відповісти на питання, бажано було б сюди включити істотні частини відповіді та надати посилання для довідки.
jjnguy
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.