Pull to refresh

Comments 73

А сколько времени уходит на поиск имени класса через бектрейс?
Результаты генерации полноценной страницы сайта с использованием get_called_class():
0.116120052338
и с хаком:
0.122987947464
Количество используемых синглтонов — 14
Тогда, вовсе не страшно =)
если добавить ещё кеширование, то будет практически не различимо в скорости работы)
Кстати, ваш пример проще реализовать с помощью static:

<?php
class Singleton {

public static function instance() {

static $inst;
if(!isset($inst)) $inst = new static();
return $inst;

}
а у меня как? только вы учитывайте, что статическая переменная должна быть массивом, так как включает в себя не один объект, а столько, сколько классов унаследовали класс Singleton.
Не сбивайте людей с толку :).

<?php
class Singleton {
public static function instance() {

static $inst;
if(!isset($inst)) $inst = new static();
return $inst;

}
}

class S1 extends Singleton {
}

class S2 extends Singleton {
}

$s1 = S1::instance();
print_r($s1);

$s2 = S2::instance();
print_r($s2);


Выведет

S1 Object
(
)
S2 Object
(
)


Как и должен :). Статические переменные у наследников класса всегда свои, они не делятся с родительскими
Расширенный пример:

<?php
class Singleton {
public static function instance() {

static $inst;
if(!isset($inst)) $inst = new static();
return $inst;

}
}

class S1 extends Singleton {
}

class S2 extends Singleton {
}

$s1 = S1::instance();
print_r($s1);

$s2 = S2::instance();
print_r($s2);

$s1->obj = true;

print_r(S1::instance());

/* Вывод:

S1 Object
(
)
S2 Object
(
)
S1 Object
(
    [obj] => 1
)
*/
Упс :) я почему то всю жизнь думал что наоборот. Спасибо, буду знать.
Но все равно это же для PHP >= 5.3
*не правильно прочитал и не правильно понял ваше сообщение. Все верно, статические переменные у наследников класса всегда свои, они не делятся с родительскими. Я подумал вы сказали, что статические переменные родителей одинаковы для наследников )
А вам не кажется, что наследовать Singleton — это явный антипаттерн, да и просто порочная практика?
ну почему? результат работы то один и тот же, иногда удобнее и легче использовать наследие, чем писать дополнительную функцию.
Дублировать в 100500 синглтонах код типа if (!isset(self::$instance)) {
self::$instance = new Class();
}

куда более порочно
Куда более порочно иметь 100500 синглтонов
Исходим из того, что они есть. И, честно говоря, никогда не понимал нелюбовь к синглтонам. Можно, конечно, передавать объект параметром на 10 уровень вложенности вызовов, чтобы на 10-м его как-то использовать, но как-то не практично, имхо, много дублирования.
UFO just landed and posted this here
Возможно, особенно если их действительно понадобится ровно два, то есть в половине случаев нужен будет один синглтон, в половине другой, например, два разных коннекта к БД, один для персистентной БД, другой для БД в памяти. Но сходу в голову не приходит способа как можно безболезненно и без передачи параметров по цепочке такую ситуацию предвидеть заранее.

Например, глобальный конфиг приложения и/или пользовательские настройки и/или права доступа и/или имя текущего хоста для многосайтовых приложений. Понадобиться они могут в любом месте приложения от фронтконтроллера до вьюхи какого-нибудь блока в подвале. Нет, конечно, можно сделать их чтение из, например, БД в каждом месте, где понадобились. И даже организовать кэширование, чтобы не открывать 100500 коннектов и не делать 100500 запросов к БД, но как-то это не тру… Да и по сути это тот же синглтон получится, по-моему.
Знаете, мы делаем игру. Клиент-серверную. На сервере — логика, на клиенте — только отображение. У нас есть comet-коннект с сервером. Он может быть только один и точно никогда не будет коннекта с другим сервером, да и браузеры не позволяют выполнить более двух запросов, а comet-коннект как раз требует два паралельных запроса. В общем идеальная ситуация для сингтона. Благо, что я додумался не использовать синглтон.
Игра пошаговая, на клиенте логики вообще нет. Инициируется игра как-то так:
var game = new Game(connection);

Нам потребовалось сделать линейную обучалку. Было куча длинных и сложных способов, и один простой — записать все ответы сервера и сделать клиенту вид, что он общается с сервером. Просто когда клиент делал действие — ему присылался следующий ответ сервера. Какое благо, что я не пользовался синглтоном. Достаточно было всего лишь сделать так:

if (isTutorial) {
  var game = new Game(new MockConnection());
} else {
  var game = new Game(connection);
}


где MockConnection — класс в 50 строк. Обучалка не входила в наш дизайн-документ и мы придумали делать её внезапно и я бы никогда заранее не догадался бы, что мне придётся передавать в один локальный объект подменённое соединение. При чём, нельзя было бы просто заменить соединение в синглтоне, т.к., например, чат должен продолжать работать.
И этот connection у вас передается практически во все функции? Такой подход мне в голову приходил, но когда пытался реализовать его на практике, быстро утомляло передавать его в функции лишь для того, чтобы передать в другую функцию… Потом количество таких объектов увеличилось…
у меня есть ссылка на controller, который содержит connection. Файтически вместо
Game.Connection.getInstance();
// я пишу
this.controller.connection

Даже короче…
Кстати, можно сделать какой-то враппер Resources, который будет содержать все необходимые ресурсы (картинки, звуки, класс мышки т.д.), будет локальным и не вылезать за дозволенные пределы. У меня в LibCanvas раньше картинки можно было получить только с God-объекта libcanvas, который также занимался отрисовкой:
var image = libcanvas.getImage( 'battle-unit-enemy' );
libcanvas.addRender(function () {
  this.ctx.drawImage( image, 100, 100 );
});


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

var image = new BigRenderer( this.scene.resources ).render();


Кажется, этот подход с resources — это что-то из разряда паттерна Registry и он особо удобен тем, что внутри класса я знаю, чем могу пользоваться и оно всё находится в одном месте.
В принципе я тоже перешел к чему-то вроде Registry вместо кучи синглтонов, но этот Registry у меня синглтон, в нём хранится информация глобальная для приложения. Правда, с учетом того, что приложение по сути CGI и информация глобальная для запроса также является информацией глобальной для приложения. Если понадобится перевести приложение под TrueFastCGI, то это, наверное, доставит много проблем, но элегантного способа пока не нашел. Если инкапсулированную информацию о запросе ещё не сложно передать в контроллер, а информацию о пользовательских настройках он сам вытащит, то вот глобальные настройки приложения, которые могут понадобиться где угодно надо думать как передавать без того, чтобы практически каждый конструктор принимал конфиг в качестве параметра или не вытаскивал его сам.
Вы же понимаете что это частный случай? Для примера синглтон — request в любом фреймворке, естественно его никто не делает синглтоном, но почему? Его работа ведь один раз обработать пришедшие данные и все, больше работы от него не требуется, дальше лишь нужно получить в любой точке кода эти данные (гет, пост, фалы итп).
Тогда зачем вам синглтон? Потому что это круто и ооп? Обработайте все данные и запишите в глобальные переменные.
Нет, не потому что это круто, а хотябы потому что при получении данных мне надо их очистить от xss, от мусора, или от еще каких либо вещей, но это нужно делать не всегда, и я заранее не знаю нужно ли мне это будет делать или нет, поэтому и в глобальные переменные не смогу заранее записать. Да и с другой стороны синглтон — по сути та же одна глобальная переменная, грубо говоря.
Т.е., фактически, вы не знаете, находятся ли в синглтоне безопасные данные или опасные? Очистка данных от xss — это не дело класса, который получает данные от пользователя.
Фактически данные в обьекте находятся опасные, и я могу получить их очищеными по желанию. Мне нравится когда сущность не зависима и сама заниматеся собой, от этого и обьект запроса занимается очищением данных. Он независим.
Т.Е., у вашего синглтон-класса, который отвечает за данные, полученные от пользователя есть метод, скажем, xssSafe, который возвращает экранированную строку. И такой же метод есть у синглтона, который отвечает за соединение с базой данных? (ведь иногда надо вывести то, что пользователь ввёл в строку запроса, а иногда — что-то из базы). Таким образом, у вас куча синглтонов, которые кроме непосредственной обязанности ещё и фильтруют данные, при этом реализация фильтров — дублирующаяся.

Так?
Об этом можно спорить долго, у каждого будут свои взгляды (и я сомневаюсь что они двумя ограничатся), (я надеюсь бд Вы взяли лишь для примера другого синглтона, потому как он по определению не может быть синглтоном) Неужели Вы отдаете в бд сырые данные не очистив их предварительно?:) Я — нет. Они из запроса уже очищеные приходят, если мне это нужно. Если нужно нечто специфическое, тогда уже естественно в дело идут другие вещи. К тому же не так много вещей на самом деле требуют синглтонов, часто нужно несколько разных обьектов, в отличии от запроса, который всегда один (я про стандартные веб сайты конечно, не нужно углубляться в сторону демонов итп)
Лично я в БД отдаю данные почти сырые — после sql_escape. А, например, htmlspecialchars, вызываю при выводе, если нужно. То есть в базе хранятся значения фактически сырые, в том виде, в котором они поступили из запросов.
Я это и имел ввиду, но мне кажется бд был лишь пример второго (гипотетического) синглтона которому нужно тоже екранирование
я надеюсь бд Вы взяли лишь для примера другого синглтона, потому как он по определению не может быть синглтоном

Не совсем. Я его взял потому что много людей делают соединение с бд синглтоном.

Неужели Вы отдаете в бд сырые данные не очистив их предварительно

Да! Я их экранирую, чтобы они не сломали запрос, но не делаю их xssSafe. xssSafe я делаю только перед выводом пользователю.

Итого, у нес есть:
 Request::instance()->getXssSafe('message');
Database::instance()->runQuery('query')->getXssSafe('message');

Приходится делать две одинаковых реализации getXssSafe, если следовать подходу «Мне нравится когда сущность не зависима и сама заниматеся собой, от этого и обьект запроса занимается очищением данных»
В таком случае в запросе не будет медота для очистки от хсс, если он Вам там не нужен. Я очищаю данные до, Вы после, соответственно у меня методы очистки до поулчения данных, у Вас после. Не вижу ни одной проблемы.
В базе ведь должны лежать данные в сыром виде. Что если вы сделаете api, в котором будете раздавать сообщения (как в Твиттере)? А там вырезаны или экранированы важные детали. А программа, которая будет их раздавать должна их деэкранировать?

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

Конечно, если у вас сверхнагружённое приложение и профайлер показывает, что htmlspecialchars — узкое место, то имеет хранить рядом с полем data в бд ещё поле data_safe. Но я крайне сомневаюсь, что это так =)
Данные экранинуются не автоматически а по желанию, надо чистые, будут чистые, надо исходные, будут исходные.
Это я понял. Вы поймите. Дело в том, что вы своё мнение уже сформировали и врядли поменяете его несмотря на все аргументы. Но этот топик могут читать люди, которые ещё определяются и будут делать глупости, строить на базе синглтонов архитектуру.

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

Нарушен DRY =)
На самом деле и соединений с пользователем может быть больше, чем одно, даже в php =) Может, когда-то оно будет нативно в php…

На самом деле в php синглтоны не смертельны, хотя и вносят грязь архитектуру. Но я видел парня, который тащил эту идеологию в Node.JS. Ему не нравилось request передавать аргументами и он делал из него глобальную переменную, утверждая, что в php у него всё на синглтонах и отлично работает =))

Хорошо, что успел предупредить его об опасностях и он не столкнулся с трудноуловимыми и очень страшными багами, которые проявляются только при большой нагрузке.
А или Б
означает истинно А, либо Б, либо оба вместе.
Поэтому вовсе не обязательно писать «и/или»
Это русский язык, а не язык программирования :) «Или» обычно означает взаимоисключающий выбор. Я люблю пить пиво или вино, но не люблю пить и то, и другое :)
Для взаимоисключающего выбора существует союз «либо».
Объясните почему?
Что не правильно?
И главное — как правильно?
потому что случае программирования на синглтонах, ооп вырождается в процедурное программирование, где процедуры — это методы, которые работают с общими(глобальными для них) переменными — полями объекта.
Синглтоны увеличивают статическую связность, что сильно уменьшает гибкость кода.
Т.е. если вам уж так нужны процедуры — пишите, не стесняйтесь, php это позволяет.
Если заменить синглтоны на статические методы и переменные класса что-то изменится? Интуиция подсказывает, что нет, что что глобальные переменные, что синглтоны, что классы работают в любом случае в глобальном контексте. Но лично мне кажется, что синглтоны из всего этого набора выглядят наиболее элегантно.

А главное, непонятно на что их заменить хотя бы в случае read only глобального конфига приложения. Передавать конфиг в конструктор каждого класса — как-то избыточно, нарушает, имхо, DRY. Читать конфиг, вернее его часть, в конструкторе каждого класса тоже не тру, особенно из БД (каждый раз устанавливать соединение, раз объект ничего не знает о внешнем мире, да и данные для соединения нужно откуда-то брать). Вариант — использовать вместо $obj = new SomeClass(); что-то вроде $obj = Fabric::create('SomeClass'); и вписывать туда конфиг после конструирования (а то и создавать объект через рефлексию без конструктора, устанавливать конфиг, а потом вызывать конструктор), но, повторюсь, принципиальной разницы между синглтонам и статическими переменными не вижу, да и с
автодополнением в IDE проблемы будут.

Подскажите, как вы реализуете конфиг?
Простите, не до конца мысль сформулировал.
Мой ответ был на вопрос, почему 100500 синглтонов плохо.
Конечно же оправдано применение Синглтона для доступа к конфигом, базе(если она действительно одна) и и т.д.
Но таких сущностный наберется не более 5. А 100500 синглтонов — это всякие хелперы. Вот их как раз я и имел в виду.
Конфиг реализуем статичным методом фабрики конфигов. По сути синглтон + регистри
Кстати, соединения к базе я реализую вроде коллекции (массива) соединений в конфиге. По сути его и конфигом уже назвать сложно, просто глобальный контекст, но унаследовано :)
Вам нужен глобальный общий конфиг для _каждого_ класса? Мне кажется вы идете неправильным путем.
Не для каждого, но для многих, навскидку для половины. От того, какие модули включены в приложении, до коэффициентов алгоритмов расчёта.
Мне очень кажется, это можно решить другим путем. Конкретных советов дать не могу, ибо не знаю о чем идет речь.
В документации, кстати, много уже предложено решений на эту тему (с использование debug_backtrace), датированных 2009 г. Есть даже, на мой беглый взгляд, более элегантные. Единственное чего не учитывают — call_user_func, зато с разбором файла справились лучше.
UFO just landed and posted this here
Не понимаю, почему DI противопоставляется синглтону. В конце-концов их можно сочетать, задавая синглтон в конструкторе или сеттере. Классу будет все равно, синглтон там или нет.
Потому что применение синглтона в подавляющем большинстве случаев не оправдано и создает проблемы в виде хардовой зависимости и усложненного юнит-тестирования, хотя при этом может быть безболезненно использован тот же DI вместо синглтона.
DI, имхо, в этом отношении аналогична передаче объекта параметром по цепочке вызовов. К тому же, повторюсь, разве что-то мешает DI использовать для работы с синглтоном?
Не соглашусь, насчет того, что DI аналогично цепочке вызовов — в случае автоматизированного DI это не так. Кроме того, можно использовать Service Locator. А реальных юз-кейсов для синглтона на самом деле очень мало.
Кстати, каюсь, сам по молодости грешил частым использованием синглтонов (до 14 конечно не доходило, но все же). Когда дело дошло до юнит-тестирования и TDD — мигом слез, и что самое главное, никаких проблем от не использования синглтонов до сих пор нет и. я уверен, не будет :)
Автоматизированное DI — это как?
Это когда не нужно прописывать зависимости руками:-) write configuration instead of writing code

Не вижу особой разницы между кодом и конфигом в случае интерпретируемых языков. Тем более конфиг DI, который для пользователя (администратора ресурса или веб-мастера) практически бессмыслен. Ну и возникает вопрос — а конфиг DI должен быть синглтоном или как? :)
Конфиг автоматизированного DI — это XML или PHP код, выполняющийся 1 раз.
Не очень представляю как это. Время жизни различных объектов в приложении различается, как время их создания, так и время их уничтожения. Даже время объявления классов не совпадает.Я понимаю, если фабрику, которая создает объекты сделать и в ней зависимости прописывать, но ведь это будет синглтон или статический метод. Или в конструктор каждого класса её передавать по цепочке. Или я чего-то очевидного не понимаю?
Варианты и с синглтоном, и с DI имеют право на существование и могут быть использованы. Перефразируя Страуструпа, «есть паттерны, которые все ругают, и есть паттерны, которые никто не использует».
Смущает только одно — количество синглтонов (14)
Можете пояснить что именно это за объекты? Есть мнение, что скорее всего была ошибка в проектировании
моя задача была запустить рабочий проект, а не проектировать его :)
Если задаче «быстро как-нибудь запустить проект на 5.2», тогда согласен.
Если же задача «Переписать проект, так чтобы он правильно работал без костылей на 5.2» — тогда не согласен
В общем это уже филосовский вопрос касающийся постоновки задач и сроков.
Фактически статью можно назвать «Как сделать еще один костыль для версии 5.2».
Прошу не обижаться на слово «костыль», так как очень часто они не по вине программиста.
>Если же задача «Переписать проект, так чтобы он правильно работал без костылей на 5.2»
*извиняюсь, случайно нажал Ctrl+Enter
>Если же задача «Переписать проект, так чтобы он правильно работал без костылей на 5.2» — тогда не согласен

В таких случаях наверное костыль — это переписывать проект под нужную версию, тем более под устаревшую. Не проще ли обновиться :)
Неужели я один забиндил в IDE создание кода из статической переменной и статического метода getInstance?
Sign up to leave a comment.

Articles